具有多个嵌套属性的Linq表达式

时间:2018-01-29 19:42:31

标签: c# linq asp.net-core linq-expressions expressionbuilder

我已阅读并回答Dynamic linq expression tree with nested properties。它似乎非常相似,虽然表达式缺乏理解导致我无法将答案转换为我自己的场景。

给出一个看起来像这样的类结构:

public class Parent
{
    public Parent()
    {
        ParentField = new HashSet<ParentField>();
    }
    public int ParentId { get; set; }
    public ICollection<ParentField> ParentField { get; set; }
}

public class ParentField
{
    public int ParentField { get; set; }
    public Field Field { get; set; }
    public string Value {get;set;}
}

public class Field
{
    public int FieldId { get; set; }
    public string Name { get; set; }
}

我正在尝试构建一个由此表示的查询:

var query = _unitOfWork.ParentRepository.Queryable().Include("ParentField.Field");
query = query.Where(i =>
    (
        i.ParentField.Any(pField => pField.Field.Name.Equals("anId", StringComparison.OrdinalIgnoreCase)) &&
                i.ParentField.Any(pField => 
                // There may be multiple values to search for, so require the OR between each value
                pField.Value.Equals("10", StringComparison.OrdinalIgnoreCase) || pField.Value.Equals("20", StringComparison.OrdinalIgnoreCase))
    )
    // There may be multiple Names to search for, so require the AND between each Any
    &&
    (
        i.ParentField.Any(pField => pField.Field.Name.Equals("anotherId", StringComparison.OrdinalIgnoreCase)) &&
                i.ParentField.Any(pField => 
                pField.Value.Equals("50", StringComparison.OrdinalIgnoreCase) || pField.Value.Equals("60", StringComparison.OrdinalIgnoreCase))
    ));

需要注意的重要部分是,可以搜索许多“Field.Name”,以及每个组中的多个“值”。

鉴于我不知道从哪里开始,我无法给出我迄今为止尝试过的很多例子。

任何指针都很棒。

2 个答案:

答案 0 :(得分:4)

在这种特殊情况下,不需要构建动态表达式谓词。通过将值放入&&并使用Enumerable.Contains来链接多个Where||,可以实现IEnumerable<string>

对于单个名称/值过滤器,它将是这样的:

var name = "anId".ToLower();
var values = new List<string> { "10", "20" }.Select(v => v.ToLower());
query = query.Where(p => p.ParentField.Any(
    pf => pf.Field.Name == name && values.Contains(pf.Value.ToLower())));

对于多个键/值对:

var filters = new Dictionary<string, List<string>>
{
    { "anId", new List<string> { "10", "20" } },
    { "anotherId", new List<string> { "50", "60" } },
};

foreach (var entry in filters)
{
    var name = entry.Key.ToLower();
    var values = entry.Value.Select(v => v.ToLower());
    query = query.Where(p => p.ParentField.Any(
        pf => pf.Field.Name == name && values.Contains(pf.Value.ToLower())));
}

@ geraphl的答案提出了相同的想法,但没有考虑EF查询提供者的具体要求(没有字典方法,只有原始值列表Contains,没有Equals和{ {1}}使用情况,但StringComparison.OrdinalIgnoreCase==等。)

答案 1 :(得分:2)

我不确定,但我认为你正在寻找类似的东西。

首先,您必须将搜索到的值放在能够执行所需操作的数据类型中,在本例中为字典:

var nameValues = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
        {
            {  "anId" , new string[] {"10", "20"} },
            {  "anotherId" , new string[] {"50", "60"} },
        };

然后首先检查名称是否在字典中,如果是,还要检查列出的值中是否有一个列在您搜索的列表中。

var query = _unitOfWork.ParentRepository.Queryable().Include("ParentField.Field");
query = query.Where(i =>
    (
        i.ParentField.Any(pFieldOuter => nameValues.ContainsKey(pFieldOuter.Field.Name) ?
        i.ParentField.Any(pFieldInner => nameValues[pFieldOuter.Field.Name].Contains(pFieldInner.Value, StringComparer.OrdinalIgnoreCase))
        : false 
    )
));

但是,如果你想要,那么所有你的搜索名称和值都包括在内你必须这样做。

var names = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "anId", "anotherId" };
var values = new List<List<string>>() {
    new List<string> { "10", "20" },
    new List<string> { "50", "60" }
};

var query = _unitOfWork.ParentRepository.Queryable().Include("ParentField.Field");
query = query.Where(i =>
    (
        names.All(n => i.ParentField.Any(pField => pField.Name.Equals(n, StringComparison.OrdinalIgnoreCase))) &&
        values.All(l => l.Any(v => i.ParentField.Any(pField => pField.Value.Equals(v, StringComparison.OrdinalIgnoreCase))))
    ));

即使我确定这不是最有效的方法,但它可能是一个很好的暗示。