因此,也许我用错误的方式解决了这个问题,但是我想从您那里获得关于StackOverflow的好人关于如何更正确地执行此操作的意见。

我有一个程序,该程序必须从Entity Framework 6.0代码优先上下文周围的存储库中检索信息,对包含的信息做一些工作,然后将新记录添加到数据库中。

无论如何,这是我通过存储库从EF检索的类的简化视图:

public class Product
{
  public int Id { get;set; }
  public virtual ProductCategory Category { get;set; }
  public string Name { get;set; }
}


然后,我用以下定义构建一个ProcessedProduct,并将先前检索到的Product传递为BaseProduct:

public class ProcessedProduct
{
  public int Id { get;set; }
  public virtual Product BaseProduct { get;set; }
}


我使用的是在Pluralsight的EF课程中看到的存储库层,并已将其用于此。我在下面添加了所有相关的位:

public class MyContext : BaseContext<MyContext>, IMyContext
{
  //Lots of IDbSets for each context
  public void SetModified(object entity)
  {
    Entry(entity).State = EntityState.Modified;
  }

  public void SetAdd(object entity)
  {
    Entry(entity).State = EntityState.Added;
  }
}

public class MyRepository : IMyRepository
{
  private readonly IMyContext _context;

  public MyRepository(IUnitOfWork uow)
  {
    _context = uow.Context as IMyContext;
  }

  public ProcessedProduct FindProcessedProduct(int id)
  {
    return _context.ProcessedProducts.Find(id);
  }

  public ProductCategory FindCategory(int id)
  {
    return _context.Categories.Find(id);
  }

  public int AddProcessedProductWithoutProduct(ProcessedProduct newRecord)
  {
    newRecord.Product = null;
    Save();
    return newRecord.Id;
  }

  public int UpdateProcessedProductWithProductButWithoutChildProperties(int processedProductId, int productId)
  {
    var processedProduct = FindProcessedProduct(processedProductId);
    processedProduct.BaseProduct = FindProduct(productId);
    processedProduct.BaseProduct.Category = null;
    _context.SetModified(product);
    Save();

    return processedProduct.Id;
  }

  public int UpdateProductChildren(int processedProductId, int categoryId)
  {
    var processedProduct = FindProcessedProduct(processedProductId);
    var category = FindCategory(categoryId);
    processedProduct.BaseProduct.Category = category;
    _context.SetModified(product);
    Save();

    return processedProduct.Id;
  }
}


最后,这是将所有内容组合在一起的部分:

try
{
  //Create the processed product without the product instance
  var processedProductId = repo.AddProcessedProductWithoutProduct(finishedProduct);

  //Now, update this processed product record with the product. This way, we don't create a
  //duplicate product.
  processedProductId = repo.UpdateProcessedProductWithProductButWithoutChildProperties(processedProductId, product.Id);

  //Finally, update the category
  processedProductId = repo.UpdateProductChildren(processedProductId, product.Category.Id);

  //Done!
}


当我尝试将此ProcessedProduct插入EF时,它会正确创建ProcessedProduct记录,但也会创建一个新的Product和新的Category行。我尝试手动更改每个对象的更改跟踪,因此将“添加” ProcessedProduct,将其他对象“修改”或“不变”,但是我会得到实体框架抛出的外键引用异常。

我的“解决方案”是将其分解为许多不同的调用:


我创建了新的ProcessedProduct记录,但是将Product值分配为null。
我使用ID查询该ProcessedProduct记录,使用其ID查询适当的Product,并将该Product分配给新检索的ProcessedProduct记录。但是,我必须使Category属性无效,否则它将添加新的重复Category记录。我保存并修改了ProcessedProduct记录。
最后,我再次查询ProcessedProduct以及ProductCategory,然后将该ProductCategory分配给ProcessedProduct.BaseProduct的Category属性。我可以再次保存,现在我创建了我需要的所有记录,而没有进行任何重复。


但是,这种方法似乎很复杂,因为我本来想要做的就是保存新的父记录,而根本不创建重复的子记录。有没有更好的方法去做我想念的事情?谢谢!

编辑:我想更大的问题是说我有一个复杂的对象,其中包含大量这些子复杂对象。无需遍历整个子对象图一次更新其父对象的最简单方法就是创建新父对象?

最佳答案

强烈建议在编辑时不要将“产品和类别”设置为导航属性。正如您看到的那样,当您将已加工产品的图形(附加了产品和类别)添加到EF上下文时,它会将图形中的所有内容标记为已添加,并在所有内容上进行插入。
我一直推荐的模式(尼古拉也在他的评论中提出了建议,所以像我一样投票赞成他的评论:))是在您的实体中包括FK ID并设置这些值,而不是导航。例如
newRecord.ProductId =产品ID值。

我有很多人在哭:“但是外键?ewwww!它们会使我的课变得如此肮脏和不纯洁!”但是在他们看到在这种情况下不纠结导航而编写代码变得容易得多之后,他们又回来说“好吧,这是值得的!”

顺便说一句,如果您在企业课程中谈论我的EF,我有一个完整的模块来处理这个问题...在断开连接的情况下,它称为回合图。 :)

07-27 20:22