深入了解EntityFramework Core 2.1延遲加載(Lazy Loading)


前言

接下來會陸續詳細講解EF Core 2.1新特性,本節我們來講講EF Core 2.1新特性延遲加載,如果您用過EF 6.x就知道濫用延遲加載所帶來的災難,同時呢,對此深知的童鞋到了EF Core中也就造成了極大的心里陰影面積,那么到底該不該用呢?當然,完全取決於您。

 

如果初學者從未接觸過EF 6.x,我們知道EF 6.x默認啟用了延遲加載,所以這似乎有點強人所難的意味,在EF Core 2.1對於是否啟用延遲加載通過單獨提供包的形式來供我們所需,二者相對而言,EF 6.x對於延遲加載的使用是不明確、含糊其辭的,而EF Core 2.1對於延遲加載是很具體、明確的,如此一來至少不會造成濫用的情況。

深入理解EF Core 2.1延遲加載

在我個人公眾號發過一篇文章也通過示例講解了EF Core延遲加載的雛形早就有了,這里再稍微給個示例解釋EF Core 2.1之前延遲加載的影子在哪里呢?我們依然給出已經用爛了的兩個Blog和Post這兩個類,如下:

    public class Blog
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
        public DateTime CreatedTime { get; set; }
        public DateTime ModifiedTime { get; set; }
        public byte Status { get; set; }
        public bool IsDeleted { get; set; }
        public ICollection<Post> Posts { get; set; } = new List<Post>();
    }
    public class Post
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime CreatedTime { get; set; }
        public DateTime ModifiedTime { get; set; }

        public int? BlogId { get; set; }
        public Blog Blog { get; set; }

    }

接下來我們進行如下查詢,我們能夠看到此時並未利用Include顯式加載Posts,通過如下查詢EF Core內部會進行關系修正從而查詢出Posts。

            var dbContext = new EFCoreDbContext();
            var blog = dbContext.Blogs.FirstOrDefault();
            var posts = dbContext.Posts.Where(d => d.Blog.Id == blog.Id).ToList();

 

在EF Core 2.1中我們需要下載【Microsoft.EntityFrameworkCore.Proxies】包,同時在OnConfiguring方法中配置啟用延遲加載代理,如下:

除了通過如上配置外導航屬性必須用virtual關鍵字修飾(如上示例類未添加,請自行添加),否則將拋出異常,你懂的。接下來在完全配置好延遲加載的前提下再來進行如下查詢,此時在我們需要用到Posts時才會去數據庫中查詢。

            var dbContext = new EFCoreDbContext();
            var blog = dbContext.Blogs.FirstOrDefault();
            var posts = blog.Posts.ToList();

