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既谈不上精通更谈不上不专家只不过是平时私下喜欢研究罢了,书中大部分都是我个人的理解,同时技术更新迭代太快,我也一直在追逐中而非停滞不前,我相信:无论出身环境怎样,自身天赋如何,笃定都可以通过自身的努力来改变并且成长

posted @ 2018-06-04 23:17  Jeffcky  阅读(4281)  评论(6编辑  收藏  举报