zoukankan      html  css  js  c++  java
  • 防止Entity Framework重复插入关联对象

    Entity Framework在数据库与对象映射上做了很多工作,除了将数据库里的表映射成相应的对象以外,它还能够自动处理表之间的外键关系,并且可以用导航属性(Navigation Property)的方式在对象层面上表示这些关系。

    一般来说,当你插入一个对象时,Entity Framework默认会自动将对象通过导航属性关联的对象也插入到数据库里面去,大部分情况下,这是我们想要的结果。当然,如果关联的对象已经存在于数据库当中时,Entity Framework会避免重复插入对象。但问题是,这个检查对象已经存在避免重复插入数据的功能,好像只在一个Context(环境)下有效,即下面的代码是可以正常执行的:

                using (var context = new TestContext())

                {

                    var milestone = new Milestone()

                    {

                        Title = "测试里程碑",

                        StartDate = DateTime.Now,

                        DueDate = DateTime.Now + TimeSpan.FromDays(30)

                    };

     

                    context.Milestones.Add(milestone);

                    context.SaveChanges();

     

                    id = milestone.Id;

     

                    var project = new Project()

                    {

                        Title = "测试项目",

                        StartDate = DateTime.Now,

                        DueDate = DateTime.Now + TimeSpan.FromDays(30),

                        Owner = "测试用户"

                    };

     

                    project.Children.Add(milestone);

                    context.Project.Add(project);

                    context.SaveChanges();

                }

    而如果对象是跨Context(环境)的话,或者基于现有对象复制的对象(包括主键也复制的情况),这就会产生重复插入的问题,因为新复制的对象,Entity Framework没有办法跟踪对象的状态,“误以为”对象是一个全新的对象,比如,下面这段代码就会导致Entity Framework抛出一个异常,异常根据Entity对象的数据库约束不同,可能会报告不同的错误信息—这个问题一开始让我迷惑了好几天:

            public static void Main(string[] args)

            {

                int id = 0;

                using (var context = new TestContext())

                {

                    var milestone = new Milestone()

                    {

                        Title = "测试里程碑",

                        StartDate = DateTime.Now,

                        DueDate = DateTime.Now + TimeSpan.FromDays(30)

                    };

     

                    context.Milestones.Add(milestone);

                    context.SaveChanges();

     

                    id = milestone.Id;

                }

     

                using ( var context = new TestContext())

                {

                    var project = new Project()

                    {

                        Title = "测试项目",

                        StartDate = DateTime.Now,

                        DueDate = DateTime.Now + TimeSpan.FromDays(30),

                        Owner = "测试用户"

                    };

                    var child = new Milestone() {

                        Id = id

                    };

     

                    project.Children.Add(child);

                                   

                    context.Project.Add(project);

                    context.SaveChanges();

                }

            }

    执行上面这段代码,Entity Framework会在最后一个context.SaveChanges()上面抛出DbUpdateException,详细信息是:“{"The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value.\r\nThe statement has been terminated."}”。这个异常一开始看上去太怪异了, 明明我将要保存的Project对象的所有DateTime类型都已经赋值(而且赋值都在范围内)了,为什么还说超出赋值范围呢?

    后面才发现,这是因为,Entity Framework在插入project对象是,看到它的关联对象列表Children里,有一个Milestone对象,而Milestone对象是重新复制的(只复制了ID)—这个场景是因为用户在网页上创建一个项目时,可以从里程碑列表里选择一个事先创建好了的里程碑。由于Entity Framework没有办法跟踪这个新复制的Milestone对象的状态,所以它“误认为”这个对象是一个新的对象,因此重新插入这个对象,而这个对象又没有设置一些必要的日期属性,导致了前面那个异常。

    既然搞明白了道理,修复起来也很简单,就是显式告诉Entity Framework跟踪这个对象—通过把第二个using段改成下面这样:

                using ( var context = new TestContext())

                {

                    var project = new Project()

                    {

                        Title = "测试项目",

                        StartDate = DateTime.Now,

                        DueDate = DateTime.Now + TimeSpan.FromDays(30),

                        Owner = "测试用户"

                    };

                    var child = new Milestone() {

                        Id = id

                    };

     

                    project.Children.Add(child);

                    var adapter = context as IObjectContextAdapter;

                    adapter.ObjectContext.AttachTo("Milestones", child);

                                   

                    context.Project.Add(project);

                    context.SaveChanges();

                }

     

    注意:我用的是Entity Framework CTP 5,采用的是代码优先(code first)的方式创建的数据库,但是本文提到的问题在数据库优先和模型优先的情况里都是一样的。

    因为在网上找了好多文章都没有提到这个问题,所以在这里记录下来。

    重现代码:/Files/killmyday/codefirstef.zip

  • 相关阅读:
    4. RDMA操作类型
    3. RDMA基本元素
    2. 比较基于Socket与RDMA的通信
    1. RDMA概述
    win10 如何开启hyper-v虚拟机
    将Oracle 当前日期加一天、一小时、一分钟
    github搜索案例(axios、pubsub、fetch)
    Tolist案例(父子传参实现增删改)
    5.key的使用
    4.React生命周期
  • 原文地址:https://www.cnblogs.com/killmyday/p/1909630.html
Copyright © 2011-2022 走看看