EntityFramework Core指定更新導航屬性了解一下?


前言

本文來自和何鎮汐大哥的探討,很多時候我習慣於和別人交流過后會思考一些問題,無論是天馬行空還是淺薄的想法都會記錄下來,或許看到此博文的您能給我更多的思考,與人交流總能收獲很多東西,出發點不一樣則結論 不一樣,思維方式不一樣則路徑不一樣,願你我共同進步。

EntityFramework Core無跟蹤視圖

首先依然給出本文需要用到的兩個實體,如下:

    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; }
      
    }

在EF Core中給我們提供了Update和UpdateRange方法,這兩個方法你說作用大吧,我看作用也沒有那么大。要利用這兩個方法,必須對值進行一一賦值,如下:

            var dbContext = new EFCoreDbContext();
            var dbBlog = dbContext.Blogs.Include(d => d.Posts).FirstOrDefault(d => d.Id == 1);
            dbBlog.Name = "Jeffcky";
            foreach (var post in dbBlog.Posts)
            {
                post.Name = "《你必須掌握的EntityFramework 6.x與Core 2.0》";
            }
            dbContext.Update(dbBlog);
            dbContext.SaveChanges();

在EF 6.x中缺失Update和UpdateRange方法,但是它可以進行如下更新啊不是。

            var dbContext = new EFCoreDbContext();
            var newBlog = new Blog()
            {
                Id = 1,
                Name = "Jeffcky1",
                IsDeleted = false,
                Status = 0,
                Url = "https://www.cnblogs.com/CreateMyself",
                CreatedTime = DateTime.Now,
                ModifiedTime = DateTime.Now,
                Posts = new List<Post>()
                {
                    new Post()
                    {
                        Id = 1,
                        BlogId = 1,
                        Name = "EF Core TrackGraph",
                        CreatedTime = DateTime.Now,
                        ModifiedTime = DateTime.Now
                    }
                }
            };

            var dbBlog = dbContext.Blogs.Include(d => d.Posts).FirstOrDefault(d => d.Id == 1);
            dbContext.Entry(dbBlog).CurrentValues.SetValues(newBlog);    
            dbContext.SaveChanges();

不太了解詳情的童鞋可能就說了在EF Core中也可以利用上述CurrentValues來指定更新列啊,如果您這樣想那就大錯特錯了,來我們在EF Core中同樣運行上述代碼通過對比前后表中數據看看。

更新前

更新后

我們通過對比可看到,導航屬性對應的表沒有進行更新,不要問我為啥,在前面我也有講過在EF Core中這種情況類似於和添加一樣通過手動這是狀態為Added,在EF 6.x中只要更新主表則對應與之相關的導航屬性也會更新,但是在EF Core中只會更新主表,EF 6.x這么好的指定更新反而被剔除了,實在不應該啊。有人說賦值兩次啊,不好意思也不行,如下:

            var dbContext = new EFCoreDbContext();
            var newBlog = new Blog()
            {
                Id = 1,
                Name = "Jeffcky1",
                IsDeleted = false,
                Status = 0,
                Url = "https://www.cnblogs.com/CreateMyself",
                CreatedTime = DateTime.Now,
                ModifiedTime = DateTime.Now,
                Posts = new List<Post>()
                {
                    new Post()
                    {
                        Id = 1,
                        BlogId = 1,
                        Name = "EF Core TrackGraph",
                        CreatedTime = DateTime.Now,
                        ModifiedTime = DateTime.Now
                    }
                }
            };

            var dbBlog = dbContext.Blogs.Include(d => d.Posts).FirstOrDefault(d => d.Id == 1);
           dbContext.Entry(dbBlog).CurrentValues.SetValues(newBlog); dbContext.Entry(dbBlog.Posts).CurrentValues.SetValues(newBlog.Posts);
            dbContext.SaveChanges();

上述這種方式對關系映射是不行的,但是若是復雜屬性則是可以,如下:

    [Owned]
    public class StreetAddress
    {
        public string Street { get; set; }
        public string City { get; set; }
    }

    public class Order
    {
        public int Id { get; set; }
        public StreetAddress ShippingAddress { get; set; }
    }

            var dbContext = new EFCoreDbContext();
            var order = dbContext.Orders.FirstOrDefault();
            order.ShippingAddress.City = "city";
            order.ShippingAddress.Street = "street";
            dbContext.SaveChanges();

