Return 表达式中的 TEntity<Func<TEntity, TResult>>

Return TEntity in Expression<Func<TEntity, TResult>>

我有一个在表达式树中传递 optional/default 参数的方法,如下所示:

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,给selector赋值时,有没有什么办法可以把TEntityreturn当成TResult?我已经尝试了下面的所有方法,但也失败了

// 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;

我认为根据您的要求,您可能有几种选择。

  • 您可以尝试将传入的 TEntity 转换为 TResult。这将需要对 TResult 的类型约束和一个有点混乱的类型约束。
  • 您可以检查 TEntity 是否属于 TResult 类型,如果是,您 return 是转换类型。否则 return 默认 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();
}

在这里你看到了我所说的丑陋的类型约束。您必须引入一种新类型以强制您的 class 约束属于 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();
}

有了这个,它将尝试将实体转换为正确的类型,但如果不能,它将 return 默认为 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();
}

就个人而言,这似乎是您想要的选项,也是我会使用的选项。它本质上具有与默认参数相同的功能,但现在要求,如果未提供选择器,则查询将 return 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 替换。

结论:

可能还有其他选项未在此处列出,但这些是我能想到的最佳选项。我测试过这些是否可以编译,但没有 运行 任何一个。