我正在使用Entity Framework 4.4,我有一个像这样的一对多关系模型:
class Item {
public string keyPart1 { get; set; }
public string keyPart2 { get; set; }
public virtual Container container { get; set; }
public string ContainerId { get; set; }
}
// Idea is that many Items will be assigned to a container
class Container {
public string ContainerId { get; set; }
private ICollection<Item> _Items;
public virtual ICollection<Item> As
{
get { return _Items ?? (_Items = new HashSet<A>()); }
protected set { _Items = value; }
}
}
现在,这是DbContext:
public class StorageContext : DbContext
{
public DbSet<Item> Items { get; set; }
public DbSet<Bucket> Buckets { get; set; }
public override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Item>().HasKey( i => new { i.keyPart1, i.keyPart2 } ).HasRequired( i => i.Container );
}
}
现在,假设我有N个Item实例。每个Item都属于一个容器,它包含多个项目实例,每个实例都属于一个容器,因此模型无休止地进行递归。
我想循环浏览我当前的Item实例列表,并将每个实例添加到db上下文:
foreach (var i in LocalItemList)
{
IDbSetExtensions.AddOrUpdate<Item>(db.Items, i);
}
dbContext.SaveChanges();
我无法弄清楚的问题是如何告诉AddOrUpdate
Container
的上下文,以便我不会获得主键重复异常。在某些时候,我们会遇到与Item
具有相同Container
的{{1}},但我会在SaveChanges()
上获得重复的主键例外。
如果Add
是DbSet的容器,那么Item
是否也被添加到了Set中?我怎样才能改为AddOrUpdate
?
答案 0 :(得分:2)
我不确定您是否要将相关的Container
与Item
一起插入数据库,或者您只想创建与现有Container
的关系秒。数据库和对象图中不存在Container
实体的可能情况,每个密钥只有一个Container
实例(多个引用允许那些实例)根本不应该是一个问题和一个简单的代码,如......
foreach (var i in LocalItemList)
{
dbContext.Items.Add(i);
}
dbContext.SaveChanges();
......实际应该毫无例外地工作。因此,您可能会遇到以下两种情况之一来解释主键约束违规:
数据库和对象图中已存在Container
个实体,每个键只有一个Container
实例(多个引用再次允许这些实例)。这是一个简单的案例,您可以使用以下方法解决它:
foreach (var i in LocalItemList)
{
dbContext.Containers.Attach(i.Container);
dbContext.Items.Add(i);
}
dbContext.SaveChanges();
如果你有相同密钥的多个Container
个实例,这将无效并抛出一个“...... ObjectContext中已经存在一个具有相同密钥的对象......”(或类似)例外。如果数据库中尚不存在Container
,它也将无法工作(那么你可能会遇到外键约束违规)。
如果对象图中有多个具有相同键的Container
个对象实例,则必须在每个键上用一个唯一实例替换重复项,然后才能将实体附加或添加到上下文中。为了简化这个过程,我首先删除循环引用,然后使用Local
集合来确定是否已经附加了具有相同键的Container
:
foreach (var i in LocalItemList)
{
i.Container.Items = null;
var attachedContainer = dbContext.Containers.Local
.SingleOrDefault(c => c.ContainerId == i.Container.ContainerId);
if (attachedContainer != null)
i.Container = attachedContainer;
else
dbContext.Containers.Attach(i.Container);
// or dbContext.Containers.Add(i.Container);
// depending on if you want to insert the Container or not
dbContext.Items.Add(i);
}
dbContext.SaveChanges();
答案 1 :(得分:1)
只要你让EntityFramework知道任何pre-exisitng容器,它似乎都可以正常工作。
例如,此测试在空数据库上运行。两个项都在同一个Container中,但EF只插入一次Container。
[TestMethod]
public void Populate()
{
const string conStr = "Data Source=(local);Initial Catalog=EfExperiment1; Integrated Security=True";
var context = new MyDbContext(conStr);
var container = new Container { ContainerId = "12345" };
var item1 = new Item { keyPart1 = "i1k1", keyPart2 = "i1k2", Container = container };
var item2 = new Item { keyPart1 = "i2k1", keyPart2 = "i2k2", Container = container };
context.Items.Add(item1);
context.Items.Add(item2);
context.SaveChanges();
}
当容器已存在时运行此测试。通过尝试从db读取容器,我们使EF知道任何现有实例。
[TestMethod]
public void PopulateSomeMore()
{
const string conStr = "Data Source=(local);Initial Catalog=EfExperiment1; Integrated Security=True";
var context = new MyDbContext(conStr);
var container = context.Buckets.FirstOrDefault(c => c.ContainerId == "12345") ??
new Container { ContainerId = "12345" };
var item3 = new Item { keyPart1 = "i3k1", keyPart2 = "i3k2", Container = container };
var item4 = new Item { keyPart1 = "i4k1", keyPart2 = "i4k2", Container = container };
context.Items.Add(item3);
context.Items.Add(item4);
context.SaveChanges();
}
此测试不会加载现有容器,因此EF认为它需要插入并因重复键错误而失败。
[TestMethod]
public void PopulateSomeMoreAgain()
{
const string conStr = "Data Source=(local);Initial Catalog=EfExperiment1; Integrated Security=True";
var context = new MyDbContext(conStr);
var container = new Container { ContainerId = "12345" };
var item5 = new Item { keyPart1 = "i5k1", keyPart2 = "i5k2", Container = container };
var item6 = new Item { keyPart1 = "i6k1", keyPart2 = "i6k2", Container = container };
context.Items.Add(item5);
context.Items.Add(item6);
context.SaveChanges();
}