C# 在Linq to实体和Linq to对象之间共享表达式

C# 在Linq to实体和Linq to对象之间共享表达式,c#,linq-to-entities,linq-to-objects,func,C#,Linq To Entities,Linq To Objects,Func,我试图在LINQtoEntities调用和其他一些代码之间“共享”一组条件,以减少两个调用之间可能存在的条件不匹配 我首先声明了我的条件: private Func<DateTime, Status, bool> _submissionDateExpiredCondition = (submissionDate, status) => submissionDate < DateTime.Now && status == Status.OK; privat

我试图在LINQtoEntities调用和其他一些代码之间“共享”一组条件,以减少两个调用之间可能存在的条件不匹配

我首先声明了我的条件:

private Func<DateTime, Status, bool> _submissionDateExpiredCondition =
(submissionDate, status) => submissionDate < DateTime.Now && status == Status.OK;

private Func<DateTime, Status, bool> _submissionDateWithinOneWeekCondition =
(submissionDate, status) => DateTime.Now < DbFunctions.AddDays(submissionDate, -7) && status == Status.Pending;

private Func<DateTime?, Status, bool> _bidValidityEndPeriodWithinThirtyDaysCondition =
(bidValidityEndPeriod, status) =>  bidValidityEndPeriod.HasValue && DateTime.Now < DbFunctions.AddDays(bidValidityEndPeriod.Value, -30) && (status == Status.OK);
和(请注意,MyCustomObject与myRepository.FindAll()返回的类型不同。)

private void应用程序条件(列表项){
foreach(项目中的var x){
if(_submissionDateExpiredCondition(x.Data.Timestamp,x.Data.Status)){
x、 Property=“条件1”;
}
否则如果(_submissiondatewithinneweekcondition(x.Data.Timestamp,x.Data.Status))
{
x、 Property=“条件2”;
}
else if(_bidvalidity和period,在第三天条件下(x.Data.ValidityStamp,x.Data.Status))
{
x、 Property=“条件3”;
}
}
}
但是我经常碰到一些常规问题,如
LINQ to实体中不支持LINQ表达式节点类型“Invoke”。

执行存储库查询时

我曾尝试使用谓词生成器(如所示)构建谓词,但没有成功


有人能给我指出正确的方向吗?

晚会迟到了,但有人可能会发现我解决这个问题的方法很有用。 但是,如果没有一些表达式操作,就很难实现

主要问题是:在
.Where
的谓词表达式中,您拥有委托的
调用表达式(即编译代码)。EF无法找出委托的逻辑是什么,因此无法将其转换为SQL。这就是异常产生的地方

目标是得到一个
.Where
谓词lambda表达式,该表达式在逻辑上与您的表达式等价,但EF可以理解。这意味着我们必须从

Expression<Func<EntityType, bool>> xPredicate = x =>
    x.Property == "value" &&
    x.Data.AnotherProperty == true && 
    _submissionDateExpiredCondition(x.Data.Timestamp, x.Data.Status)
    || ...;
,其中,
EntityType
是由
Find
返回的可查询元素类型,该元素类型不同于
MyCustomObject

请注意,委托的调用被其定义表达式(lambda body)替换,其中(lambda)参数
submissionDate
status
被调用的各个参数表达式替换

如果将条件定义为委托,则它们的内部逻辑将在编译代码中丢失,因此我们必须从lambda表达式而不是委托开始:

private Expression<Func<DateTime, Status, bool>> _xSubmissionDateExpiredCondition = (submissionDate, status) => submissionDate < DateTime.Now && status == Status.OK;
// getting the delegate as before (to be used in ApplyConditions) is trivial:
private Func<DateTime, Status, bool> _submissionDateExpiredCondition = _xSubmissionDateExpiredCondition.Compile();

// ... other conditions here
MAGIC
的作用是:查找委托的
InvocationExpression
,该委托是对lambda表达式进行
Compile()
方法调用的结果,并将其替换为lambda的主体,但确保将主体中的lambda参数替换为调用的参数表达式

