加速实体框架核心

别贪心!


选择数据时,您一次需要选择的数量完全相同。切勿从表中检索所有数据!

错误:

using var ctx = new EFCoreTestContext(optionsBuilder.Options);                
//    ID  ,       !
ctx.FederalDistricts.Select(x=> new { x.ID, x.Name, x.ShortName }).ToList();

正确地:

using var ctx = new EFCoreTestContext(optionsBuilder.Options);  
//     ID     !
ctx.FederalDistricts.Select(x=> new { x.Name, x.ShortName }).ToList();
ctx.FederalDistricts.Select(x => new MyClass { Name = x.Name, ShortName = x.ShortName }).ToList();


错误:

var blogs = context.Blog.ToList(); //       . ?
//     ?
var somePost = blogs.FirstOrDefault(x=>x.Title.StartWidth(“Hello world!”));

正确地:

var somePost = context.Blog.FirstOrDefault(x=>x.Title.StartWidth(“Hello world!”));

当查询返回一些记录时,可以执行集成数据验证。

错误:


var blogs = context.Blogs.Where(blog => StandardizeUrl(blog.Url).Contains("dotnet")).ToList();

public static string StandardizeUrl(string url)
{
    url = url.ToLower();
    if (!url.StartsWith("http://"))
    {
        url = string.Concat("http://", url);
    }
    return url;
}

正确地:

var blogs = context.Blogs.AsEnumerable().Where(blog => StandardizeUrl(blog.Url).Contains("dotnet")).ToList();
 
//  
var blogs = context.Blogs.Where(blog => blog.Contains("dotnet"))
    .OrderByDescending(blog => blog.Rating)
    .Select(blog => new
    {
        Id = blog.BlogId,
        Url = StandardizeUrl(blog.Url)
    })
    .ToList();

哇,哇,哇!

现在该刷新一下您对LINQ技术的知识了。

让我们看一下ToList AsEnumerable AsQueryable 之间的区别


所以要列出

  • .
  • .ToList() (lazy loading), .

AsEnumerable

  • (lazy loading)
  • : Func <TSource, bool>
  • ( Where/Take/Skip , , select * from Table1,
  • , N )
  • : Linq-to-SQL + Linq-to-Object.
  • IEnumerable (lazy loading).

AsQueryable

  • (lazy loading)
  • :
    AsQueryable(IEnumerable)  AsQueryable<TElement>(IEnumerable<TElement>) 

  • Expression T-SQL ( ), .
  • DbSet ( Entity Framework) AsQueryable .
  • , Take(5) «select top 5 * SQL» . , SQL , . AsQueryable() , AsEnumerable() T-SQL Linq .
  • 如果希望在服务器端运行之前可以改善数据库查询,请使用AsQueryable


在最简单的情况下使用AsQueryable的示例:

public IEnumerable<EmailView> GetEmails(out int totalRecords, Guid? deviceWorkGroupID,
                DateTime? timeStart, DateTime? timeEnd, string search, int? confirmStateID, int? stateTypeID, int? limitOffset, int? limitRowCount, string orderBy, bool desc)
        {
            var r = new List<EmailView>();

            using (var db = new GJobEntities())
            {
                var query = db.Emails.AsQueryable();

                if (timeStart != null && timeEnd != null)
                {
                    query = query.Where(p => p.Created >= timeStart && p.Created <= timeEnd);
                }

                if (stateTypeID != null && stateTypeID > -1)
                {
                    query = query.Where(p => p.EmailStates.OrderByDescending(x => x.AtTime).FirstOrDefault().EmailStateTypeID == stateTypeID);
                }


                if (confirmStateID != null && confirmStateID > -1)
                {
                    var boolValue = confirmStateID == 1 ? true : false;
                    query = query.Where(p => p.IsConfirmed == boolValue);
                }

                if (!string.IsNullOrEmpty(search))
                {
                    search = search.ToLower();
                    query = query.Where(p => (p.Subject + " " + p.CopiesEmails + " " + p.ToEmails + " " + p.FromEmail + " " + p.Body)
                                        .ToLower().Contains(search));
                }

                if (deviceWorkGroupID != Guid.Empty)
                {
                    query = query.Where(x => x.SCEmails.FirstOrDefault().SupportCall.Device.DeviceWorkGroupDevices.FirstOrDefault(p => p.DeviceWorkGroupID == deviceWorkGroupID) != null);
                }

                totalRecords = query.Count();
                query = query.OrderByDescending(p => p.Created);
                if (limitOffset.HasValue)
                {
                    query = query.Skip(limitOffset.Value).Take(limitRowCount.Value);
                }
                var items = query.ToList(); //    

                foreach (var item in items)
                {
                    var n = new EmailView
                    {
                        ID = item.ID,
                        SentTime = item.SentTime,
                        IsConfirmed = item.IsConfirmed,
                        Number = item.Number,
                        Subject = item.Subject,
                        IsDeleted = item.IsDeleted,
                        ToEmails = item.ToEmails,
                        Created = item.Created,
                        CopiesEmails = item.CopiesEmails,
                        FromEmail = item.FromEmail,
                    };

                    //     - 

                    r.Add(n);
                }
            }

            return r;
        }


