linq2entities的IQueryable扩展方法

我正在尝试实现一个与linq2entities一起使用的扩展方法。 我最初假设如果我的扩展方法接受并返回了一个IQueryable,并且只要我的表达式只使用了受支持的方法,那么它就能正常工作。 我有很多麻烦,所以作为最后的手段,我复制了一个我知道工作的现有.NET扩展方法(FirstOrDefault)并简单地重命名它。 看起来它会根据方法返回的表达式来评估“无法转换为商店表达式”validation,而不是方法本身的名称。

var prs = db.People.Where(p => p.PersonKey == 15).Select(p => new { id = p.PersonKey, name1 = p.PersonHistories.AsQueryable().AsOf().Name } ).ToList(); 

我的扩展方法,它只是我重命名的FirstOrDefault的副本:

 public static TSource AsOf(this IQueryable source) { return source.Provider.Execute(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSource) }), new Expression[] { source.Expression })); } 

错误:

 LINQ to Entities does not recognize the method 'Models.PersonHistory AsOf[PersonHistory](System.Linq.IQueryable`1[Models.PersonHistory])' method, and this method cannot be translated into a store expression. 

如何实现Linq2Entities支持的IQueryable扩展方法?

我真正想要的是AsOf(source,DateTime asOf)要做的事情就像source.FirstOrDefault source.FirstOrDefault(s => s.EndDate > asOf && asOf >= s.StartDate ) ,但我不知道如何实现这一点所以它由linq2entities支持。

