EF5迁移种子AddOrUpdate具有可为空的选择条件

时间:2013-03-14 09:49:46

标签: entity-framework-5 ef-migrations

我有一个问题,过去有人可能找到了解决方案。我正在使用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

2 个答案:

答案 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]);
        }
    }
}

希望这会有所帮助,虽然问题有点陈旧。