简单阅读的魔力


如果不需要更改数据,只需使用.AsNoTracking()方法显示即可

采样缓慢

var blogs = context.Blogs.ToList();

快速获取(只读)

var blogs = context.Blogs.AsNoTracking().ToList();

感觉您已经预热了一点?

加载相关数据的类型


对于那些忘记了懒惰加载的人

延迟加载(Lazy loading)是指访问导航属性时从数据库透明加载的关联数据。在这里阅读更多

同时,让我提醒您其他类型的加载相关数据。

活动加载积极加载)是指作为初始请求的一部分从数据库中加载关联的数据。

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
                .ThenInclude(author => author.Photo)
        .Include(blog => blog.Owner)
            .ThenInclude(owner => owner.Photo)
        .ToList();
}

注意!从版本EF Core 3.0.0开始,每个Include都会将一个附加的JOIN添加到关系提供程序生成的SQL查询中,而以前的版本会生成附加的SQL查询。无论是好是坏,这都会大大改变查询的性能。特别是,包含大量包含语句的LINQ查询可以分为几个单独的LINQ查询。

显式加载(Explicit loading)表示以后从数据库显式加载的关联数据。

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    var goodPosts = context.Entry(blog)
        .Collection(b => b.Posts)
        .Query()
        .Where(p => p.Rating > 3)
        .ToList();
}

混蛋和突破!继续?

准备加快速度了吗?


为了在从关系数据库中获取结构复杂甚至异常的数据时显着提高速度,有两种方法可以做到这一点:使用索引视图(1)或更好地以简单的平面形式预先准备(计算)的数据进行显示(2)。

(1)MS SQL Server上下文中的索引视图

索引视图具有唯一的聚集索引。唯一的聚集索引存储在SQL Server中,并且像其他任何聚集索引一样进行更新。索引视图比标准视图更重要,标准视图包括大量行的复杂处理,例如,聚合大量数据或合并多行。

如果查询中经常引用此类视图,则可以通过为视图创建唯一的聚集索引来提高性能。对于标准视图,结果集未存储在数据库中;而是为每个查询计算结果集,但是对于聚集索引而言,结果集以与具有聚集索引的表相同的方式存储在数据库中。没有专门使用索引视图的查询甚至可能受益于该视图中存在聚集索引。

就性能而言,提出索引具有一定的成本。如果创建索引视图,则每次更改基表中的数据时,SQL Server不仅必须支持这些表中的索引记录,而且还必须支持视图中的索引记录。在面向开发人员和企业的SQL Server版本中,优化器可以使用视图索引来优化未指定索引视图的查询。但是,在其他版本的SQL Server中,查询必须包括索引视图并提供NOEXPAND提示以利用视图中的索引。

(2)如果您需要发出一个请求,要求显示三层以上或更多的相关表,并且CRUD增加加载时,最好的方法是定期计算结果集,将其保存在表格中并用于显示。将在其中存储数据的结果表应具有主键,并在LINQ的搜索字段上具有索引

异步呢?


是! 我们会尽可能使用它!这是一个例子:

public void Do()
{
    var myTask = GetFederalDistrictsAsync ();
    foreach (var item in myTask.Result)
    {
         // 
    }
}

public async Task<List<FederalDistrict>> GetFederalDistrictsAsync()
{
    var conn = configurationRoot.GetConnectionString("EFCoreTestContext");
    optionsBuilder.UseSqlServer(conn);
    using var context = new EFCoreTestContext(optionsBuilder.Options);
    return await context.FederalDistricts.ToListAsync();
}

是的,您是否忘了增加生产率的任何事情?um!

return await context.FederalDistricts.<b>AsNoTracking()</b>.ToListAsync();