對於EF Core中的延遲加載和EF 6.x使用方式無異,接下來我們再來看看官網給出了未啟用代理也可進行延遲加載,通過安裝【Microsoft.EntityFrameworkCore.Abstractions 】包引用ILazyLoader服務進行實現,如下:

    public class Blog
    {
        private ILazyLoader LazyLoader { get; set; }

        public Blog(ILazyLoader lazyLoader)
        {
            LazyLoader = lazyLoader;
        }
        public int Id { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
        public DateTime CreatedTime { get; set; }
        public DateTime ModifiedTime { get; set; }
        public byte Status { get; set; }
        public bool IsDeleted { get; set; }

        private ICollection<Post> _posts;

        public ICollection<Post> Posts
        {
            get => LazyLoader?.Load(this, ref _posts);
            set => _posts = value;
        }
    }
    public class Post
    {
        private ILazyLoader LazyLoader { get; set; }
        public Post(ILazyLoader lazyLoader)
        {
            LazyLoader = lazyLoader;
        }
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime CreatedTime { get; set; }
        public DateTime ModifiedTime { get; set; }

        public int? BlogId { get; set; }

        private Blog _blog;
        public Blog Blog
        {
            get => LazyLoader?.Load(this, ref _blog);
            set => _blog = value;
        }
    }

通過如上注入ILazyLoader服務依附於實體上最終實現延遲加載,這么做對於啟用延遲加載代理的最大好處在於只針對特定實體進行延遲加載,而使用延遲加載代理則是全局配置,所有實體都必須通過virtual關鍵字修飾且都會實現延遲加載。同時呢,通過如下圖可知,此時ILazyLoader依附在上下文中也就是說一旦創建實體實例將實現延遲加載。

當然無論是EF 6.x還是EF Core 2.1進行如下查詢,那么結果對於如下第二次查詢的導航屬性將不會再去數據庫中查詢,因為主體對應的導航屬性在內存中已存在,此時將直接返回,也就是說主體查詢兩次,而依賴實體則將只執行一次查詢。

            var dbContext = new EFCoreDbContext();
            var blog = dbContext.Blogs.FirstOrDefault();
            var posts = blog.Posts.ToList();

            var blog1 = dbContext.Blogs.FirstOrDefault();
            var posts1 = blog1.Posts.ToList();

接下來我們再來看看在上下文實例池中啟用延遲加載並結合顯式加載看看EF Core執行策略是怎樣的呢?

            var services = new ServiceCollection();
            services.AddDbContextPool<EFCoreDbContext>(options =>
            {
                var loggerFactory = new LoggerFactory();
                loggerFactory.AddConsole(LogLevel.Debug);
                options.UseLazyLoadingProxies().UseLoggerFactory(loggerFactory).UseSqlServer("data source=WANGPENG;User Id=sa;Pwd=sa123;initial catalog=EFCore2xDb;integrated security=True;MultipleActiveResultSets=True;");
            });

            var serviceProvider = services.BuildServiceProvider();

            var dbContext = serviceProvider.GetRequiredService<EFCoreDbContext>();

            var blog = dbContext.Blogs.Include(d => d.Posts).Last();
            var posts = blog.Posts.FirstOrDefault();


            var blog1 = dbContext.Blogs.FirstOrDefault();
            var posts1 = blog1.Posts.FirstOrDefault();

上述查詢並結合最終生成的SQL得知:當沒有執行顯式加載時若我們啟用延遲加載則強制執行延遲加載,否則執行顯式加載,同時我們通過驗證也得知注入ILazyLoader服務后並調用Load方法加載關聯實體內部本質則是若要訪問的關聯實體在內存中不存在時則執行RPC加載關聯實體,否則將從內存中獲取

如上所述若我們沒有顯式進行飢餓加載,那么在啟用延遲加載的前提下對於任何關聯實體是否都會執行延遲加載呢?比如復雜屬性呢?我們再來看看如下示例(請自行在上述配置上下文實例池中啟用延遲加載代理)。

public class StreetAddress
    {
        public string Street { get; set; }
        public string City { get; set; }
    }

    public class Order
    {
        public int Id { get; set; }
        public virtual StreetAddress ShippingAddress { get; set; }
    }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Order>(e => 
            {
                e.ToTable("Orders");

                e.HasKey(k => k.Id);

                e.OwnsOne(o => o.ShippingAddress);
            });
        }
            var order = dbContext.Orders.FirstOrDefault();
            var shippingAddress = order.ShippingAddress;

我們從如上圖生成的SQL得知:對於復雜屬性即通過OwnOne配置的復雜屬性在查詢時,EF Core會一次性將復雜屬性值全部返回(也就是說復雜屬性值總是會加載),所以對於啟用延遲加載不會起到任何作用,即使是通過Include進行顯式加載都是多此一舉。

延遲加載避免循環引用 

無論是EF 6.x還是EF Core 2.1中的延遲加載都無法避免循環引用問題,若在.NET Core Web應用程序中啟用了延遲加載,我們需要在ConfigureServices方法中進行如下配置才行。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().AddJsonOptions(
            options => options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
        }

在寫博客期間有一些童鞋在評論中留言問我是如何學習的,每個人學習方式肯定不一樣,但是絕對少不了多敲代碼,這里就有一個實際例子(這里不是批評那位童鞋哈),有一位童鞋購買了我寫的書(首先感謝那位童鞋),看到某一章節問我那里是不是錯了,我一看大概就問了那位童鞋是不是沒敲代碼,個人覺得無論是看技術書還是看技術博客,還是要敲一遍為好,因為那個章節的某一個類是在某一命名空間下的,估計那位童鞋沒見過所以以為錯了,別光顧着看,多敲敲代碼驗證驗證總沒錯的。

評論送書規則

6月、19、20、21、22總計4天,在本帖,每天上午10點的第一個回帖評論者,分別贈送本書1本,good luck to u(如果您需要簽名留作紀念的話私信我可告知,默認不需要,雖然我字寫的很丑)。

同一ID不可以重復參與活動,重復的話,取緊接着的下一個人。不允許用程序刷屏,一旦發現,取消資格

明確確認您滿足以上規則后,請寫下您的地址、姓名、郵編、手機號私信給我,以便后續郵寄。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM