前段时间,我想实现一个能够确定是否对给定实体执行插入或更新的方法,因此我不必公开“插入”和“更新”方法,而只是简单的“InsertOrUpdate”。
找出实体是否是新实体的代码部分是:
public virtual T GetEntityByPrimaryKey<T>(T entity) where T : class
{
var entityType = entity.GetType();
var objectSet = ((IObjectContextAdapter)this.DatabaseContext).ObjectContext.CreateObjectSet<T>();
var keyNames = objectSet.EntitySet.ElementType.KeyMembers.Select(edmMember => edmMember.Name);
var keyValues = keyNames.Select(name => entityType.GetProperty(name).GetValue(entity, null)).ToArray();
return this.DatabaseContext.Set<T>().Find(keyValues);
}
InsertOrUpdate方法是这样的:
public virtual T InsertOrUpdate<T>(T entity) where T : class
{
var databaseEntity = this.GetEntityByPrimaryKey(entity);
if (databaseEntity == null)
{
var entry = this.DatabaseContext.Entry(entity);
entry.State = EntityState.Added;
databaseEntity = entry.Entity;
}
else
{
this.DatabaseContext.Entry(databaseEntity).CurrentValues.SetValues(entity);
}
return databaseEntity;
}
现在,只要对象的“主键”由代码确定,这种方法就会产生奇迹。有效示例包括GUID,HI-LO算法,自然键等。
然而,对于“数据库生成的身份”场景,这是非常可怕的,原因很简单:因为我要插入的所有对象的代码中的“Id”为0,所以会认为他们是一样的。如果我要添加10个对象,第一个将导致“new”,但接下来的九个将导致“已经存在”。这是因为EF的“查找”方法从objectcontext中读取数据,并且只有当它不存在时才会进入数据库进行查询。在第一个对象之后,将跟踪具有Id 0的给定类型的实体。连续调用将导致“更新”,这是错误的。
现在,我知道数据库生成的ID是邪恶的,绝对不适合任何ORM,但我坚持使用这些,我需要修复此方法或完全删除它并回退到单独的“插入”和“更新“方法并将任务委托给调用者以确定要执行的操作。由于我们有一个高度解耦的解决方案,我宁愿避免这样做。
如果有人可以提供帮助并找到修复GetEntityByPrimaryKey方法的方法,那就太棒了。
感谢。
答案 0 :(得分:2)
我有以下建议:
<强> 1 强>
我会向实体添加类似IsTransient
属性的内容。如果PK为0,则返回true
,否则返回false
您可以使用此属性更改方法,如下所示:
IsTransient
==是吗? - &GT;插入IsTransient
== false? - &GT;您现有的数据库检查代码将该属性设为虚拟,您甚至可以通过覆盖IsTransient
来支持具有“奇怪”PK的实体。
<强> 2 强>
如果您不喜欢将其添加到实体,您仍然可以创建一个封装此逻辑的扩展方法。或者甚至将该检查直接添加到InsertOrUpdate
。
由于您的实体没有共同的基类,这些建议将变得有点乏味。你基本上每个实体都必须有一个扩展方法。
第3:强>
如果您有适用于PK的约定,则可以使用dynamic
来访问ID属性:
dynamic dynamicEntity = entity;
if(dynamicEntity.Id == 0)
{
// Insert
}
else
{
// Current code.
}
<强> 4 强>
看到向上下文添加瞬态实体会破坏所有后续瞬态项的内容,将临时项添加到列表而不是上下文中可能是个好主意。
仅在提交时将它们添加到上下文中。我确信有一个钩子,你可以使用:
List<object> _newEntities;
private override OnCommit()
{
foreach(var newEntity in newEntities)
DatabaseContext.Entry(newEntity).State = EntityState.Added;
}
public virtual T InsertOrUpdate<T>(T entity) where T : class
{
var databaseEntity = this.GetEntityByPrimaryKey(entity);
if (databaseEntity == null)
_newEntities.Add(entity);
else
this.DatabaseContext.Entry(databaseEntity).CurrentValues.SetValues(entity);
return databaseEntity;
}
答案 1 :(得分:0)
因为代码中的“Id”对于我要去的所有对象都是0 插入
当你没有提供钥匙时,你似乎期待钥匙的独特性。是否可以将它们初始化为唯一的负数? (某些不是实际数据库条目的有效值)
我有一个类似的问题(自我跟踪对象能够判断两个尚未插入的子对象是否与键相同......),这解决了它。
答案 2 :(得分:0)
鉴于您的POCO没有数据库污染,您必须使用Fluent API来声明数据库生成的密钥信息。因此,CONTEXT上的DbSets可能包含此DB Generated标志。
我使用扩展来获取上下文中使用的所有POCO。也许通过足够的反射,您可以找到一个有用的属性或属性作为DB生成的标志。其余部分已经很清楚了。
也许这是一个有用的起点:
public static List<string> GetModelNames(this DbContext context ) {
var model = new List<string>();
var propList = context.GetType().GetProperties();
foreach (var propertyInfo in propList)
{
if (propertyInfo.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
{
model.Add(propertyInfo.Name);
var innerProps = propertyInfo.GetType().GetProperties(); // added to snoop around in debug mode , can your find anything useful?
}
}
return model;
}
public static List<string> GetModelTypes(this DbContext context)
{
var model = new List<string>();
var propList = context.GetType().GetProperties();
foreach (var propertyInfo in propList)
{
if (propertyInfo.PropertyType.GetTypeInfo().Name.StartsWith("DbSet" ))
{
model.Add(propertyInfo.PropertyType.GenericTypeArguments[0].Name);
}
}
return model;
}
}