注意:添加Do()方法仅用于演示目的,以指示GetFederalDistrictsAsync()方法的可操作性正如我的同事正确指出的那样,还需要另一个纯异步的例子。

让我基于ASP .NET Core中的视图组件的概念给出它

//  
public class PopularPosts : ViewComponent
    {
        private readonly IStatsRepository _statsRepository;

        public PopularPosts(IStatsRepository statsRepository)
        {
            _statsRepository = statsRepository;
        }

        public async Task<IViewComponentResult> InvokeAsync()
        {
           //         -
            var federalDistricts = await _statsRepository.GetFederalDistrictsAsync(); 
            var model = new TablePageModel()
            {
                FederalDistricts = federalDistricts,
            };

            return View(model);
        }
    }
    // 
    
    /// <summary>
    ///  -   .... -
    /// </summary>
    public interface IStatsRepository
    {
        /// <summary>
        ///        
        /// </summary>
        /// <returns></returns>
        IEnumerable<FederalDistrict> FederalDistricts();

        /// <summary>
        ///        
	/// !!!
        /// </summary>
        /// <returns></returns>
        Task<List<FederalDistrict>> GetFederalDistrictsAsync();
    }	
	
    /// <summary>
    /// -   .... -
    /// </summary>
    public class StatsRepository : IStatsRepository
    {
        private readonly DbContextOptionsBuilder<EFCoreTestContext>
            optionsBuilder = new DbContextOptionsBuilder<EFCoreTestContext>();
        private readonly IConfigurationRoot configurationRoot;

        public StatsRepository()
        {
            IConfigurationBuilder configurationBuilder = new ConfigurationBuilder()
                    .SetBasePath(Environment.CurrentDirectory)
                    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
            configurationRoot = configurationBuilder.Build();
        }

        public async Task<List<FederalDistrict>> GetFederalDistrictsAsync()
        {
            var conn = configurationRoot.GetConnectionString("EFCoreTestContext");
            optionsBuilder.UseSqlServer(conn);
            using var context = new EFCoreTestContext(optionsBuilder.Options);
            return await context.FederalDistricts.Include(x => x.FederalSubjects).ToListAsync();
        }

        public IEnumerable<FederalDistrict> FederalDistricts()
        {
            var conn = configurationRoot.GetConnectionString("EFCoreTestContext");
            optionsBuilder.UseSqlServer(conn);

            using var ctx = new EFCoreTestContext(optionsBuilder.Options);
            return ctx.FederalDistricts.Include(x => x.FederalSubjects).ToList();
        }
    }

   //         Home\Index 
    <div id="tableContainer">
            @await Component.InvokeAsync("PopularPosts")
     </div>
  //   HTML      Shared\Components\PopularPosts\Default.cshtml



让我提醒您何时在Entity Framework Core中执行查询。

调用LINQ语句时,您只需在内存中创建查询视图。仅在处理结果之后,该请求才发送到数据库。

以下是导致将请求发送到数据库的最常见操作。

  • 在for循环中遍历结果。
  • 使用运算符,例如ToList,ToArray,Single,Count。
  • 将查询结果中的数据绑定到用户界面。

如何根据应用程序体系结构组织EF Core代码?


(1)从应用程序体系结构的角度来看,您需要确保对数据库的访问代码是在明确定义的位置(隔离)中隔离/分隔的。这使您可以查找影响性能的数据库代码。

(2)请勿将数据库的访问代码与应用程序的其他部分(例如用户界面或API)混合使用。因此,可以更改数据库访问代码,而不必担心与数据库无关的其他问题。

如何使用SaveChanges正确且快速地保存数据


如果插入的记录相同,则对所有记录使用一个保存操作是有意义的。

错误

using(var db = new NorthwindEntities())
{
var transaction = db.Database.BeginTransaction();

try
{
    //   1
    var  obj1 = new Customer();
    obj1.CustomerID = "ABCDE";
    obj1.CompanyName = "Company 1";
    obj1.Country = "USA";
    db.Customers.Add(obj1);

  //          db.SaveChanges();

    //   2
    var  obj2 = new Customer();
    obj2.CustomerID = "PQRST";
    obj2.CompanyName = "Company 2";    
    obj2.Country = "USA";
    db.Customers.Add(obj2);

    //   
    db.SaveChanges();

    transaction.Commit();
}
catch
{
    transaction.Rollback();
}
}

正确地

