Entity framework 使用实体框架分离的POCO对象有困难

Entity framework 使用实体框架分离的POCO对象有困难,entity-framework,Entity Framework,我希望以分离的方式使用EF DbContext/POCO实体,即从我的业务层检索实体的层次结构,进行一些更改,然后将整个层次结构发送回业务层以持久化回数据库。每个BLL调用使用不同的DbContext实例。为了测试这一点,我编写了一些代码来模拟这样的环境 首先,我检索客户以及相关的订单和订单行:- Customer customer; using (var context = new TestContext()) { customer = context.Customers.Includ

我希望以分离的方式使用EF DbContext/POCO实体,即从我的业务层检索实体的层次结构,进行一些更改,然后将整个层次结构发送回业务层以持久化回数据库。每个BLL调用使用不同的DbContext实例。为了测试这一点,我编写了一些代码来模拟这样的环境

首先,我检索
客户
以及相关的
订单
订单行
:-

Customer customer;
using (var context = new TestContext())
{
    customer = context.Customers.Include("Orders.OrderLines").SingleOrDefault(o => o.Id == 1);
}
var newOrder = new Order { OrderDate = DateTime.Now, OrderDescription = "Test" };
newOrder.OrderLines.Add(new OrderLine { ProductName = "foo", Order = newOrder, OrderId = newOrder.Id });
newOrder.OrderLines.Add(new OrderLine { ProductName = "bar", Order = newOrder, OrderId = newOrder.Id });
customer.Orders.Add(newOrder);
newOrder.Customer = customer;
newOrder.CustomerId = customer.Id;
接下来,我添加一个新的
订单
,其中包含两条
订单行
:-

Customer customer;
using (var context = new TestContext())
{
    customer = context.Customers.Include("Orders.OrderLines").SingleOrDefault(o => o.Id == 1);
}
var newOrder = new Order { OrderDate = DateTime.Now, OrderDescription = "Test" };
newOrder.OrderLines.Add(new OrderLine { ProductName = "foo", Order = newOrder, OrderId = newOrder.Id });
newOrder.OrderLines.Add(new OrderLine { ProductName = "bar", Order = newOrder, OrderId = newOrder.Id });
customer.Orders.Add(newOrder);
newOrder.Customer = customer;
newOrder.CustomerId = customer.Id;
最后,我将保留更改(使用新上下文):-

我意识到最后一部分是不完整的,因为毫无疑问,在调用SaveChanges()之前,我需要更改新实体的状态。我是否添加或附加客户?我必须更改哪些实体状态

在进入此阶段之前,运行上述代码会引发异常:

ObjectStateManager中已存在具有相同密钥的对象

这似乎源于没有显式设置两个
OrderLine
实体的ID,因此这两个实体都默认为0。我认为这样做很好,因为EF会自动处理事情。我做错什么了吗

此外,以这种“分离”的方式工作,似乎需要大量的工作来建立关系-我必须将新订单实体添加到
customer.Orders
集合中,设置新订单的
customer
属性及其
CustomerId
属性。这是正确的方法还是有更简单的方法


我看一下自跟踪实体会更好吗?我在某个地方读到过,他们不赞成使用POCO,或者至少不赞成使用POCO。

您基本上有两种选择:

A) 乐观的

您可以按照现在的方式进行操作,只需将所有内容附加为修改和希望。您要查找的代码而不是.Attach()是:

绝对不是凭直觉。这个外观怪异的调用附加了已分离(或由您新构造)的对象(如修改的)。资料来源:

如果不确定对象是否已添加或修改,可以使用最后一段的示例:

context.Entry(customer).State = customer.Id == 0 ?
                               EntityState.Added :
                               EntityState.Modified;
您需要对所有要添加/修改的对象执行这些操作,因此,如果此对象很复杂,并且有其他需要通过FK关系在DB中更新的对象,则还需要设置它们的EntityState

根据您的场景,您可以通过使用不同的上下文变体来降低这些类型的不关心写入的成本:

public class MyDb : DbContext
{
    . . .
    public static MyDb CheapWrites()
    {
        var db = new MyDb();
        db.Configuration.AutoDetectChangesEnabled = false;
        db.Configuration.ValidateOnSaveEnabled = false;
        return db;
    }
}

using(var db = MyDb.CheapWrites())
{
    db.Entry(customer).State = customer.Id == 0 ?
        EntityState.Added :
        EntityState.Modified;
    db.SaveChanges();
}
您基本上只是禁用了EF代表您进行的一些额外调用,而忽略了这些调用的结果

B) 悲观的。实际上,您可以查询数据库,以验证自上次拾取数据以来数据没有更改/添加,然后在安全的情况下进行更新

var existing = db.Customers.Find(customer.Id);
// Some logic here to decide whether updating is a good idea, like
// verifying selected values haven't changed, then

db.Entry(existing).CurrentValues.SetValues(customer);

这是我需要研究的问题,但我仍然不理解我遇到的例外情况。我不认为我必须将主键ID分配给新的实体——我认为EF会为我解决这个问题。或者这只是在附加的场景中?好吧,如果你看一下我在那里链接的文档,.Attach()方法是针对现有的,而不是修改过的实体的。如果您的订单行是新对象,则它们都不符合这些条件。NotModified对象在进入数据库的过程中使用其密钥(通常为.Id);0导致异常。因此,解决方案是如上所述附加它们,这会导致它在不包含密钥的情况下插入它们,当然,然后db会为您设置密钥(同样是,.Id),因为它是一个标识列。基本上,Attach()有点混乱,应该避免使用。