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);
- 有一个更全局声明的表达式,而不是局部变量。
- 添加DateTime asOf参数,而不是.Now硬编码。
- 如果可能的话,将其改编为扩展方法(这种方式与#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);
这样,您不需要用于构建表达式的框架。