深刻了解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對於延遲加載是很具體、明確的,如此一來至少不會形成濫用的狀況。ide

深刻理解EF Core 2.1延遲加載

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

    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。post

            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時纔會去數據庫中查詢。ui

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

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

    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依附在上下文中也就是說一旦建立實體實例將實現延遲加載。spa

固然不管是EF 6.x仍是EF Core 2.1進行以下查詢,那麼結果對於以下第二次查詢的導航屬性將不會再去數據庫中查詢,由於主體對應的導航屬性在內存中已存在,此時將直接返回,也就是說主體查詢兩次,而依賴實體則將只執行一次查詢。3d

            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月、1九、20、2一、22總計4天,在本帖,天天上午10點的第一個回帖評論者,分別贈送本書1本,good luck to u(若是您須要簽名留做記念的話私信我可告知,默認不須要,雖然我字寫的很醜)。

同一ID不能夠重複參與活動,重複的話,取緊接着的下一我的。不容許用程序刷屏,一旦發現,取消資格

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

相關文章
相關標籤/搜索