Вернуть TEntity в выражении <Func <TEntity, TResult >> - PullRequest
1 голос
/ 20 июня 2019

У меня есть метод, который передает необязательный параметр / значение по умолчанию в дереве выражений, как показано ниже:

Task<TResult> GetSingleAsync<TResult>(Expression<Func<TEntity, TResult>> selector = null);

Что я должен делать, это проверять при вызове вышеупомянутого метода с игнорированием параметра selector, присвойте TResult как TEntity селектору внутри моего метода, в основном что-то вроде selector = (TEntity) => TEntity, поэтому я пытаюсь с реализацией ниже

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
     private readonly DbSet<TEntity> _collection;

     public async Task<TResult> GetSingleAsync<TResult>(Expression<Func<TEntity, TResult>> selector = null)
     {
          IQueryable<TEntity> query = this._collection;

          if (selector == null) selector = (entity) => default(TResult);

          return await query.Select(selector).SingleOrDefaultAsync();
     }
}

, когда я вызываю функцию, подобную GetSingleAsync<User>(), которая игнорируетпараметр по умолчанию selector, однако, selector = x => default(TResult) показывает селектор как null , есть ли способ вернуть TEntity как TResult при присвоении значения selector?Я перепробовал все способы, как показано ниже, но тоже не получилось

// error: cannot implicitly convert type 'TEntity' to 'TResult'
if (selector == null) selector = x => x;
if (selector == null) selector = x => TEntity;
if (selector == null) selector = x => (default)TEntity;
if (selector == null) selector = x => (TResult)x;
if (selector == null) selector = x => x as TResult;

1 Ответ

1 голос
/ 22 июня 2019

Я думаю, что у вас может быть несколько вариантов в зависимости от ваших требований.

  • Вы можете попробовать привести входящее TEntity к TResult. Это потребует ограничения типа на TResult и довольно сложного.
  • Вы можете проверить, имеет ли TEntity тип TResult, и если это так, вы вернете приведенный тип. В противном случае вернуть значение по умолчанию TResult. Для этого не требуется ограничение типа, и я думаю, что это лучшее, что вы могли бы получить без ограничения.
  • Вы можете отказаться от значения параметра по умолчанию все вместе и перегрузить функцию. Лично я думаю, что это будет вашим лучшим выбором.

Вот примеры вариантов, которые я перечислил выше:

Вариант 1: Приведение с ограничением типа.

public async Task<TResult> GetSingleAsync<TResult, TNewEntity>(Expression<Func<TNewEntity, TResult>> selector = null)
where TNewEntity: TEntity, TResult
{
   IQueryable<TNewEntity> query = this._collection;

   if (selector == null) selector = entity => (TResult) entity;

   return await query.Select(selector).SingleOrDefaultAsync();
}

Здесь вы видите уродливое ограничение типа, о котором я говорил. Вы должны ввести новый тип, чтобы убедиться, что ограничение вашего класса имеет тип TResult.

Вариант 2: Проверка типа и приведение.

public async Task<TResult> GetSingleAsync<TResult>(Expression<Func<TEntity, TResult>> selector = null)
{
    IQueryable<TNewEntity> query = this._collection;

    // I would use pattern matching here if I could, but unfortunately it looks like
    // expression trees cannot have pattern matching so we have to box then cast.
    if (selector == null) selector = entity =>  entity is TResult ? (TResult)(object) entity : default(TResult);


    return await query.Select(selector).SingleOrDefaultAsync();
}

При этом он будет пытаться привести сущность к правильному типу, но если это не удастся, он вернет значение по умолчанию для TResult, которое будет null, если это ссылочный тип. Это все равно повлечет за собой ту же проблему, с которой вы сталкивались ранее, но в тех случаях, когда приведение прошло успешно, это может быть тем, что вы хотите.

Вариант 3: Перегрузка метода.

// New method with no selector. Notice the return type is now TEntity
public async Task<TEntity> GetSingleAsync(){
    return GetSingleAsync(x => x); // This now works because TResult is TEntity.
}

// Original method, but now it requires the selector
public async Task<TResult> GetSingleAsync<TResult>(Expression<Func<TEntity, TResult>> selector)
{
    IQueryable<TNewEntity> query = this._collection;
    return await query.Select(selector).SingleOrDefaultAsync();
}

Лично это похоже на тот вариант, который вам нужен, и тот, который я бы использовал. По сути, он обладает той же функциональностью, что и параметр по умолчанию, но теперь требует, чтобы, если селектор не был предоставлен, запрос возвратил TEntity. Однако я не знаю всех ограничений вашей проблемы или требуется ли параметр по умолчанию.

Примечание: это похоже на то, что LINQ делает с дополнительными селекторами. Вот источник нескольких расширений ToDictionary:

public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer) {
    return ToDictionary<TSource, TKey, TSource>(source, keySelector, IdentityFunction<TSource>.Instance, comparer);
}

public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector) {
    return ToDictionary<TSource, TKey, TElement>(source, keySelector, elementSelector, null);
}

Обратите внимание, как они передают функцию идентификации для elementSelector, и каждый TElement по сути просто заменяется на TSource в первом методе.

Вывод:

Могут быть и другие варианты, которые здесь не перечислены, но это лучшие из тех, которые я мог бы придумать. Я проверил, скомпилируются ли они, но не запустил ни одного из них.

...