c# - 如何创建动态 Entity Framework 过滤表达式,如 Expression<Func<T, bool>>

标签 c# entity-framework linq lambda dynamic-linq

我想创建一个用于过滤的动态 lambda 表达式。

我搜索了一点,但找不到对子集合有用的东西。那么我怎样才能创建这样的表达式呢?

Expression<Func<ORDERS, bool>> filter1 = c => c.ORDER_DETAILS.Any(x => x.PRODUCTS.HEADING.Contains("foo"));

PS:我问过类似的问题,但没有得到正确的答案。所以如果我没猜错的话,我决定从这个方向开始。

有关我的问题的更多信息:( How to filter child collection with linq dynamic )

I'm trying to filter results for user request. For instance you have orders and order details and products is child collection.

When user wants to filter by product I'm getting an error because of No property or field 'PRODUCTS' exists in type 'ICollection1'`

I'm writing my query like this.

var orders = _uow.Repository() .Query() .Where("PRODUCTS.HEADING.ToLower().Contains(\"foo\")") .Include("ORDER_DETAILS") .Include("ORDER_DETAILS.PRODUCTS") .ToList(); So it's not possible to filter child collection like this? Or any way to filter?

最佳答案

是的,可以。我使用的一种方法使用与您的返回类型相同的对象作为搜索过滤器。因此,如果您要搜索“Bill”的客户名称,则将 Order.Customer.Name 设置为 Bill。将该对象传递给方法,然后应用所有适用的搜索。

为此,首先定义可搜索字段的列表:

Field<Order>[] Fields;

通过声明新字段来填充这些内容:

var newField = new Field<Order>(o => o.Customer.Name, true, "Customer Name");

“true”参数意味着它将充当结果的排序字段。

Field 对象包含足够的信息以供稍后生成表达式。它看起来像这样:

public class Field<T>
{
    public Field(Expression<Func<T, object>> field, bool sortField = false, string displayName = null)
    {
        //get & validate member
        MemberExp = field.Body is UnaryExpression ? ((UnaryExpression)field.Body).Operand as MemberExpression
                                                  : (MemberExpression)field.Body;

        Field = MemberExp?.Member;
        if (Field == null) throw new ArgumentException("Field expression is not a member.");

        //set field type
        switch (Field.MemberType)
        {
            case MemberTypes.Property:
                PropertyInfo p = (PropertyInfo)Field;
                FieldType = p.PropertyType;
                break;
            case MemberTypes.Field:
                FieldInfo f = (FieldInfo)Field;
                FieldType = f.FieldType;
                break;
            default:
                throw new Exception("Unsupported member type detected.");
        }

        //store input values
        FieldExpression = field;
        SortField = sortField;
        DisplayName = displayName ?? Field.Name;
    }

    public bool SortField { get; set; }
    public string DisplayName { get; private set; }
    public MemberExpression MemberExp { get; private set; }
    public Expression<Func<T, object>> FieldExpression { get; private set; }
    public Func<T, object> GetValue => FieldExpression.Compile();
    public Type FieldType { get; set; }

    /// <summary>
    /// Gets the full field name, i.e o => o.Customer.CustomerName returns "Customer.CustomerName"
    /// </summary>
    public string UnqualifiedFieldName
    {
        get
        {
            var stringExp = MemberExp.ToString();
            var paramEnd = stringExp.IndexOf('.') + 1;
            return  stringExp.Substring(paramEnd);
        }
    }
}

定义所有可搜索字段后,您将调用一个方法来根据从用户收集的搜索过滤器 (T) 获取搜索结果:

//get the results in ascending order, 10 items per page, first page
var results = GetSearchResults(searchFilters, "ASC", 10, 1);

该方法将要求您拥有可查询的数据集合。我假设您有一些方法,例如 context.GetCollection() 来检索您的数据。 GetSearchResults 方法如下所示:

//Returns a filtered dataset based on provided search filters
//searchFilters is an object T which contains the search filters entered.
private List<T> GetSearchResults(T searchFilters, string sortDir = "ASC", int pageSize, int currentPage)
{
    IQueryable<T> searchResults = context.GetCollection(); //get your data context here

    var filterExpressions = new List<Expression<Func<T, bool>>>();

    //Add filters
    foreach (var field in Fields)
    {
        //try to get the search value, ignoring null exceptions because it's much harder
        //to check for null objects at multiple levels. Instead the exception tells us there's
        //no search value
        string searchValue = null;
        try 
        {
            searchValue = field.GetValue(searchFilters)?.ToString(); 
        }
        catch (NullReferenceException) { }
        if (string.IsNullOrWhiteSpace(searchValue)) continue;

        //shared expression setup
        ParameterExpression param = field.FieldExpression.Parameters.First();
        Expression left = field.FieldExpression.Body;
        ConstantExpression right = Expression.Constant(searchValue);
        Expression body = null;

        //create expression for strings so we can use "contains" instead of "equals"           
        if (field.FieldType == typeof(string))
        {
            //build the expression body
            MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });                    
            body = Expression.Call(left, method, right);
        }
        else
        {   //handle expression for all other types      
            body = Expression.Equal(left, right);
        }

        //finish expression
        Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(body, param);
        filterExpressions.Add(lambda);
    }

    //apply the expressions
    searchResults = filterExpressions.Aggregate(searchResults, (current, expression) => current.Where(expression));

    //get sort field
    Field<T> sortField = Fields.FirstOrDefault(f => f.SortField);
    searchResults = searchResults.OrderBy($"{sortField.UnqualifiedFieldName} {sortDir}");                                                                         

    // Get the search results
    int count = searchResults.Count();
    int maxPage = count / pageSize;
    if (maxPage * pageSize < count) maxPage++;
    if (currentPage > maxPage) currentPage = maxPage;
    int skip = Math.Max(0, (filters.page - 1) * pageSize);
    int display = Math.Max(0, Math.Min(count - skip, pageSize));
    return searchResults.Skip(skip).Take(display).ToList();
}     

此方法使用您的 Field[] 数组为您的条件构建表达式并将其应用到数据集。

希望有帮助!如果您有任何疑问,请告诉我。

关于c# - 如何创建动态 Entity Framework 过滤表达式,如 Expression<Func<T, bool>>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38308354/

相关文章:

c# - 如何只搜索 XDocument 的子项,而不是其所有后代?

c# - 复选框 : Pointer over Checkbox changes style? 的 UWP CustomRenderer

c# - 如何反序列化 XML 中特定元素中属性的特定值?

c# - 如何快速刷新ADO.NET实体数据模型?

entity-framework - EF Code First、IoC 和 DbConnection

LINQ 包含与加入。它们是等价的吗?

linq - 无法将类型 'int?'隐式转换为 'int'

c# - WPF 将祖先对象绑定(bind)为 CommandParameter

c# - 我正在尝试从 HttpRequest 消息中检索输入请求。 HttpRequest 中的内容显示内容类型和长度。我如何得到json?

c# - 参数化查询的糟糕 Dapper 性能