防止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

标签:  NET
本文转自 donjuan 博客园博客,原文链接: http://www.cnblogs.com/killmyday/archive/2010/12/17/1909630.html   ,如需转载请自行联系原作者

相关文章
|
7月前
mongoTemplate 嵌套对象包含id
mongoTemplate 嵌套对象包含id
65 0
|
7月前
|
SQL 存储 Java
MyBatis【付诸实践 02】 mapper文件未编译+statementType使用+返回结果字段顺序不一致+获取自增ID+一个update标签批量更新记录
MyBatis【付诸实践 02】 mapper文件未编译+statementType使用+返回结果字段顺序不一致+获取自增ID+一个update标签批量更新记录
83 0
|
数据库 C#
Entity Framework 简单属性映射
Entity Framework 简单属性映射
1099 0
Entity Framework 简单属性映射
|
索引
Entity Framework 索引
Entity Framework 索引
236 0
|
SQL Java 数据库连接
Hibernate中执行NativeSQL语句查询返回自定义类型的POJO实例的List(多表查询)
Hibernate中定义了hql的概念,简单地说就是,为java的码农提供了一套类似于sql的语法,但是数据表名变成了PO名,数据字段名变成了PO中属性成员名,并把这种语法称为hql。优点就是:hql看上去是面向对象的,码农不需要知道数据库中数据表的结构,只需要依据PO编写面向对象的数据库增删改查的语句。
4176 0