具有可选泛型参数的扩展方法
Extension method with optional generic argument
我需要基于扩展方法实现 API(即我必须使用静态非泛型 class)。 API 应该可以流畅地使用 LINQ fluent API 并且主要是使用 IQueryable 参数。像这样:
public static class SomeExtensions
{
public static IQueryable<TEntity> SomeMethod<TEntity>(this IQueryable<TEntity> set, ... some arguments)
{
}
}
现在,假设该方法应该接受一些参数加上一个 Expression<Func<TEntity, TResult>>
一个:
public static IQueryable<TEntity> SomeMethod<TEntity, TResult>(
this IQueryable<TEntity> set,
...,
Expression<Func<TEntity, TResult>> orderByExpression)
{
}
我想将 orderByExpression 传递给流利的 OrderBy 方法 API。或者如果 orderByExpression == null
.
做其他事情
当然,我想要这样的东西:
public static IQueryable<TEntity> SomeMethod<TEntity, TResult>(
this IQueryable<TEntity> set,
...,
Expression<Func<TEntity, TResult>> orderByExpression = null)
{
}
...但是在调用此方法时 w/o 可选参数我必须隐式传递泛型类型,因为编译器不知道 TResult 的类型。
我看到了一些可能的方法,但我不太喜欢它们。
定义两个方法:一个带有这个参数,一个w/o,然后从第二个调用第一个。我不喜欢它,因为实际上 API 中有很多这样的方法,我必须为它们中的每一个定义一个额外的方法。
使用Expression<Func<TEntity, object>>
代替Expression<Func<TEntity, TResult>>
(目前是这样)。我摆脱了泛型类型,但是像 int 这样的简单(值)类型存在问题:LINQ 在尝试将 System.Int32 转换为 System.Object.
时引发异常
也许(还没有尝试过)我可以使用 Expression<Func<TEntity, dynamic>>
- 但我认为这根本不是一个好方法。
还有其他想法吗?
您指定的第一个选项是最明显和最干净的方法,尽管这种方法维护工作量最大。
此外,您可以在流利的语法中引入另一个步骤。喜欢定义:
public interface ISortableQueryable<T> : IQueryable<T>
{
IQueryable<T> WithSorting<TResult>(Expression<Func<TEntity, TResult>> orderByExpression);
}
正在退货:
public static ISortableQueryable<TEntity> SomeMethod<TEntity>(
this IQueryable<TEntity> @this, ...)
{ ... }
并提供此接口的实现,其中常规 IQueryable
调用要么重定向到它在构造函数中接收的 IQueryable
实例,要么根据 WithSorting
方法是否被调用。
从来电者的角度来看,选项 (1) 是最好的。请记住 API 的主要目标是让调用者的生活更轻松,因此在实施方面付出额外的努力应该是值得的。
选项(3)不好。您不想输入 dynamic
类型引入的并发症。而且EF不喜欢动态表达式。
选项(2)其实还不错。因此,如果它是您当前使用的,您可以继续使用它。要使 EF 满意,您只需通过删除为值类型属性引入的 Convert
来转换传递的表达式。为此,您可以使用以下辅助方法:
internal static IQueryable<T> ApplyOrderBy<T>(
this IQueryable<T> source,
Expression<Func<T, object>> orderByExpression = null)
{
if (orderByExpression == null) return source;
var body = orderByExpression.Body;
// Strip the Convert if any
if (body.NodeType == ExpressionType.Convert)
body = ((UnaryExpression)body).Operand;
// Create new selector
var keySelector = Expression.Lambda(body, orderByExpression.Parameters[0]);
// Here we cannot use the typed Queryable.OrderBy method because
// we don't know the TKey, so we compose a method call instead
var queryExpression = Expression.Call(
typeof(Queryable), "OrderBy", new[] { typeof(T), body.Type },
source.Expression, Expression.Quote(keySelector));
return source.Provider.CreateQuery<T>(queryExpression);
}
这是一个小测试,展示了上述方法如何适用于不同的 属性 类型:
var input = new[]
{
new { Id = 2, Name = "B", ParentId = (int?)1 },
new { Id = 1, Name = "A", ParentId = (int?)null },
}.AsQueryable();
var output1 = input.ApplyOrderBy(e => e.Id).ToList();
var output2 = input.ApplyOrderBy(e => e.Name).ToList();
var output3 = input.ApplyOrderBy(e => e.ParentId).ToList();
您的示例用法示例:
public static IQueryable<TEntity> SomeMethod<TEntity>(
this IQueryable<TEntity> source,
...,
Expression<Func<TEntity, object>> orderByExpression = null)
{
var result = source;
result = preprocess(result);
result = result.ApplyOrderBy(orderByExpression);
result = postprocess(result);
return result;
}
我需要基于扩展方法实现 API(即我必须使用静态非泛型 class)。 API 应该可以流畅地使用 LINQ fluent API 并且主要是使用 IQueryable 参数。像这样:
public static class SomeExtensions
{
public static IQueryable<TEntity> SomeMethod<TEntity>(this IQueryable<TEntity> set, ... some arguments)
{
}
}
现在,假设该方法应该接受一些参数加上一个 Expression<Func<TEntity, TResult>>
一个:
public static IQueryable<TEntity> SomeMethod<TEntity, TResult>(
this IQueryable<TEntity> set,
...,
Expression<Func<TEntity, TResult>> orderByExpression)
{
}
我想将 orderByExpression 传递给流利的 OrderBy 方法 API。或者如果 orderByExpression == null
.
当然,我想要这样的东西:
public static IQueryable<TEntity> SomeMethod<TEntity, TResult>(
this IQueryable<TEntity> set,
...,
Expression<Func<TEntity, TResult>> orderByExpression = null)
{
}
...但是在调用此方法时 w/o 可选参数我必须隐式传递泛型类型,因为编译器不知道 TResult 的类型。
我看到了一些可能的方法,但我不太喜欢它们。
定义两个方法:一个带有这个参数,一个w/o,然后从第二个调用第一个。我不喜欢它,因为实际上 API 中有很多这样的方法,我必须为它们中的每一个定义一个额外的方法。
使用
Expression<Func<TEntity, object>>
代替Expression<Func<TEntity, TResult>>
(目前是这样)。我摆脱了泛型类型,但是像 int 这样的简单(值)类型存在问题:LINQ 在尝试将 System.Int32 转换为 System.Object. 时引发异常
也许(还没有尝试过)我可以使用
Expression<Func<TEntity, dynamic>>
- 但我认为这根本不是一个好方法。
还有其他想法吗?
您指定的第一个选项是最明显和最干净的方法,尽管这种方法维护工作量最大。
此外,您可以在流利的语法中引入另一个步骤。喜欢定义:
public interface ISortableQueryable<T> : IQueryable<T>
{
IQueryable<T> WithSorting<TResult>(Expression<Func<TEntity, TResult>> orderByExpression);
}
正在退货:
public static ISortableQueryable<TEntity> SomeMethod<TEntity>(
this IQueryable<TEntity> @this, ...)
{ ... }
并提供此接口的实现,其中常规 IQueryable
调用要么重定向到它在构造函数中接收的 IQueryable
实例,要么根据 WithSorting
方法是否被调用。
从来电者的角度来看,选项 (1) 是最好的。请记住 API 的主要目标是让调用者的生活更轻松,因此在实施方面付出额外的努力应该是值得的。
选项(3)不好。您不想输入 dynamic
类型引入的并发症。而且EF不喜欢动态表达式。
选项(2)其实还不错。因此,如果它是您当前使用的,您可以继续使用它。要使 EF 满意,您只需通过删除为值类型属性引入的 Convert
来转换传递的表达式。为此,您可以使用以下辅助方法:
internal static IQueryable<T> ApplyOrderBy<T>(
this IQueryable<T> source,
Expression<Func<T, object>> orderByExpression = null)
{
if (orderByExpression == null) return source;
var body = orderByExpression.Body;
// Strip the Convert if any
if (body.NodeType == ExpressionType.Convert)
body = ((UnaryExpression)body).Operand;
// Create new selector
var keySelector = Expression.Lambda(body, orderByExpression.Parameters[0]);
// Here we cannot use the typed Queryable.OrderBy method because
// we don't know the TKey, so we compose a method call instead
var queryExpression = Expression.Call(
typeof(Queryable), "OrderBy", new[] { typeof(T), body.Type },
source.Expression, Expression.Quote(keySelector));
return source.Provider.CreateQuery<T>(queryExpression);
}
这是一个小测试,展示了上述方法如何适用于不同的 属性 类型:
var input = new[]
{
new { Id = 2, Name = "B", ParentId = (int?)1 },
new { Id = 1, Name = "A", ParentId = (int?)null },
}.AsQueryable();
var output1 = input.ApplyOrderBy(e => e.Id).ToList();
var output2 = input.ApplyOrderBy(e => e.Name).ToList();
var output3 = input.ApplyOrderBy(e => e.ParentId).ToList();
您的示例用法示例:
public static IQueryable<TEntity> SomeMethod<TEntity>(
this IQueryable<TEntity> source,
...,
Expression<Func<TEntity, object>> orderByExpression = null)
{
var result = source;
result = preprocess(result);
result = result.ApplyOrderBy(orderByExpression);
result = postprocess(result);
return result;
}