我有一个问题,过去有人可能找到了解决方案。我正在使用AddOrUpdate方法在EF5迁移的Configuration类中为数据库播种。
以下是域模型的快速示例:
public class Club
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
}
public class Court
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
public virtual long? ClubId { get; set; }
public virtual Club Club { get; set; }
}
然后这是我种子方法的摘录:
Club cb = new Club { Name = "Test Club 1" };
context.Set<Club>().AddOrUpdate(m=>m.Name, cb);
context.SaveChanges();
Court crt1 = new Court { ClubId = cb.Id, Name = "Court 1" };
Court crt2 = new Court { ClubId = cb.Id, Name = "Court 2" };
context.Set<Court>().AddOrUpdate(m => new { m.Name, m.ClubId }, crt1, crt2);
context.SaveChanges();
现在,一旦代码到达第7行,就会抛出错误:
没有为类型'System.Nullable`1 [System.Int64]'和'System.Int64'定义二元运算符Equal。
根据我的调查,这是因为ClubId是一个Nullable long。
有什么方法吗?
不是主要问题 - 我只是一个完美主义者,并希望看到其他人如何解决这个问题......
谢谢, 尼克Goloborodko
答案 0 :(得分:10)
我没有一个非常令人满意的答案,但我认为需要对AddOrUpdate实现进行代码更改才能解决此问题,因此我已应用了一种解决方法。
简单地说,您可以手动执行相同的任务,而不是使用AddOrUpdate
。例如:
private void AddOrUpdateCourt(long id, string name, string someOtherPropertyValue)
{
var court = _context.Set<Court>().SingleOrDefault(c => c.Id = id && c.Name = name);
if(court == null)
{
_context.Set<Court>().Add(new Court
{
ClubId=id,
Name=name,
SomeOtherProperty = someOtherPropertyValue
});
}
else
{
court.SomeOtherProperty = someOtherPropertyValue;
}
}
答案 1 :(得分:6)
遇到同样的问题,最后实现了我自己的AddOrUpdate
。
首先,我们必须获取实体的实际主键(可能需要在此添加其他命名约定......):
private static PropertyInfo[] PrimaryKeys<TEntity>()
where TEntity : class
{
return typeof(TEntity).GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(KeyAttribute))
|| "Id".Equals(p.Name, StringComparison.Ordinal))
.ToArray();
}
然后,我们需要解析AddOrUpdate
用来匹配现有项目的“标识符表达式”(如原始AddOrUpdate
):
private static PropertyInfo[] Properties<TEntity>(
Expression<Func<TEntity, object>> identifiers)
where TEntity : class
{
// e => e.SomeValue
var direct = identifiers.Body as MemberExpression;
if (direct != null)
{
return new[] { (PropertyInfo)direct.Member };
}
// e => (object)e.SomeValue
var convert = identifiers.Body as UnaryExpression;
if (convert != null)
{
return new[] { (PropertyInfo)((MemberExpression)convert.Operand).Member };
}
// e => new { e.SomeValue, e.OtherValue }
var multiple = identifiers.Body as NewExpression;
if (multiple != null)
{
return multiple.Arguments
.Cast<MemberExpression>()
.Select(a => (PropertyInfo)a.Member)
.ToArray();
}
throw new NotSupportedException();
}
因此,.AddOrUpdate(m => m.Name, ...)
应该与.AddOrUpdate(m => new { m.Name, m.ClubId }, ...)
一样有用。
最后,我们为每个实体动态构建 where表达式,将其与我们当前的数据库匹配,并相应地采取行动(添加或更新):
public static void AddOrUpdate<TEntity>(this DbContext context,
Expression<Func<TEntity, object>> identifiers,
params TEntity[] entities)
where TEntity : class
{
var primaryKeys = PrimaryKeys<TEntity>();
var properties = Properties<TEntity>(identifiers);
for (var i = 0; i < entities.Length; i++)
{
// build where condition for "identifiers"
var parameter = Expression.Parameter(typeof(TEntity));
var matches = properties.Select(p => Expression.Equal(
Expression.Property(parameter, p),
Expression.Constant(p.GetValue(entities[i]), p.PropertyType)));
var match = Expression.Lambda<Func<TEntity, bool>>(
matches.Aggregate((p, q) => Expression.AndAlso(p, q)),
parameter);
// match "identifiers" for current item
var current = context.Set<TEntity>().SingleOrDefault(match);
if (current != null)
{
// update primary keys
foreach (var k in primaryKeys)
k.SetValue(entities[i], k.GetValue(current));
// update all the values
context.Entry(current).CurrentValues.SetValues(entities[i]);
// replace updated item
entities[i] = current;
}
else
{
// add new item
entities[i] = context.Set<TEntity>().Add(entities[i]);
}
}
}
希望这会有所帮助,虽然问题有点陈旧。