【问题标题】:Expression.Call and Count表达式.调用和计数
【发布时间】:2011-05-14 13:30:37
【问题描述】:

我正在寻找一种动态跟踪的方法:

var q = context.Subscription
               .Include("Client")
               .Include("Invoices")
                Where(s=>s.Client.Invoices.Count(i=>i.InvoiceID == SomeInt) > 0);

我想为左侧动态构建表达式:

Expression left = s => s.Client.Invoices.Count(i => i.InvoiceID == iSomeVar); //!
Expression right = Expression.Constant(0);
var binary = Expression.GreaterThan(left, right);

谢谢!

更新说明:

请注意:最终结果必须是

Expression<Func<T, bool>>

简单版:

// To give clear idea, all what I want to achieve is to determine 
// whether specific record exists in reference table using known Path. 
// Ultimately I want to extend following function (which works great by 
// the way, but for simple operations)

static Expression CreateExpression<T>(string propertyPath, 
                                      object propertyValue, 
                                      ParameterExpression parameterExpression)
{
     PropertyInfo property = typeof(T).GetProperty(propertyName);
     MemberExpression left = Expression.Property(parameterExpression, property);
     ConstantExpression right = Expression.Constant(0);
     BinaryExpression binary = Expression.GreaterThan(left, right);

     return binary;
}

// And I want to call this function and get result exactly as shown below:

Expression result = 
           CreateExpression<Subscription>("Client.Invoices.InvoiceID", 
                                          theID,
                                          valueSelector.Parameters.Single());

// Where result will be: 
//       t => t.Client.Invoices.Count(i => i.InvoiceID == theID) > 0;

扩展版:

// 1) I'm using Silverlight 4, EF, RIA.

// 2) At the server side I have a function GetSubscriptionsByCriteria
//   that looks about it:

public IQueryable<Subscription> GetSubscriptionsByCriteria(...)
{
      var query = this.ObjectContext.Subscriptions.Include("Client")
                                                  .Include("Client.Invoices");
      var criteria = BuildCriteria(...);
      return query.Where(criteria)
}

// 3) BuildCriteria(...) function gathers Expressions and 
//    aggregates it into the single Expression with different 
//    AND/OR conditions, something like that:

public Expression<Func<Subscription, bool>> BuildCriteria(
                      List<SearchFilter> filters,
                      Expression<Func<Subscription, bool>> valueSelector)
{
    List<Expression> filterExpressions = new List<Expression>();
    ...
    Expression expr = CreateExpression<Subscription>(
                                   sfItem.DBPropertyName, 
                                   sfItem.DBPropertyValue, 
                                   paramExpression, 
                                   sf.SearchCondition);
    filterExpressions.Add(expr);
    ...

    var filterBody = 
        filterExpressions.Aggregate<Expression>(
                (accumulate, equal) => Expression.And(accumulate, equal));
   return Expression
           .Lambda<Func<Subscription, bool>>(filterBody, paramExpression);
}

// 4) Here is the simplified version of CreateExpression function:

 static Expression CreateExpression<T>(string propertyName, 
                                       object propertyValue, 
                                       ParameterExpression paramExpression)
 {
        PropertyInfo property = typeof(T).GetProperty(propertyName);
        ConstantExpression right = Expression.Constant(0);
        MemberExpression left = Expression.Property(paramExpression, property);

        return binary = Expression.Equals(left, right);
 }

所以,我希望现在很清楚为什么我在原始帖子中需要左侧的表达式。尽量让这个干燥。

附:不要让它太混乱这里是为什么我认为我需要做ёExpression.Call(...)ё: 当我运行以下代码并打破它以查看 DebugView 时,我注意到了这一点:

Expression<Func<Subscription, bool>> predicate = 
           t => t.Client.Invoices.Count(i => i.InvoiceID == 5) > 0;
BinaryExpression eq = (BinaryExpression)predicate.Body;
var left = eq.Left; // <-- See DEBUG VIEW
var right = eq.Right;   