这里是我的实现。实际上,
MAGIC
被称为
Express.Prepare
在这里,这稍微不太具体

/// <summary>
/// Helps in building expressions.
/// </summary>
public static class Express
{

    #region Prepare

    /// <summary>
    /// Prepares an expression to be used in queryables.
    /// </summary>
    /// <returns>The modified expression.</returns>
    /// <remarks>
    /// The method replaces occurrences of <see cref="LambdaExpression"/>.Compile().Invoke(...) with the body of the lambda, with it's parameters replaced by the arguments of the invocation.
    /// Values are resolved by evaluating properties and fields only.
    /// </remarks>
    public static Expression<TDelegate> Prepare<TDelegate>(this Expression<TDelegate> lambda) => (Expression<TDelegate>)new PrepareVisitor().Visit(lambda);

    /// <summary>
    /// Wrapper for <see cref="Prepare{TDelegate}(Expression{TDelegate})"/>.
    /// </summary>
    public static Expression<Func<T1, TResult>> Prepare<T1, TResult>(Expression<Func<T1, TResult>> lambda) => lambda.Prepare();

    /// <summary>
    /// Wrapper for <see cref="Prepare{TDelegate}(Expression{TDelegate})"/>.
    /// </summary>
    public static Expression<Func<T1, T2, TResult>> Prepare<T1, T2, TResult>(Expression<Func<T1, T2, TResult>> lambda) => lambda.Prepare();

    // NOTE: more overloads of Prepare here.

    #endregion

    /// <summary>
    /// Evaluate an expression to a simple value.
    /// </summary>
    private static object GetValue(Expression x)
    {
        switch (x.NodeType)
        {
            case ExpressionType.Constant:
                return ((ConstantExpression)x).Value;
            case ExpressionType.MemberAccess:
                var xMember = (MemberExpression)x;
                var instance = xMember.Expression == null ? null : GetValue(xMember.Expression);
                switch (xMember.Member.MemberType)
                {
                    case MemberTypes.Field:
                        return ((FieldInfo)xMember.Member).GetValue(instance);
                    case MemberTypes.Property:
                        return ((PropertyInfo)xMember.Member).GetValue(instance);
                    default:
                        throw new Exception(xMember.Member.MemberType + "???");
                }
            default:
                // NOTE: it would be easy to compile and invoke the expression, but it's intentionally not done. Callers can always pre-evaluate and pass a captured member.
                throw new NotSupportedException("Only constant, field or property supported.");
        }
    }

    /// <summary>
    /// <see cref="ExpressionVisitor"/> for <see cref="Prepare{TDelegate}(Expression{TDelegate})"/>.
    /// </summary>
    private sealed class PrepareVisitor : ExpressionVisitor
    {
        /// <summary>
        /// Replace lambda.Compile().Invoke(...) with lambda's body, where the parameters are replaced with the invocation's arguments.
        /// </summary>
        protected override Expression VisitInvocation(InvocationExpression node)
        {
            // is it what we are looking for?
            var call = node.Expression as MethodCallExpression;
            if (call == null || call.Method.Name != "Compile" || call.Arguments.Count != 0 || call.Object == null || !typeof(LambdaExpression).IsAssignableFrom(call.Object.Type))
                return base.VisitInvocation(node);

            // get the lambda
            var lambda = call.Object as LambdaExpression ?? (LambdaExpression)GetValue(call.Object);

            // get the expressions for the lambda's parameters
            var replacements = lambda.Parameters.Zip(node.Arguments, (p, x) => new KeyValuePair<ParameterExpression, Expression>(p, x));

            // return the body with the parameters replaced
            return Visit(new ParameterReplaceVisitor(replacements).Visit(lambda.Body));
        }
    }

    /// <summary>
    /// <see cref="ExpressionVisitor"/> to replace parameters with actual expressions.
    /// </summary>
    private sealed class ParameterReplaceVisitor : ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, Expression> _replacements;

