c# - 表达式调用和计数

标签 c# .net linq expression-trees dynamic

我正在寻找一种动态执行以下操作的方法:

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。尽量让它变得干燥。

附言不要让它太困惑,这就是为什么我认为我需要做 ё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 }"

最佳答案

这是一个可以执行我认为您喜欢的工作的程序。它定义了一个函数,该函数采用指向集合内整数属性的路径和一个整数值。然后检查该集合是否有 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);
        }
    }
}

关于c# - 表达式调用和计数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6002131/

相关文章:

c# - 检测应用于 IQueryable<T> 的位置

c# - SqlConnection 状态总是在执行时关闭

c# - WCF 请求体有时是一个流,有时是缓冲的?

c# - 同一个类实现,只是父级不同,如何处理?

c# - 基于HttpListener的自托管站点——如何处理鉴权?

c# - 有条件的 Linq 外连接

c# - 用随机值填充空 Guid 列表的最简单方法

c# - C#-添加到字符串[]成员

c# - Func<Task> 伪装成 Action

c# - DataGridView 上的自定义排序