13

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

4

2 回答 2

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;
    }
}
于 2013-08-28T16:16:09.533 回答
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]);
        }
    }
}

希望这会有所帮助,尽管这个问题有点老了。

于 2015-03-30T06:02:55.253 回答