我对 EF 相当陌生,似乎对以下问题有点卡住了..
我有以下情况。
两个应用程序使用相同的数据库,并且同时加载了显示相同信息的上下文。
然后应用程序 1 删除一个实体并使用 savechanges 更新数据库。然后应用程序 2 继续保存相同的修改实体。
我们使用更新图递归遍历树并将所有实体保存到数据库上下文中。但是,当我们要保存已删除的实体时,从数据库获取的 db 上下文中不存在该实体,但我们希望将其添加回数据库。
我尝试过添加,但这给了我们一个多重性错误,因为它试图将完整的树添加回上下文!
请问有什么想法吗?
以下是 updategraph 扩展方法的代码片段。( http://github.com/refactorthis/GraphDiff )
private static void RecursiveGraphUpdate<T>(DbContext context, T dataStoreEntity, T updatingEntity, UpdateMember member) where T:class
{
if (member.IsCollection)
{
// We are dealing with a collection
var updateValues = (IEnumerable)member.Accessor.GetValue(updatingEntity, null);
var dbCollection = (IEnumerable)member.Accessor.GetValue(dataStoreEntity, null);
var keyFields = context.GetKeysFor(updateValues.GetType().GetGenericArguments()[0]);
var dbHash = MapCollectionToDictionary(keyFields, dbCollection);
// Iterate through the elements from the updated graph and try to match them against the db graph.
List<object> additions = new List<object>();
foreach (object updateItem in updateValues)
{
var key = CreateHash(keyFields, updateItem);
// try to find in db collection
object dbItem;
var itemID = updateItem.GetType().GetProperty("ID");
if (dbHash.TryGetValue(key, out dbItem))
{
var property = updateItem.GetType().GetProperty("Deleted");
bool ysn = property.GetValue(updateItem, null).ToString() == "True" ? true : false;
if (ysn)
{
context.Entry(dbItem).CurrentValues.SetValues(updateItem);
continue;
}
// If we own the collection
if (member.IsOwned)
{
context.Entry(dbItem).CurrentValues.SetValues(updateItem); // match means we are updating
foreach (var childMember in member.Members)
RecursiveGraphUpdate(context, dbHash[key], updateItem, childMember);
}
dbHash.Remove(key); // remove to leave only db removals in the collection
}
else
additions.Add(updateItem);
}
// Removal of dbItem's left in the collection
foreach (var dbItem in dbHash.Values)
{
var property = dbItem.GetType().GetProperty("Deleted");
bool ysn = property.GetValue(dbItem, null).ToString() == "True" ? true : false;
if (ysn)
{
// Own the collection so remove it completely.
if (member.IsOwned)
context.Set(dbItem.GetType()).Remove(dbItem);
dbCollection.GetType().GetMethod("Remove").Invoke(dbCollection, new[] { dbItem });
}
}
// Add elements marked for addition
foreach (object newItem in additions)
{
if (!member.IsOwned)
context.Set(newItem.GetType()).Attach(newItem);
// Otherwise we will add to object
dbCollection.GetType().GetMethod("Add").Invoke(dbCollection, new[] { newItem });
}
}
else // not collection
{
var dbvalue = member.Accessor.GetValue(dataStoreEntity, null);
var newvalue = member.Accessor.GetValue(updatingEntity, null);
if (dbvalue == null && newvalue == null) // No value
return;
// If we own the collection then we need to update the entities otherwise simple relationship update
if (!member.IsOwned)
{
if (dbvalue != null && newvalue != null)
{
var keyFields = context.GetKeysFor(newvalue.GetType());
var newKey = CreateHash(keyFields, newvalue);
var updateKey = CreateHash(keyFields, dbvalue);
if (newKey == updateKey)
return; // do nothing if the same key
}
if (context.Entry(newvalue).State == EntityState.Detached)
context.Set(newvalue.GetType()).Attach(newvalue);
member.Accessor.SetValue(dataStoreEntity, newvalue, null);
context.Entry(newvalue).Reload();
// TODO would like to do this: context.Entry(newvalue).State = EntityState.Unchanged;
// However it seems even though we are in an unchanged state EF will still update the database if the original values are different.
}
else
{
if (dbvalue != null && newvalue != null)
{
// Check if the same key, if so then update values on the entity
var keyFields = context.GetKeysFor(newvalue.GetType());
var newKey = CreateHash(keyFields, newvalue);
var updateKey = CreateHash(keyFields, dbvalue);
// perform update if the same
if (updateKey == newKey)
{
context.Entry(dbvalue).CurrentValues.SetValues(newvalue);
context.Entry(dbvalue).State = EntityState.Modified;
}
else
member.Accessor.SetValue(dataStoreEntity, newvalue, null);
}
else
member.Accessor.SetValue(dataStoreEntity, newvalue, null);
// TODO
foreach (var childMember in member.Members)
RecursiveGraphUpdate(context, dbvalue, newvalue, childMember);
}
}
}