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
赋值时,有没有什么办法可以把TEntity
return当成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
替换。
结论:
可能还有其他选项未在此处列出,但这些是我能想到的最佳选项。我测试过这些是否可以编译,但没有 运行 任何一个。
我有一个在表达式树中传递 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
赋值时,有没有什么办法可以把TEntity
return当成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
替换。
结论:
可能还有其他选项未在此处列出,但这些是我能想到的最佳选项。我测试过这些是否可以编译,但没有 运行 任何一个。