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

  • 相关阅读:
    Python标准库之csv(1)
    python3:csv的读写
    Python os模块方法
    Python闭包
    Python修饰器
    Python生成器
    Python迭代器
    Python文件对象方法
    Justep X5 Studio,业界公认第一的快速开发平台
    马云:早九晚五的工作方式在2013-2015年就是慢性自杀
  • 原文地址:https://www.cnblogs.com/CreateMyself/p/9129971.html
Copyright © 2011-2022 走看看