(如何)我可以创建一个带有类型参数作为变量的选择器函数吗?
(How) can I create a selector function with a type parameter as a variable?
方法 System.Linq.Queryable.OrderBy、.OrderByDescending、.ThenBy 和 .ThenByDescending 有一个带有两个类型参数的参数“keySelector”:TSource 和 TKey。
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector);
我想预先创建keySelector参数,然后将其传递给上述四个方法。
假设我有一个具有三个属性的实体 class MyEntity,例如一个字符串,一个整数和一个布尔值(以及一些继承 MyEntity 的实体类型)。我想通过另一种方法为排序方法创建 keySelector 参数。
private static Expression<Func<TSource, TKey>> GetExpression<TSource, TKey>(string propertyName) where TSource : MyEntity
{
return propertyName switch
{
"propa" => entity => entity.MyPropertyA,
"propb" => entity => entity.MyPropertyB,
"propc" => entity => entity.MyPropertyC,
_ => throw new Exception("Unknown property: " + propertyName)
}
}
现在我遇到了两个错误:
- CS0029 - 无法将类型 'string' 隐式转换为 'TKey'
- CS1662 - 无法将 lambda 表达式转换为预期的委托类型,因为块中的某些 return 类型不能隐式转换为委托 return 类型
CS1662 可以通过 returning entity => (TKey)entity.MyPropertyA
解决,但 CS0029 逻辑上仍然存在。
有什么办法可以提前建立keySelector参数吗?因为目前我被迫重复那个 switch 表达式四次。 (实际上有九个属性。)
从不同的角度解决问题,因为问题标题中所要求的内容并不是真正可行的:
Is there any way to build the keySelector parameter in advance? Because currently I am forced to repeat that switch expression four times. (And there are actually nine properties.)
您可以通过以下方式减少重复:a) 首先应用一个愚蠢的 OrderBy
表达式,以便您始终使用 IOrderedQueryable<T>
和 b) 提供您自己的扩展方法来决定哪个 ThenXxx
基于布尔参数链接的方法1:
public static IOrderedQueryable<TSource> ThenBy<TSource, TKey>(
this IOrderedQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector,
bool descending = false)
{
if(descending) return source.ThenByDescending(keySelector);
return source.ThenBy(keySelector);
}
那么在你的调用代码中,你有
var myUnorderedQuery = ...
var mySortedQuery = myUnsortedQuery.OrderBy(x=>1);
foreach(<sort item to apply>)
{
bool descending = <logic>
switch (propertyName)
{
case "propa":
mySortedQuery = mySortedQuery.ThenBy(entity=>entity.PropertyA, descending);
break;
...
}
}
现在你的代码中只出现了 switch
一次。
如果这是 IEnumerable<T>
,这会更容易,因为上述扩展方法的道德等价物是 built-in 作为 CreatedOrderedEnumerable
,尽管在那种情况下你必须提供比较器或知道默认为 Comparer<T>.Default
.
1另一种方法是接受 IQueryable<T>
的 OrderBy
扩展方法,检查它是否实际上是 IOrderedQueryable<T>
并将其用于 select 是否使用 OrderBy
或 ThenBy
。但是我发现这种方法有点“神奇”,如果你想覆盖已经证明是 IOrderedQueryable<T>
.
的东西的顺序,那会很痛苦。
如果使用object
作为常用键类型(SQL翻译时不会使用该类型进行排序)并显式指定TSource
类型,则可以:
public static Expression<Func<MyEntity, Object>> GetExpression(string propName) {
switch (propName) {
case "propa":
return (MyEntity s) => s.MyPropertyA;
case "propb":
return (MyEntity s) => s.MyPropertyB;
case "propc":
return (MyEntity s) => s.MyPropertyC;
default:
throw new Exception($"Unrecognized property: {propName}");
}
}
方法 System.Linq.Queryable.OrderBy、.OrderByDescending、.ThenBy 和 .ThenByDescending 有一个带有两个类型参数的参数“keySelector”:TSource 和 TKey。
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector);
我想预先创建keySelector参数,然后将其传递给上述四个方法。
假设我有一个具有三个属性的实体 class MyEntity,例如一个字符串,一个整数和一个布尔值(以及一些继承 MyEntity 的实体类型)。我想通过另一种方法为排序方法创建 keySelector 参数。
private static Expression<Func<TSource, TKey>> GetExpression<TSource, TKey>(string propertyName) where TSource : MyEntity
{
return propertyName switch
{
"propa" => entity => entity.MyPropertyA,
"propb" => entity => entity.MyPropertyB,
"propc" => entity => entity.MyPropertyC,
_ => throw new Exception("Unknown property: " + propertyName)
}
}
现在我遇到了两个错误:
- CS0029 - 无法将类型 'string' 隐式转换为 'TKey'
- CS1662 - 无法将 lambda 表达式转换为预期的委托类型,因为块中的某些 return 类型不能隐式转换为委托 return 类型
CS1662 可以通过 returning entity => (TKey)entity.MyPropertyA
解决,但 CS0029 逻辑上仍然存在。
有什么办法可以提前建立keySelector参数吗?因为目前我被迫重复那个 switch 表达式四次。 (实际上有九个属性。)
从不同的角度解决问题,因为问题标题中所要求的内容并不是真正可行的:
Is there any way to build the keySelector parameter in advance? Because currently I am forced to repeat that switch expression four times. (And there are actually nine properties.)
您可以通过以下方式减少重复:a) 首先应用一个愚蠢的 OrderBy
表达式,以便您始终使用 IOrderedQueryable<T>
和 b) 提供您自己的扩展方法来决定哪个 ThenXxx
基于布尔参数链接的方法1:
public static IOrderedQueryable<TSource> ThenBy<TSource, TKey>(
this IOrderedQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector,
bool descending = false)
{
if(descending) return source.ThenByDescending(keySelector);
return source.ThenBy(keySelector);
}
那么在你的调用代码中,你有
var myUnorderedQuery = ...
var mySortedQuery = myUnsortedQuery.OrderBy(x=>1);
foreach(<sort item to apply>)
{
bool descending = <logic>
switch (propertyName)
{
case "propa":
mySortedQuery = mySortedQuery.ThenBy(entity=>entity.PropertyA, descending);
break;
...
}
}
现在你的代码中只出现了 switch
一次。
如果这是 IEnumerable<T>
,这会更容易,因为上述扩展方法的道德等价物是 built-in 作为 CreatedOrderedEnumerable
,尽管在那种情况下你必须提供比较器或知道默认为 Comparer<T>.Default
.
1另一种方法是接受 IQueryable<T>
的 OrderBy
扩展方法,检查它是否实际上是 IOrderedQueryable<T>
并将其用于 select 是否使用 OrderBy
或 ThenBy
。但是我发现这种方法有点“神奇”,如果你想覆盖已经证明是 IOrderedQueryable<T>
.
如果使用object
作为常用键类型(SQL翻译时不会使用该类型进行排序)并显式指定TSource
类型,则可以:
public static Expression<Func<MyEntity, Object>> GetExpression(string propName) {
switch (propName) {
case "propa":
return (MyEntity s) => s.MyPropertyA;
case "propb":
return (MyEntity s) => s.MyPropertyB;
case "propc":
return (MyEntity s) => s.MyPropertyC;
default:
throw new Exception($"Unrecognized property: {propName}");
}
}