我是使用Entity Framework 6的新手,我在尝试更新实体时遇到困难。我使用Code First方法,我的模型缩减为相关类,看起来像这样:
public class Document
{
public long Key { get; set; }
public string Name { get; set; }
}
public class Batch
{
public long Key { get; set; }
public virtual List<Document> Documents { get; private set; }
public void Add(Document document)
{
Documents.Add(document);
}
public void Remove(Document document)
{
Documents.RemoveAt(Documents.FindIndex(d => d.Key == document.Key));
}
}
Batch和Document之间存在一对多关系,通过独立关联建模,因此没有明确的FK。 一个重要的特性是Document类不知道Batch。这与Stackoverflow上的其他类似问题有所不同,例如this one。实体框架在Document表中生成一个可以为空的列Batch_Key。
这是上下文类:
public class MyDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Document>().HasKey(d => d.Key);
modelBuilder.Entity<Batch>().HasKey(b => b.Key);
modelBuilder.Entity<Batch>().HasMany(b => b.Documents).WithOptional().WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
}
我有一个带有Update()方法的BatchRepository类,它接收Batch参数并更新数据库中的批处理:
public class BatchRepository
{
public void Update(Batch item)
{
using (var dbCtx = new MyDbContext(DBContextName))
{
batchesContext.Batches.Attach(item);
batchesContext.Entry(item).State = EntityState.Modified;
batchesContext.SaveChanges();
}
}
}
这是一个使用Update()方法从批处理中删除文档的单元测试:
[TestMethod]
public void CheckRemoveDocument()
{
var batches = BatchRepository.FindAll().ToList();
var batch = batches[0];
var batchKey = batches[0].Key;
var doc = batch.Documents[0];
int batchNumberOfDocuments = batch.Documents.Count;
batch.Remove(doc);
BatchRepository.Update(batch);
batch = BatchRepository.FindBy(batchKey);
Assert.AreEqual(batchNumberOfDocuments - 1, batch.Documents.Count);
}
测试失败,batchNumberOfDocuments与之前相同。如果我实现了一个方法来删除批处理中的所有文档,如下所示:
public class BatchRepository
{
public void RemoveDocuments(Batch item)
{
using (var dbCtx = new MyDbContext(DBContextName))
{
var existingBatch = batchesContext.Batches.Find(item.Key);
batchesContext.Entry(existingBatch).Collection(b => b.Documents).Load();
existingBatch.Documents.RemoveAll(d => true);
batchesContext.SaveChanges();
}
}
}
以下测试成功:
[TestMethod]
public void CheckRemoveAllDocuments()
{
var batches = BatchRepository.FindAll().ToList();
var batch = batches[0];
var batchKey = batches[0].Key;
BatchRepository.RemoveDocuments(batch);
batch = BatchRepository.FindBy(batchKey);
Assert.AreEqual(0, batch.Documents.Count);
}
因此,EF正确跟踪批处理与文档之间的关系,将Batch_Key列设置为NULL以删除文档。为什么这在这种情况下有效,而在我正在更新的情况下呢?我认为这是因为链接到批处理的文档实体未附加到上下文。问题是我不知道Update()方法的参数中的批次是否有更多的文档,更少的文档或完全不同的文档列表(也可能是除了文件清单已更改)。
这样的实现:
public class BatchRepository
{
public void Update(Batch item)
{
using (var dbCtx = new MyDbContext(DBContextName))
{
var existingBatch = batchesContext.Batches.Find(item.Key);
batchesContext.Entry(existingBatch).Collection(b => b.Documents).Load();
foreach (var doc in item.Documents)
{
batchesContext.Documents.Attach(doc);
batchesContext.Entry(doc).State = EntityState.Modified;
}
batchesContext.SaveChanges();
}
}
}
抛出异常:
System.InvalidOperationException:附加类型的实体 &#39;文件&#39;失败,因为同一类型的另一个实体已经有了 相同的主键值。使用&#39;附加&#39; 方法或将实体的状态设置为“未更改”#39;或者&#39;修改&#39; 如果图中的任何实体具有冲突的键值。这可能是 因为有些实体是新的,还没有收到 数据库生成的键值。在这种情况下,请使用&#39;添加&#39;方法或 &#39;已添加&#39;实体状态跟踪图形然后设置状态 非新实体“未变”&#39;或者&#39;修改&#39;酌情。
如何实施Update()方法以正确执行更新?
谢谢你,如果这个问题听起来令人困惑,那么这一切都归功于我对EF的疑惑。
答案 0 :(得分:2)
很明显,您知道当您将实体与上下文断开连接时,它的状态不会被跟踪。因此,正如您所做的那样,您需要将实体附加回上下文并设置正确的状态。
问题在于,当被删除的实体是断开连接的树的根时,您必须处理相关的实体。有可能的方法:
Attach()
:在这种情况下,您需要单独附加每个实体,并设置每个实体的状态。 Add()
:在这种情况下,整个树都会立即附加,并且所有实体都会获得Added
状态。所以你需要设置每个实体的状态,除了新实体(因为它们已经具有Added
状态)。请注意,此行为是可取的。例如,如果你有多对多的关系,那么新的孩子可以是全新的(添加的)或者是预先存在的(不变的)。或者,您可以拥有与已删除子项(已删除)和新实体(已添加)的一对多关系的父实体。因此,您始终必须跟踪每个实体的状态。
答案 1 :(得分:2)
按照@ JotaBe的建议,我自己跟踪所有文档实体。 这是代码:
public class BatchRepository
{
protected override void Update(Batch item)
{
using (var dbCtx = new MyDbContext(DBContextName))
{
var existingBatch = dbCtx.Batches.Find(item.Key);
if (null != existingBatch)
{
// Load the documents for the existing batch
dbCtx.Entry(existingBatch).Collection(b => b.Documents).Load();
// Get the list of the documents that were removed from the existing batch
var removedDocuments = existingBatch.Documents.Except(item.Documents).ToList();
foreach (var doc in removedDocuments)
{
// Remove the relationship between the documents and the batch
existingBatch.Documents.Remove(doc);
}
// Get the list of the newly added documents
var addedDocuments = item.Documents.Except(existingBatch.Documents).ToList();
foreach (var doc in addedDocuments)
{
// The document exists in the repository, so we just attach it to the context
dbCtx.Documents.Attach(doc);
// Create the relation between the batch and document
existingBatch.Documents.Add(doc);
}
// Overwrite all property current values from modified batch' entity values,
// so that it will have all modified values and mark entity as modified.
var batchEntry = dbCtx.Entry(existingBatch);
batchEntry.CurrentValues.SetValues(item);
dbCtx.SaveChanges();
}
}
}
}