using(var db = new NorthwindEntities())
{
var transaction = db.Database.BeginTransaction();

try
{
   //  1
    var  obj1 = new Customer();
    obj1.CustomerID = "ABCDE";
    obj1.CompanyName = "Company 1";
    obj1.Country = "USA";
    db.Customers.Add(obj1); 

    //   2
    var  obj2 = new Customer();
    obj2.CustomerID = "PQRST";
    obj2.CompanyName = "Company 2";    
    obj2.Country = "USA";
    db.Customers.Add(obj2);

   //    N 
    db.SaveChanges();

    transaction.Commit();
}
catch
{
    transaction.Rollback();
}
}

规则总是有例外。如果事务上下文很复杂,即由几个独立的操作组成,则可以在每个操作之后进行保存。而且在事务中使用异步存储更为正确。

//    
public async Task<IActionResult> AddDepositToHousehold(int householdId, DepositRequestModel model)
{
    using (var transaction = await Context.Database.BeginTransactionAsync(IsolationLevel.Snapshot))
    {
        try
        {
            //     
            var deposit = this.Mapper.Map<Deposit>(model);
            await this.Context.Deposits.AddAsync(deposit);

            await this.Context.SaveChangesAsync();

            //    
               var debtsToPay = await this.Context.Debts.Where(d => d.HouseholdId == householdId && !d.IsPaid).OrderBy(d => d.DateMade).ToListAsync();

            debtsToPay.ForEach(d => d.IsPaid = true);

            await this.Context.SaveChangesAsync();

            //   
            var household = this.Context.Households.FirstOrDefaultAsync(h => h.Id == householdId);

            household.Balance += model.DepositAmount;

            await this.Context.SaveChangesAsync();

            transaction.Commit();
            return this.Ok();
        }
        catch
        {
            transaction.Rollback();
            return this.BadRequest();
        }
    }
}

触发器,计算字段,自定义函数和EF Core


为了减轻包含EF Core的应用程序的负担,可以使用简单的计算字段和数据库触发器,但最好不要介入,因为应用程序可能会造成很大的混乱。但是用户定义的函数可能非常有用,尤其是在获取操作期间!

EF Core中的并发


如果要并行化所有内容以加快速度,请中断:EF Core不支持在一个上下文实例中执行多个并行操作。等待一个操作完成,然后再开始下一个操作。为此,通常需要在每个异步操作中指定await关键字。

EF Core使用异步查询来避免在数据库中执行查询时阻塞流程。异步请求对于确保胖客户端中的快速用户界面响应非常重要。它们还可以提高Web应用程序中的吞吐量,您可以在其中释放线程以处理其他请求。这是一个例子:

public async Task<List<Blog>> GetBlogsAsync()
{
    using (var context = new BloggingContext())
    {
        return await context.Blogs.ToListAsync();
    }
}

您对LINQ编译查询了解多少?


如果您的应用程序在Entity Framework中重复执行结构上相似的查询,则通常可以通过一次编译查询并使用不同的参数多次执行查询来提高性能。例如,一个应用程序可能需要吸引特定城市的所有客户。用户在运行时在表格中指示城市。 LINQ to Entities支持为此目的使用编译查询。

从.NET Framework 4.5开始,LINQ查询将自动缓存。但是,您仍然可以使用已编译的LINQ查询来减少后续运行的成本,并且已编译的查询比自动缓存的LINQ查询更有效。请注意,将Enumerable.Contains运算符应用于内存中集合的LINQ to Entities查询不会自动缓存。此外,不允许在已编译的LINQ查询中对内存中的集合进行参数化。

这里可以找到许多示例

不要创建大型DbContext上下文!


总的来说,我知道你们中的许多人(即使不是几乎全部)都是懒惰的f_u__c_k__e_r__s以及整个数据库,它们都放在一个上下文中,尤其是对于数据库优先方法而言,这是典型的。而且徒劳地做到了!以下是如何划分上下文的示例。当然,上下文之间的连接表将必须重复,这是一个减号。一种或另一种方式,如果上下文中的表超过50个,则最好考虑对其进行划分。

使用上下文分组(池化DdContext)