// DEBUG VIEW:
// Arguments: Count = 2
//            [0] = {t.Client.Invoices}
//            [1] = {i => (i.InvoiceID == 5)}
// DebugView: ".Call System.Linq.Enumerable.Count(
//               ($t.Client).ClientInvoices,
//               .Lambda#Lambda1<System.Func`2[SLApp.Web.Invoice,System.Boolean]>)
//               .Lambda#Lambda1<System.Func`2[SLApp.Web.Invoice,System.Boolean]>
//               (SLApp.Web.ClientInvoice $i){ $i.ClientInvoiceID == 5 }"

【问题讨论】:

    标签: c# .net linq expression-trees dynamic


    【解决方案1】:

    这是一个可以执行我认为您想要的工作的程序。它定义了一个函数,该函数获取集合内整数属性的路径和整数值。然后它检查该集合是否具有该值的 Count > 0。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Collections;
    
    namespace Test_Console
    {
        public class Subscription
        {
            public int Id { get; set; }
            public Client Client { get; set; }
        }
    
        public class Client
        {
            public ICollection<Invoice> Invoices { get; set; }
        }
    
        public class Invoice
        {
            public int Id { get; set; }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                var subscriptions = new[]
                {
                    new Subscription { Id = 1, Client = new Client { Invoices = new [] {
                        new Invoice { Id = 1 },
                        new Invoice { Id = 2 },
                        new Invoice { Id = 5 }
                    } } },
                    new Subscription { Id = 2, Client = new Client { Invoices = new [] {
                        new Invoice { Id = 4 },
                        new Invoice { Id = 5 },
                        new Invoice { Id = 5 }
                    } } },
                    new Subscription { Id = 3, Client = new Client { Invoices = new Invoice[] {
                    } } },
                };
    
                var propertyPath = "Client.Invoices.Id";
                Console.WriteLine("What Id would you like to check " + propertyPath + " for?");
                var propertyValue = int.Parse(Console.ReadLine());
                var whereNumberOne = makeWhere<Subscription>(propertyPath, propertyValue);
    
                Console.WriteLine("The following Subscription objects match:");
                foreach (var s in subscriptions.Where(whereNumberOne).ToList())
                {
                    Console.WriteLine("Id: " + s.Id);
                }
            }
    
            private static Func<T, bool> makeWhere<T>(string propertyPath, int propertyValue)
            {
                string[] navigateProperties = propertyPath.Split('.');
    
                var currentType = typeof(T);
                var functoidChain = new List<Func<object, object>>();
                functoidChain.Add(x => x);  // identity function starts the chain
                foreach (var nextProperty in navigateProperties)
                {
                    // must be inside loop so the closer on the functoids works properly
                    PropertyInfo nextPropertyInfo;
    
                    if (currentType.IsGenericType
                     && currentType.GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable)))
                    {
                        nextPropertyInfo = currentType.GetGenericArguments()[0].GetProperty(nextProperty);
                        functoidChain.Add(x =>
                            ((IEnumerable<object>)x)
                            .Count(y => (int)nextPropertyInfo.GetValue(y, null) == propertyValue)
                        );
                    }
                    else
                    {
                        nextPropertyInfo = currentType.GetProperty(nextProperty);
                        functoidChain.Add(x => nextPropertyInfo.GetValue(x, null));
                    }
                    currentType = nextPropertyInfo.PropertyType;
                }
                // compose the functions together
                var composedFunctoidChain = functoidChain.Aggregate((f, g) => x => g(f(x)));
                var leftSide = new Func<T, int>(x => (int)composedFunctoidChain(x));
                return new Func<T, bool>(r => leftSide(r) > 0);
            }
        }
    }
    

    【讨论】:

    • 谢谢,但您提供的链接与我遇到的问题无关
    • 非常感谢您回复此问题,但是您提供的解决方案仍然无法解决问题。我非常需要左侧的表达式,并尝试在上面的帖子中解释原因。
    • 好的,我明白你现在在做什么,做得好解释得更好。我仍然不确定你需要表达,我会告诉你为什么。您总是在右侧使用 > 0,所以这是不变的。如果你改变它,那么你应该让它动态化。让一切动态化并不是更好的设计。在我看来,最好的设计只是尽可能简单和 DRYly 解决问题。我会编辑我的答案,让我知道你的想法。
    • 谢谢Milimetric,好吧,我提供的代码非常简化,我只是想了解如何创建更复杂的表达式树,适用于特定场景,我看到了很多示例,从不同的地方尝试过,但没有一个对我有用。
    • 好的,看看我的最新消息。您可以在需要的地方将其更改为更具动态性,但只要在循环内定义了所有与 lambda 相关的变量,它就可以工作,因此闭包在评估时按您所期望的那样工作。
    【解决方案2】:

    我认为这应该让你更接近你的目标:

    static Expression<Func<T, bool>> CreateAnyExpression<T, T2>(string propertyPath, 
                                        Expression<Func<T2, bool>> matchExpression)
    {
        var type = typeof(T);
        var parameterExpression = Expression.Parameter(type, "s");
        var propertyNames = propertyPath.Split('.');
        Expression propBase = parameterExpression;
        foreach(var propertyName in propertyNames)
        {
            PropertyInfo property = type.GetProperty(propertyName);
            propBase = Expression.Property(propBase, property);
            type = propBase.Type;
        }
        var itemType = type.GetGenericArguments()[0];
        // .Any(...) is better than .Count(...) > 0
        var anyMethod = typeof(Enumerable).GetMethods()
            .Single(m => m.Name == "Any" && m.GetParameters().Length == 2)
            .MakeGenericMethod(itemType);
        var callToAny = Expression.Call(anyMethod, propBase, matchExpression);
        return Expression.Lambda<Func<T, bool>>(callToAny, parameterExpression);
    }
    

    这样称呼它:

    CreateAnyExpression<Subscription, Invoice>("Client.Invoices", i => i.InvoiceID == 1)
    

    ...产生以下Expression&lt;Func&lt;Subscription,bool&gt;&gt;

    s => s.Client.Invoices.Any(i => (i.InvoiceID == 1)) 
    

    【讨论】:

      【解决方案3】:

      这是一个构建 Linq 表达式的工作程序

      {(x.Children.Count(y => y.SomeID == SomeVar) > 0)}
      
      using System;
      using System.Linq;
      using System.Linq.Expressions;
      
      namespace ExpressionTree
      {
          class Program
          {
              static void Main(string[] args)
              {
                  ParameterExpression foundX = Expression.Parameter(typeof(Parent), "x");
                  Guid[] guids = new Guid[1] { Guid.NewGuid() };
      
                  Expression expression = GetCountWithPredicateExpression(guids, foundX);
              }
      
              private static Expression GetCountWithPredicateExpression(Guid[] idsToFilter, ParameterExpression foundX)
              {
                  System.Reflection.PropertyInfo childIDPropertyInfo = typeof(Child).GetProperty(nameof(Child.SomeID));
                  ParameterExpression foundY = Expression.Parameter(typeof(Child), "y");
      
                  Expression childIDLeft = Expression.Property(foundY, childIDPropertyInfo);
                  Expression conditionExpression = Expression.Constant(false, typeof(bool));
      
                  foreach (Guid id in idsToFilter)
                      conditionExpression = Expression.Or(conditionExpression, Expression.Equal(childIDLeft, Expression.Constant(id)));
      
                  Expression<Func<Child, bool>> idLambda = Expression.Lambda<Func<Child, bool>>(conditionExpression, foundY);
      
                  var countMethod = typeof(Enumerable).GetMethods()
                      .First(method => method.Name == "Count" && method.GetParameters().Length == 2)
                      .MakeGenericMethod(typeof(Child));
      
                  System.Reflection.PropertyInfo childrenPropertyInfo = typeof(Parent).GetProperty("Children");
                  Expression childrenLeft = Expression.Property(foundX, childrenPropertyInfo);
      
                  Expression ret = Expression.GreaterThan(Expression.Call(countMethod, childrenLeft, idLambda), Expression.Constant(0));
      
                  return ret;
              }
          }
      
          public class Parent
          {
              public Child[] Children { get; set; }
          }
      
          public class Child
          {
              public int ID { get; set; }
              public Guid SomeID { get; set; }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-14
        • 2019-09-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多