Выражение C # для использования закладки Guid - PullRequest
2 голосов
/ 26 марта 2019

У меня есть требование работать с несколькими таблицами, которые необходимо синхронизировать / создать резервную копию. Все классы этих таблиц реализуют ITrackModifiedDate:

interface ITrackModifiedDate
{
    DateTime ModifiedDate { get; set; }
}

Мне нужно работать с этими партиями, а это значит, что мне нужно сделать закладку, где я последний раз занимался. Так что я могу сортировать по ModifiedDate и просто отслеживать свои последние ModifiedDate. Но в плане есть морщина: может случиться так, что несколько записей будут иметь одинаковые значения ModifiedDate. Это означает, что мне нужен вторичный идентификатор, и единственное, на что я могу в общем полагаться, это ключевое поле, которое неизменно представляет собой Guid.

Я получил некоторую помощь в Expression материале от здесь , но когда я попытался переделать, чтобы добавить в предложение "больше чем", все сломалось.

async Task<ICollection<T>> GetModifiedRecords<T>(DateTime modifiedSince, Guid lastId) where T : class, ITrackModifiedDate
{
    var parameterExp = Expression.Parameter(typeof(T), "x");
    var propertyExp = Expression.Property(parameterExp, keyField);
    var target = Expression.Constant(lastId);
    var greaterThanMethod = Expression.GreaterThan(propertyExp, target);
    var lambda = Expression.Lambda<Func<T, bool>>(greaterThanMethod, parameterExp);

    var query = db.Set<T>()
                  .Where(t => t.ModifiedDate > modifiedSince ||
                              t.ModifiedDate == modifiedSince && lambda.Invoke(t));

    var orderByExp = Expression.Lambda(propertyExp, parameterExp);
    var thenByMethodGeneric = typeof(Queryable)
                               .GetMethods()
                               .Single(mi => mi.Name == "ThenBy" && mi.GetParameters().Length == 2);

    var thenByMethod = thenByMethodGeneric.MakeGenericMethod(typeof(T), propertyExp.Type);
    // first order by date, then id
    query = query.OrderBy(t => t.ModifiedDate)
                 .AsQueryable();
    query = (IQueryable<T>)thenByMethod.Invoke(null, new object[] { query, orderByExp });
    return await query.ToListAsync();
}

Попытка выполнить этот запрос приводит к:

System.InvalidOperationException: бинарный оператор GreaterThan не определен для типов «System.Guid» и «System.Guid».

О, дорогой. Кажется, гидам, как и людям, не нравится, когда их сравнивают друг с другом. Либо так, либо я использую неправильное выражение сравнения.

Очевидное решение, которое приходит на ум - преобразовать Guid в строку для сравнения, но (а) это выглядит немного неэффективно, и (б) я не знаю, как написать Expression, который преобразует Направляющий на строку.

Является ли преобразование в строку правильным способом? Если так, что Expression сделает работу? Если нет, то каков правильный подход?

1 Ответ

3 голосов
/ 26 марта 2019

Общий подход заключается в замене неподдерживаемых Guid операторов на Guid.CompareTo (Guid) , например, вызовы. вместо

guidA > guidB

использование

guidA.CompareTo(guidB) > 0

В вашем случае замените

 var greaterThanMethod = Expression.GreaterThan(propertyExp, target);

с

var compareTo = Expression.Call(propertyExp, "CompareTo", Type.EmptyTypes, target);
var greaterThanMethod = Expression.GreaterThan(compareTo, Expression.Constant(0));

Это работает для большинства поставщиков запросов (LINQ to Objects, LINQ To Entities (EF6)). К сожалению, не работает с EF Core 2.x, который требует другого подхода . Если это так, зарегистрируйте пользовательский класс GuidFunctions, как описано в связанном ответе, и используйте вместо этого:

var greaterThanMethod = Expression.Call(
    typeof(GuidFunctions), "IsGreaterThan", Type.EmptyTypes,
    propertyExp, target);
...