DbContext 池的含义是允许重用该池中DbContext实例,在某些情况下,与每次创建一个新实例相比,它可以带来更好的性能。这也是在ADO.NET中创建连接池的主要原因,尽管由于连接通常是更难的资源,所以连接的性能提升将更为显着。

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Demos
{
    public class Blog
    {
        public int BlogId { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
    }

    public class BloggingContext : DbContext
    {
        public static long InstanceCount;

        public BloggingContext(DbContextOptions options)
            : base(options)
            => Interlocked.Increment(ref InstanceCount);

        public DbSet<Blog> Blogs { get; set; }
    }

    public class BlogController
    {
        private readonly BloggingContext _context;

        public BlogController(BloggingContext context) => _context = context;

        public async Task ActionAsync() => await _context.Blogs.FirstAsync();
    }

    public class Startup
    {
        private const string ConnectionString
            = @"Server=(localdb)\mssqllocaldb;Database=Demo.ContextPooling;Integrated Security=True;ConnectRetryCount=0";

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<BloggingContext>(c => c.UseSqlServer(ConnectionString));
        }
    }

    public class Program
    {
        private const int Threads = 32;
        private const int Seconds = 10;

        private static long _requestsProcessed;

        private static async Task Main()
        {
            var serviceCollection = new ServiceCollection();
            new Startup().ConfigureServices(serviceCollection);
            var serviceProvider = serviceCollection.BuildServiceProvider();

            SetupDatabase(serviceProvider);

            var stopwatch = new Stopwatch();

            MonitorResults(TimeSpan.FromSeconds(Seconds), stopwatch);

            await Task.WhenAll(
                Enumerable
                    .Range(0, Threads)
                    .Select(_ => SimulateRequestsAsync(serviceProvider, stopwatch)));
        }

        private static void SetupDatabase(IServiceProvider serviceProvider)
        {
            using (var serviceScope = serviceProvider.CreateScope())
            {
                var context = serviceScope.ServiceProvider.GetService<BloggingContext>();

                if (context.Database.EnsureCreated())
                {
                    context.Blogs.Add(new Blog { Name = "The Dog Blog", Url = "http://sample.com/dogs" });
                    context.Blogs.Add(new Blog { Name = "The Cat Blog", Url = "http://sample.com/cats" });
                    context.SaveChanges();
                }
            }
        }
        private static async Task SimulateRequestsAsync(IServiceProvider serviceProvider, Stopwatch stopwatch)
        {
            while (stopwatch.IsRunning)
            {
                using (var serviceScope = serviceProvider.CreateScope())
                {
                    await new BlogController(serviceScope.ServiceProvider.GetService<BloggingContext>()).ActionAsync();
                }

                Interlocked.Increment(ref _requestsProcessed);
            }
        }

        private static async void MonitorResults(TimeSpan duration, Stopwatch stopwatch)
        {
            var lastInstanceCount = 0L;
            var lastRequestCount = 0L;
            var lastElapsed = TimeSpan.Zero;

            stopwatch.Start();

            while (stopwatch.Elapsed < duration)
            {
                await Task.Delay(TimeSpan.FromSeconds(1));

                var instanceCount = BloggingContext.InstanceCount;
                var requestCount = _requestsProcessed;
                var elapsed = stopwatch.Elapsed;
                var currentElapsed = elapsed - lastElapsed;
                var currentRequests = requestCount - lastRequestCount;

                Console.WriteLine(
                    $"[{DateTime.Now:HH:mm:ss.fff}] "
                    + $"Context creations/second: {instanceCount - lastInstanceCount} | "
                    + $"Requests/second: {Math.Round(currentRequests / currentElapsed.TotalSeconds)}");

                lastInstanceCount = instanceCount;
                lastRequestCount = requestCount;
                lastElapsed = elapsed;
            }

            Console.WriteLine();
            Console.WriteLine($"Total context creations: {BloggingContext.InstanceCount}");
            Console.WriteLine(
                $"Requests per second:     {Math.Round(_requestsProcessed / stopwatch.Elapsed.TotalSeconds)}");

            stopwatch.Stop();
        }

如何避免EF Core中的CRUD造成不必要的错误?


切勿在同一代码中插入计算。始终将对象的形成/准备及其插入/更新分开。只需按功能进行传播即可:检查用户输入的数据,计算必要的初步数据,映射或创建对象以及实际的CRUD操作。

当应用程序性能真的不好时该怎么办?


啤酒在这里绝对无济于事。但是,这将有助于在应用程序体系结构中进行读写分离,然后在套接字上分配这些操作。考虑使用命令和查询责任隔离(CQRS)模式,并尝试将表拆分为插入并在两个数据库之间读取。

向您,朋友和同事们加速应用程序!

Source: https://habr.com/ru/post/undefined/


All Articles