LINQKIT:这是我用linqkit想出来的,我希望不知怎的,我可以把它变成更可重用的东西:

 Expression<Func> IsCurrent = (p) => p.Ends > DateTime.Now && p.Starts <= DateTime.Now; var query = db.PersonHistories.Where(IsCurrent); 
  1. 有一个更全局声明的表达式,而不是局部变量。
  2. 添加DateTime asOf参数,而不是.Now硬编码。
  3. 如果可能的话,将其改编为扩展方法(这种方式与#1相同,只是扩展方法是理想的。

我看到你在另一个问题上评论了我的答案,所以我想我也会在这里回复。 我对代码做了一些修改和改进(支持编译查询和自定义扩展方法到表达式替换)。

这可以作为答案:

 ///  /// Type helpers ///  internal static class TypeSystem { private static Type FindIEnumerable(Type seqType) { Type type; if (seqType == null || seqType == typeof(string) || seqType == typeof(byte[])) { return null; } else { if (!seqType.IsArray) { if (seqType.IsGenericType) { Type[] genericArguments = seqType.GetGenericArguments(); int num = 0; while (num < (int)genericArguments.Length) { Type type1 = genericArguments[num]; Type[] typeArray = new Type[1]; typeArray[0] = type1; Type type2 = typeof(IEnumerable<>).MakeGenericType(typeArray); if (!type2.IsAssignableFrom(seqType)) { num++; } else { type = type2; return type; } } } Type[] interfaces = seqType.GetInterfaces(); if (interfaces != null && (int)interfaces.Length > 0) { Type[] typeArray1 = interfaces; int num1 = 0; while (num1 < (int)typeArray1.Length) { Type type3 = typeArray1[num1]; Type type4 = TypeSystem.FindIEnumerable(type3); if (type4 == null) { num1++; } else { type = type4; return type; } } } if (!(seqType.BaseType != null) || !(seqType.BaseType != typeof(object))) { return null; } else { return TypeSystem.FindIEnumerable(seqType.BaseType); } } else { Type[] elementType = new Type[1]; elementType[0] = seqType.GetElementType(); return typeof(IEnumerable<>).MakeGenericType(elementType); } } } internal static Type GetElementType(Type seqType) { Type type = TypeSystem.FindIEnumerable(seqType); if (type != null) { return type.GetGenericArguments()[0]; } else { return seqType; } } } ///  /// Marks an extension as compatible for custom linq expression expansion /// Optionally if you can not write the extension method to fit your needs, you can provide a /// expression id constant for a registered expression. ///  [AttributeUsage(AttributeTargets.Method, AllowMultiple= false, Inherited = false)] class ExpandableQueryMethodAttribute : Attribute { public ExpandableQueryMethodAttribute() { } public ExpandableQueryMethodAttribute(string expressionId) { _expressionId = expressionId; } private string _expressionId; public LambdaExpression TranslationExpression { get { return _expressionId != null ? QueryMethodTranslationExpressions.GetRegistered(_expressionId) : null; } } } ///  /// Used to register expressions for extension method to expression substitutions ///  static class QueryMethodTranslationExpressions { private static Dictionary expressionList = new Dictionary(); ///  /// Register expression ///  /// type of expression delegate /// id constant for use with ExpandableQueryMethodAttribute /// expression public static void RegisterExpression(string id, Expression expr) { expressionList.Add(id, expr); } public static LambdaExpression GetRegistered(string id) { //Extensions; return expressionList[id]; } } static class Extensions { ///  /// Use on object sets before using custom extension methods, except inside compiled queries ///  public static IQueryable AsExtendable(this IQueryable source) { if (source is ExtendableQuery) { return (ExtendableQuery)source; } return new ExtendableQueryProvider(source.Provider).CreateQuery(source.Expression); } } ///  /// Provides PlaceHolderQuery /// /// No other functionality ///  public class PlaceHolderQueryProvider : IQueryProvider { public PlaceHolderQueryProvider() { } public IQueryable CreateQuery(Expression expression) { return new PlaceHolderQuery(this, expression); } public IQueryable CreateQuery(Expression expression) { Type elementType = TypeSystem.GetElementType(expression.Type); try { return (IQueryable)Activator.CreateInstance(typeof(PlaceHolderQuery<>).MakeGenericType(elementType), new object[] { this, expression }); } catch (System.Reflection.TargetInvocationException tie) { throw tie.InnerException; } } public TResult Execute(Expression expression) { throw new NotImplementedException(); } public object Execute(Expression expression) { throw new NotImplementedException(); } } ///  /// Does nothing /// /// Acts only as a holder for expression ///  public class PlaceHolderQuery : IQueryable, IOrderedQueryable { private Expression _expression; private PlaceHolderQueryProvider _provider; public PlaceHolderQuery(PlaceHolderQueryProvider provider, Expression expression) { _provider = provider; _expression = expression; } public IEnumerator GetEnumerator() { throw new NotImplementedException(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw new NotImplementedException(); } public Type ElementType { get { return typeof(T); } } public Expression Expression { get { return _expression; } } public IQueryProvider Provider { get { return _provider; } } } ///  /// Walks the expression tree and invokes custom extension methods ( to expand them ) or substitutes them with custom expressions ///  class ExtendableVisitor : ExpressionVisitor { class ExpandingVisitor : ExpressionVisitor { private Dictionary _substitutionDictionary; public ExpandingVisitor(Dictionary subDict) { _substitutionDictionary = subDict; } protected override Expression VisitParameter(ParameterExpression node) { if (_substitutionDictionary != null && _substitutionDictionary.ContainsKey(node)) return _substitutionDictionary[node]; else return base.VisitParameter(node); } } IQueryProvider _provider; internal ExtendableVisitor() { _provider = new PlaceHolderQueryProvider(); } protected override Expression VisitMethodCall(MethodCallExpression node) { ExpandableQueryMethodAttribute attrib = (ExpandableQueryMethodAttribute)node.Method.GetCustomAttributes(typeof(ExpandableQueryMethodAttribute), false).FirstOrDefault(); if (attrib != null && node.Method.IsStatic) { if (attrib.TranslationExpression != null && attrib.TranslationExpression.Parameters.Count == node.Arguments.Count) { Dictionary subDict = new Dictionary(); for (int i = 0; i < attrib.TranslationExpression.Parameters.Count; i++) { subDict.Add(attrib.TranslationExpression.Parameters[i], node.Arguments[i]); } ExpandingVisitor expander = new ExpandingVisitor(subDict); Expression exp = expander.Visit(attrib.TranslationExpression.Body); return exp; } else if (typeof(IQueryable).IsAssignableFrom(node.Method.ReturnType)) { object[] args = new object[node.Arguments.Count]; args[0] = _provider.CreateQuery(node.Arguments[0]); for (int i = 1; i < node.Arguments.Count; i++) { Expression arg = node.Arguments[i]; args[i] = (arg.NodeType == ExpressionType.Constant) ? ((ConstantExpression)arg).Value : arg; } Expression exp = ((IQueryable)node.Method.Invoke(null, args)).Expression; return exp; } } return base.VisitMethodCall(node); } } ///  /// Used for query compilation /// /// If custom extension methods are used, the existing CompileQuery functions do not work, so I had to write this. ///  static class CompiledExtendableQuery { public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile (Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } public static Func Compile( Expression> expr) where TContext : ObjectContext { return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters)); } } ///  /// The query as it becomes when AsExtendable is called on it. ///  class ExtendableQuery : IQueryable, IOrderedQueryable { ExtendableQueryProvider _provider; Expression _expression; public ExtendableQuery(ExtendableQueryProvider provider, Expression expression) { _provider = provider; _expression = expression; } public IEnumerator GetEnumerator() { return _provider.ExecuteQuery(_expression).GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } public Type ElementType { get { return typeof(T); } } public Expression Expression { get { return _expression; } } public IQueryProvider Provider { get { return _provider; } } } class ExtendableQueryProvider : IQueryProvider { IQueryProvider _underlyingQueryProvider; private ExtendableQueryProvider() { } internal ExtendableQueryProvider(IQueryProvider underlyingQueryProvider) { _underlyingQueryProvider = underlyingQueryProvider; } public IQueryable CreateQuery(Expression expression) { return new ExtendableQuery(this, expression); } public IQueryable CreateQuery(Expression expression) { Type elementType = TypeSystem.GetElementType(expression.Type); try { return (IQueryable)Activator.CreateInstance(typeof(ExtendableQuery<>).MakeGenericType(elementType), new object[] { this, expression }); } catch (System.Reflection.TargetInvocationException tie) { throw tie.InnerException; } } internal IEnumerable ExecuteQuery(Expression expression) { return _underlyingQueryProvider.CreateQuery(Visit(expression)).AsEnumerable(); } public TResult Execute(Expression expression) { return _underlyingQueryProvider.Execute(Visit(expression)); } public object Execute(Expression expression) { return _underlyingQueryProvider.Execute(Visit(expression)); } private Expression Visit(Expression exp) { ExtendableVisitor vstr = new ExtendableVisitor(); Expression visitedExp = vstr.Visit(exp); return visitedExp; } } 

对不起,我的回复简短,这是半夜,我必须快点,因为有工作要做。

我很乐意回答您的任何问题。

这应该可以在不扩展查询提供程序的情 它基本上将它分解为你的IsCurrent函数正在做的事情。

 public static class IQueryableExtensions { public static IQueryable IsCurrent(this IQueryable query, Expression> expressionEnd, Expression> expressionStart, DateTime asOf) where T : class { // Lambdas being sent in ParameterExpression paramEnd = expressionEnd.Parameters.Single(); ParameterExpression paramStart = expressionStart.Parameters.Single(); // GT Comparison BinaryExpression expressionGT = ExpressionGT(expressionEnd.Body, asOf); // LT Comparison BinaryExpression expressionLT = ExpressionLE(expressionStart.Body, asOf); query = query.Where(Expression.Lambda>(expressionGT, paramEnd)) .Where(Expression.Lambda>(expressionLT, paramStart)); return query; } private static BinaryExpression ExpressionLE(Expression body, DateTime value) { return Expression.LessThanOrEqual(body, Expression.Constant(value, typeof(DateTime))); } private static BinaryExpression ExpressionGT(Expression body, DateTime value) { return Expression.GreaterThan(body, Expression.Constant(value, typeof(DateTime))); } } 

并使用它

 var query = db.PersonHistories.IsCurrent( p => p.Ends, p => p.Starts, DateTime.Now ); 

而不是使用表达式,例如

 Expression> IsCurrent = (p) => p.Ends > DateTime.Now && p.Starts <= DateTime.Now; var query = db.PersonHistories.Where(IsCurrent); 

您可以定义扩展方法,例如:

 public static IsCurrent Func< IQueryable, DateTime, IQueryable >() { return (IQueryable query, DateTime referenceDate) => query.Where(p.Ends > referenceDate && p.Starts <= referenceDate); } 

使用它像这样:

 var query = IsCurrent(); var results = query(context.PeoplesHistory, referenceDate); 

要么:

 var results = query(previousResults, referenceDate); 

或者:

 public static IsCurrent Func, IQueryable>( DateTime referenceDate) { return (IQueryable query) => query.Where(p.Ends > referenceDate && p.Starts <= referenceDate); } var query = IsCurrent(refernceDate); var results = query(context.PeoplesHistory); 

这样,您不需要用于构建表达式的框架。