前一段时间,我想实现一个能够确定是否执行插入或在一个给定的实体的更新的方法,所以我没有揭露“插入”和“更新”的方法,而只是一个简单的“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个对象,首先会导致“新”,但未来九个月将导致“已经存在”。 这是由于这样的事实,EF的“查找”方法从ObjectContext中读取数据,只有当它不存在,它的股价下跌到数据库进行查询。
第一对象之后,给定的类型的ID为0的实体将被跟踪。 连续调用将导致“更新”,这是错误的。
现在,我知道该数据库生成的ID是邪恶的,绝对没有任何ORM好,但我坚持与那些和我需要或者解决这个问题的方法,或者完全删除它,然后回落到单独的“插入”和“更新”的方法并委托给调用者的任务,以确定该怎么做。 因为我们有一个高度解耦解决方案,我宁愿避免这样做。
如果有人可以帮助找到一种方法来解决GetEntityByPrimaryKey方法,这将是真棒。
谢谢。
我有以下几点建议:
1:
我想补充的东西就像一个IsTransient
属性的实体。 它返回true
,如果PK是0,否则返回false
。
您可以使用此属性来更改你的方法如下:
-
IsTransient
==真的吗? - >插入 -
IsTransient
==假的? - >您现有的与数据库校验码
作出这样的虚拟财产,你甚至可以通过重写以支持与“怪” PK实体IsTransient
。
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;
}
因为我的“ID”的代码将是0,我要插入的所有对象
看来你在按键上期待的独特性,当你不为他们提供。 是否有可能将其初始化为唯一的负数 ? (东西是不实际数据库条目的有效值)
我有一个类似的问题(这是自跟踪目标能够告诉如果两个尚未插入子对象是相同的密钥明智或不...),这解决了这个问题。
鉴于你波苏斯是免费DB污秽的,你一定用流利的API声明DB生成的密钥信息。 因此,也许在上下文中DbSets包含这个DB生成的标志。
我使用一个扩展来获得在上下文中使用的所有POCO。 也许有足够的反射,你可以找到一个属性或属性是作为数据库生成的标志是有用的。 其余的则已经很明显了。
也许这一个有用的出发点:
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;
}
}