        /// <summary>
        /// Init.
        /// </summary>
        /// <param name="replacements">Parameters and their respective replacements.</param>
        public ParameterReplaceVisitor(IEnumerable<KeyValuePair<ParameterExpression, Expression>> replacements)
        {
            _replacements = replacements.ToDictionary(kv => kv.Key, kv => kv.Value);
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            Expression replacement;
            return _replacements.TryGetValue(node, out replacement) ? replacement : node;
        }
    }
}
//
///帮助构建表达式。
/// 
公共静态类快车
{
#区域准备
/// 
///准备要在查询文件中使用的表达式。
/// 
///修改后的表达式。
/// 
///该方法将.Compile().Invoke(…)的实例替换为lambda的主体,其参数替换为调用的参数。
///仅通过计算属性和字段来解析值。
/// 
公共静态表达式Prepare(此表达式lambda)=>(表达式)new PrepareVisitor()。访问(lambda);
/// 
///包装纸。
/// 
公共静态表达式Prepare(表达式lambda)=>lambda.Prepare();
/// 
///包装纸。
/// 
公共静态表达式Prepare(表达式lambda)=>lambda.Prepare();
//注意:这里有更多的Prepare重载。
#端区
/// 
///将表达式求值为简单值。
/// 
私有静态对象GetValue(表达式x)
{
开关(x.NodeType)
{
大小写表达式类型。常量:
返回((恒压)x)值;
case ExpressionType.MemberAccess:
var xMember=(MemberExpression)x;
var实例=xMember.Expression==null?null:GetValue(xMember.Expression);
开关(xMember.Member.MemberType)
{
案例成员类型。字段:
return((FieldInfo)xMember.Member).GetValue(实例);
案例成员类型。属性:
return((PropertyInfo)xMember.Member).GetValue(实例);
违约:
抛出新异常(xMember.Member.MemberType+“???”);
}
违约:
//注意:编译和调用表达式很容易,但这不是有意的。调用者总是可以预先计算并传递捕获的成员。
抛出新的NotSupportedException(“仅支持常量、字段或属性”);
}
}
/// 
///因为。
/// 
私人密封类准备人:ExpressionVisitor
{
/// 
///将lambda.Compile().Invoke(…)替换为lambda的主体,其中参数替换为调用的参数。
/// 
受保护的重写表达式VisitInvocation(调用表达式节点)
{
//这就是我们要找的吗?
var call=node.Expression作为MethodCallExpression;
如果(call==null | | | call.Method.Name!=“Compile”| | call.Arguments.Count!=0 | | call.Object==null | |!typeof(LambdaExpression).IsAssignableFrom(call.Object.Type))
返回基地。访问职业(节点);
//去拿兰姆达
var lambda=call.Object作为LambdaExpression???(LambdaExpression)GetValue(call.Object);
//获取lambda参数的表达式
var replacements=lambda.Parameters.Zip(node.Arguments,(p,x)=>newkeyv
Expression<Func<EntityType, bool>> xPredicate = x =>
    x.Property == "value" &&
    x.Data.AnotherProperty == true && 
    x.Data.Timestamp < DateTime.Now && x.Data.Status == Status.OK
    || ...;
myRepository.FindAll().Where(xPredicate)
private Expression<Func<DateTime, Status, bool>> _xSubmissionDateExpiredCondition = (submissionDate, status) => submissionDate < DateTime.Now && status == Status.OK;
// getting the delegate as before (to be used in ApplyConditions) is trivial:
private Func<DateTime, Status, bool> _submissionDateExpiredCondition = _xSubmissionDateExpiredCondition.Compile();

// ... other conditions here
Expression<Func<EntityType, bool>> xPredicate = x =>
    x.Property == "value" &&
    x.Data.AnotherProperty == true && 
    _xSubmissionDateExpiredCondition.Compile()(x.Data.Timestamp, x.Data.Status)
    || ...;
xPredicate = MAGIC(xPredicate);
/// <summary>
/// Helps in building expressions.
/// </summary>
public static class Express
{

    #region Prepare

    /// <summary>
    /// Prepares an expression to be used in queryables.
    /// </summary>
    /// <returns>The modified expression.</returns>
    /// <remarks>
    /// The method replaces occurrences of <see cref="LambdaExpression"/>.Compile().Invoke(...) with the body of the lambda, with it's parameters replaced by the arguments of the invocation.
    /// Values are resolved by evaluating properties and fields only.
    /// </remarks>
    public static Expression<TDelegate> Prepare<TDelegate>(this Expression<TDelegate> lambda) => (Expression<TDelegate>)new PrepareVisitor().Visit(lambda);

    /// <summary>
    /// Wrapper for <see cref="Prepare{TDelegate}(Expression{TDelegate})"/>.
    /// </summary>
    public static Expression<Func<T1, TResult>> Prepare<T1, TResult>(Expression<Func<T1, TResult>> lambda) => lambda.Prepare();

    /// <summary>
    /// Wrapper for <see cref="Prepare{TDelegate}(Expression{TDelegate})"/>.
    /// </summary>
    public static Expression<Func<T1, T2, TResult>> Prepare<T1, T2, TResult>(Expression<Func<T1, T2, TResult>> lambda) => lambda.Prepare();

    // NOTE: more overloads of Prepare here.

    #endregion

    /// <summary>
    /// Evaluate an expression to a simple value.
    /// </summary>
    private static object GetValue(Expression x)
    {
        switch (x.NodeType)
        {
            case ExpressionType.Constant:
                return ((ConstantExpression)x).Value;
            case ExpressionType.MemberAccess:
                var xMember = (MemberExpression)x;
                var instance = xMember.Expression == null ? null : GetValue(xMember.Expression);
                switch (xMember.Member.MemberType)
                {
                    case MemberTypes.Field:
                        return ((FieldInfo)xMember.Member).GetValue(instance);
                    case MemberTypes.Property:
                        return ((PropertyInfo)xMember.Member).GetValue(instance);
                    default:
                        throw new Exception(xMember.Member.MemberType + "???");
                }
            default:
                // NOTE: it would be easy to compile and invoke the expression, but it's intentionally not done. Callers can always pre-evaluate and pass a captured member.
                throw new NotSupportedException("Only constant, field or property supported.");
        }
    }

    /// <summary>
    /// <see cref="ExpressionVisitor"/> for <see cref="Prepare{TDelegate}(Expression{TDelegate})"/>.
    /// </summary>
    private sealed class PrepareVisitor : ExpressionVisitor
    {
        /// <summary>
        /// Replace lambda.Compile().Invoke(...) with lambda's body, where the parameters are replaced with the invocation's arguments.
        /// </summary>
        protected override Expression VisitInvocation(InvocationExpression node)
        {
            // is it what we are looking for?
            var call = node.Expression as MethodCallExpression;
            if (call == null || call.Method.Name != "Compile" || call.Arguments.Count != 0 || call.Object == null || !typeof(LambdaExpression).IsAssignableFrom(call.Object.Type))
                return base.VisitInvocation(node);

            // get the lambda
            var lambda = call.Object as LambdaExpression ?? (LambdaExpression)GetValue(call.Object);

            // get the expressions for the lambda's parameters
            var replacements = lambda.Parameters.Zip(node.Arguments, (p, x) => new KeyValuePair<ParameterExpression, Expression>(p, x));

            // return the body with the parameters replaced
            return Visit(new ParameterReplaceVisitor(replacements).Visit(lambda.Body));
        }
    }

    /// <summary>
    /// <see cref="ExpressionVisitor"/> to replace parameters with actual expressions.
    /// </summary>
    private sealed class ParameterReplaceVisitor : ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, Expression> _replacements;

        /// <summary>
        /// Init.
        /// </summary>
        /// <param name="replacements">Parameters and their respective replacements.</param>
        public ParameterReplaceVisitor(IEnumerable<KeyValuePair<ParameterExpression, Expression>> replacements)
        {
            _replacements = replacements.ToDictionary(kv => kv.Key, kv => kv.Value);
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            Expression replacement;
            return _replacements.TryGetValue(node, out replacement) ? replacement : node;
        }
    }
}