在Entity Framework中添加具有多对多关系的项目
在Entity Framework中添加具有多对多关系的项目
当我尝试添加具有多对多关系的项时,出现了主键违规错误:
我有两个类 - Articles和Tags,它们之间有一个多对多的关系:
当我添加一个新的Article时,我允许用户输入任何Tags,然后如果数据库中尚未创建该Tag,我希望创建一个新的Tag,否则将Tag添加到Article对象的Tags集合中。
因此,当我创建新的Article对象时,我调用以下函数:
此函数基本上检查数据库中是否有可用的Tag,如果有,则返回该Tag,然后使用article.Tags.Add()将其添加到Article对象的Tag集合中。
然而,当我尝试使用以下代码保存时,我会得到一个主键约束违规错误:
我无法弄清楚如何只创建Article和已存在的Tag之间的关系。
在Entity Framework中,当我们尝试在多对多关系中添加项目时,可能会遇到问题。具体问题是如何创建新的标签,并将现有或创建的实体附加到文章上。有人建议使用以下代码:
Article a = new Article(...); a.tags.add(GetOrLoadTag("some tag"));
然后有人提供了一篇文章链接:http://thedatafarm.com/blog/data-access/inserting-many-to-many-relationships-in-ef-with-or-without-a-join-entity/
但是在尝试保存时,会抛出主键冲突错误。有人指出,当标签存在时,需要让框架知道它是一个已存在的实体。框架似乎尝试再次插入该实体。并且请确保已设置了主键的标识列。
于是有人问如何让框架知道这是一个已存在的实体。有人建议将主键字段设置为INT IDENTITY,然后观察是否有效。并给出了一个链接:stackoverflow.com/questions/1173001/…
但是问题仍然存在,没有任何改变。看不出为什么这样会起作用,问题不是创建一个唯一的主键,而是在新对象和现有对象之间创建关系的问题。
有人建议提供完整的代码和数据库模式。如果是关系问题,应该会抛出外键约束错误。但是似乎你的上下文认为你正在将一个新实体放入其中,而实际上它已经存在于数据库中。因此,请确保在代码中从数据库检索标签时,使用的是与调用SaveChanges时使用的相同的数据上下文。
问题的原因是在Entity Framework中使用不同的上下文实例,这会导致操作失败。解决方法是使用相同的上下文实例来处理整个操作过程。
第一种解决方法是在整个操作过程中使用相同的上下文实例,示例代码如下:
using (var ctx = new MyContext()) { Article article = ctx.Articles.Single(a => a.Id == articleId); Tag tag = ctx.Tags.SingleOrDefault(t => t.UrlSlug == tagUrl); if (tag == null) { tag = new Tag() { ... } ctx.Tags.AddObject(tag); } article.Tags.Add(tag); ctx.SaveChanges(); }
第二种解决方法是不从数据库加载文章(如果已知文章存在,则该查询是多余的),示例代码如下:
using (var ctx = new MyContext()) { Article article = new Article() { Id = articleId }; ctx.Articles.Attach(article); Tag tag = ctx.Tags.SingleOrDefalut(t => t.UrlSlug == tagUrl); if (tag == null) { tag = new Tag() { ... } ctx.Tags.AddObject(tag); } article.Tags.Add(tag); ctx.SaveChanges(); }
这两种方法都可以解决使用不同上下文实例导致的问题。在使用Entity Framework时,确保在检索标签时使用的是与调用SaveChanges方法时相同的上下文。
另外,如果使用了Repository模式来封装上下文,请确保可以通过注入/工厂正确共享上下文。如果在方法/作用域之间共享上下文,可能会遇到失败提交破坏后续无关提交的问题。可以参考rundevrun.blogspot.com/2012/06/…中的"undo"方法解决这个问题。
另外,如果你不仅仅有一个Tag对象,而是一个Tag对象的序列(例如从UI的复选框列表中获取),那么你只需要将添加Tag的操作放在循环内即可。