Подтвердить что ты не робот

EF5 Migrations Seed AddOrUpdate с нулевым критерием выбора

У меня есть вопрос, на который кто-то мог найти решение в прошлом. Я посеял базу данных в классе конфигурации миграции EF5, используя метод AddOrUpdate.

Ниже приведен краткий пример модели домена:

 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, он выдает ошибку:

Бинарный оператор Equal не определен для типов "System.Nullable`1 [System.Int64]" и "System.Int64".

Из моего расследования это связано с тем, что ClubId является Nullable long.

Есть ли способ обойти это?

Не главная проблема - я просто перфекционист, и хотел бы видеть, как другие могли решить это...

Спасибо, Николь Голобородко

4b9b3361

Ответ 1

У меня нет удовлетворительного ответа, но я верю, что для реализации 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;
    }
}

Ответ 2

Если бы та же проблема, в конце реализовала мою собственную 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]);
        }
    }
}

Надеюсь, это поможет, хотя вопрос немного устарел.