linq2entities的IQueryable扩展方法

Aar*_*nLS 6 c# linq linq-to-entities entity-framework

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

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

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

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

错误:

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.
Run Code Online (Sandbox Code Playgroud)

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

我真正想要的AsOf(source,DateTime asOf)就是这样source.FirstOrDefault<IHistory>(s => s.EndDate > asOf && asOf >= s.StartDate ),但我不知道如何实现这一点,以便linq2entities支持它.

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

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

小智 3

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

这或许可以作为一个答案:

/// <summary>
/// Type helpers
/// </summary>
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;
        }
    }
}

/// <summary>
/// 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.
/// </summary>
[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;
        }
    }
}

/// <summary>
/// Used to register expressions for extension method to expression substitutions
/// </summary>
static class QueryMethodTranslationExpressions
{
    private static Dictionary<string, LambdaExpression> expressionList = new Dictionary<string, LambdaExpression>();

    /// <summary>
    /// Register expression
    /// </summary>
    /// <typeparam name="TFunc">type of expression delegate</typeparam>
    /// <param name="id">id constant for use with ExpandableQueryMethodAttribute</param>
    /// <param name="expr">expression</param>
    public static void RegisterExpression<TFunc>(string id, Expression<TFunc> expr)
    {
        expressionList.Add(id, expr);
    }

    public static LambdaExpression GetRegistered(string id)
    {
        //Extensions;
        return expressionList[id];
    }
}

static class Extensions
{
    /// <summary>
    /// Use on object sets before using custom extension methods, except inside compiled queries
    /// </summary>
    public static IQueryable<T> AsExtendable<T>(this IQueryable<T> source)
    {
        if (source is ExtendableQuery<T>)
        {
            return (ExtendableQuery<T>)source;
        }

        return new ExtendableQueryProvider(source.Provider).CreateQuery<T>(source.Expression);
    }
}

/// <summary>
/// Provides PlaceHolderQuery
/// 
/// No other functionality
/// </summary>
public class PlaceHolderQueryProvider : IQueryProvider
{
    public PlaceHolderQueryProvider()
    {
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new PlaceHolderQuery<TElement>(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<TResult>(Expression expression)
    {
        throw new NotImplementedException();
    }

    public object Execute(Expression expression)
    {
        throw new NotImplementedException();
    }
}

/// <summary>
/// Does nothing
/// 
/// Acts only as a holder for expression
/// </summary>
public class PlaceHolderQuery<T> : IQueryable<T>, IOrderedQueryable<T>
{

    private Expression _expression;
    private PlaceHolderQueryProvider _provider;

    public PlaceHolderQuery(PlaceHolderQueryProvider provider, Expression expression)
    {
        _provider = provider;
        _expression = expression;
    }

    public IEnumerator<T> 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;
        }
    }
}

/// <summary>
/// Walks the expression tree and invokes custom extension methods ( to expand them ) or substitutes them with custom expressions
/// </summary>
class ExtendableVisitor : ExpressionVisitor
{
    class ExpandingVisitor : ExpressionVisitor
    {
        private Dictionary<ParameterExpression, Expression> _substitutionDictionary;

        public ExpandingVisitor(Dictionary<ParameterExpression, Expression> 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<ParameterExpression, Expression> subDict = new Dictionary<ParameterExpression,Expression>();

                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);
    }
}

/// <summary>
/// Used for query compilation
/// 
/// If custom extension methods are used, the existing CompileQuery functions do not work, so I had to write this.
/// </summary>
static class CompiledExtendableQuery
{
    public static Func<TContext, TResult>
               Compile<TContext, TResult>(
       Expression<Func<TContext, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TResult>
               Compile<TContext, TArg0, TResult>(
       Expression<Func<TContext, TArg0, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TResult>
               Compile<TContext, TArg0, TArg1, TResult>
      (Expression<Func<TContext, TArg0, TArg1, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TArg2, TResult>
               Compile<TContext, TArg0, TArg1, TArg2, TResult>(
       Expression<Func<TContext, TArg0, TArg1, TArg2, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TArg2, TArg3, TResult>
               Compile<TContext, TArg0, TArg1, TArg2, TArg3, TResult>(
       Expression<Func<TContext, TArg0, TArg1, TArg2, TArg3, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TResult>
               Compile<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TResult>(
       Expression<Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TResult>
               Compile<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TResult>(
       Expression<Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult>
               Compile<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult>(
       Expression<Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult>
               Compile<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult>(
       Expression<Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult>
               Compile<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult>(
       Expression<Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult>
               Compile<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult>(
       Expression<Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult>
               Compile<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult>(
       Expression<Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TResult>
               Compile<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TResult>(
       Expression<Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TResult>
               Compile<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TResult>(
       Expression<Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TResult>
               Compile<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TResult>(
       Expression<Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

    public static Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TArg14, TResult>
               Compile<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TArg14, TResult>(
       Expression<Func<TContext, TArg0, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TArg11, TArg12, TArg13, TArg14, TResult>> expr) where TContext : ObjectContext
    {
        return System.Data.Objects.CompiledQuery.Compile(expr.Update(new ExtendableVisitor().Visit(expr.Body), expr.Parameters));
    }

}

/// <summary>
/// The query as it becomes when AsExtendable is called on it.
/// </summary>
class ExtendableQuery<T> : IQueryable<T>, IOrderedQueryable<T>
{
    ExtendableQueryProvider _provider;
    Expression _expression;

    public ExtendableQuery(ExtendableQueryProvider provider, Expression expression)
    {
        _provider = provider;
        _expression = expression;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _provider.ExecuteQuery<T>(_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<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new ExtendableQuery<TElement>(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.In