這樣更新肯定是可以的,我們不做過多探討,利用CurrentValues只能進行兩次賦值才行,如下。

            var newOrder = new Order()
            {
                Id = 1,
                ShippingAddress = new StreetAddress()
                {
                    City = "city",
                    Street = "street"
                }
            };
            var dbContext = new EFCoreDbContext();
            var order = dbContext.Orders.FirstOrDefault();
            dbContext.Entry(order).CurrentValues.SetValues(newOrder);
            dbContext.Entry(order.ShippingAddress).CurrentValues.SetValues(newOrder.ShippingAddress); var result = dbContext.SaveChanges();

讓我們再次回到更新Blog,除了利用CurrentValues指定更新外,我們還可以在查詢Posts時不進行顯式加載,然后調用直接將更新newBlog賦值與dbBlog,這種方式和手動賦值本質一樣,但是至少不用一一賦值不是,如下:

            var dbContext = new EFCoreDbContext();
            var newBlog = new Blog()
            {
                Id = 1,
                Name = "Jeffcky1",
                IsDeleted = false,
                Status = 0,
                Url = "https://www.cnblogs.com/CreateMyself",
                CreatedTime = DateTime.Now,
                ModifiedTime = DateTime.Now,
                Posts = new List<Post>()
                {
                    new Post()
                    {
                        Id = 1,
                        BlogId = 1,
                        Name = "EF Core TrackGraph",
                        CreatedTime = DateTime.Now,
                        ModifiedTime = DateTime.Now
                    }
                }
            };

            var dbBlog = dbContext.Blogs
                .AsNoTracking()
                .Include(d => d.Posts).FirstOrDefault(d => d.Id == 1);
            dbBlog = newBlog;
            dbContext.Update(dbBlog);
            var result = dbContext.SaveChanges();

說了這么多在EF Core中對於指定更新列不是太友好,當屬性過多利用手動賦值就太麻煩,應該保留EF 6.x中利用CurrntValues對導航屬性也進行直接更新豈不更好,如果調用Update方法將當前實體與快照中的實體比較指定更新列應該才是最佳方案。

Include....ThenInclude加載導航屬性是否為最佳方案呢? 

我們看如下三個示例實體

    public class A
    {
        public int Id { get; set; }
        public ICollection<B> Bs { get; set; }
    }

    public class B
    {
        public int Id { get; set; }
        public C C { get; set; }
    }

    public class C
    {
        public int Id { get; set; }
    }

此時我們來查詢A並通過顯式加載B和C,如下:

            var dbContext = new EFCoreDbContext();
            var As = dbContext.As.Include(d => d.Bs).ThenInclude(d => d.C).ToList();

大部分查詢我們都會進行如上查詢,但是我們是否思考是上述是否為最佳方案呢?或者性能更好呢?我也不知道,我也只是純屬猜測,因為要是我們進行如下加載數據呢?

        static void IncludeLoadCollection(EFCoreDbContext dbContext, object obj)
        {
            var entityEntry = dbContext.Entry(obj);
            foreach (var collection in entityEntry.Collections)
            {
                if (collection.IsLoaded)
                {
                    continue;
                }

                collection.Load();

                if (collection.CurrentValue != null)
                {
                    foreach (var child in collection.CurrentValue)
                    {
                        IncludeLoadCollection(dbContext, child);
                    }
                }
            }
        }
           var dbContext = new EFCoreDbContext();

            var a = dbContext.As.FirstOrDefault();
            IncludeLoadCollection(dbContext, a);

如上代碼未經測試,只是作為個人思考而給,您看到后私下可自行測試對比上述方案和通過Include....ThenInclude哪種方案更好呢?本文稍微講解了下個人認為EF Core對於指定更新沒有一個恰當的方式除了手動更新列外,當然字段太多,大部分情況下都會借助AutoMapper等進行DTO。

出版購買通知

現京東和淘寶上可正式預售購買《你必須掌握的EntityFramework 6.x與Core 2.0》書籍,我博客右上方也給了一個購買鏈接,讓各位久等了。感謝各位同行一直以來的大力支持,同時也再次感謝博客園這個大平台,給了我機會去分享技術,我對EF既談不上精通更談不上不專家只不過是平時私下喜歡研究罷了,書中大部分都是我個人的理解,同時技術更新迭代太快,我也一直在追逐中而非停滯不前,我相信:無論出身環境怎樣,自身天賦如何,篤定都可以通過自身的努力來改變並且成長


免責聲明!

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



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