【问题标题】:Linq Expression with multiple nested properties具有多个嵌套属性的 Linq 表达式
【发布时间】:2018-01-29 19:42:31
【问题描述】:

我已阅读问题并回答 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”,以及每个组内的多个“值”。

鉴于我不确定从哪里实际开始,我无法给出我迄今为止尝试过的很多示例。

任何指针都会很棒。

【问题讨论】:

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


    【解决方案1】:

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

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

    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 使用 StringComparison.OrdinalIgnoreCase,但 ==ToLower 等)

    【讨论】:

    • 呃...有时我只是看不到简单的解决方案。非常感谢!
    • 我建议使用String.IndexOf(x, StringComparison.OrdinalIgnoreCase) !=0(因为它是the only method 在接受比较选项的字符串中搜索子字符串)而不是使用ToLower()
    • @abatishchev 不错,但不能翻译成 SQL。
    • 然后使用纯字符串比较x == y,因为底层 SQL 架构可能不区分大小写。
    • @abatishchev 可能,但不能保证。 ToLower 正在强制(至少尝试)。
    【解决方案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))))
        ));
    

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

    【讨论】:

    • 谢谢。我喜欢这种方法并且充满希望。我正在使用 ASP.Net Core,这会导致错误(减少)NotSupportedException: Could not parse expression __nameValues_1.get_Item(pFieldOuter.Field.Name) This overload of the method is currently not supported.
    • 哦,对不起,我没有看到你在使用核心。我只是尽可能好地模拟你的情况。我刚刚添加了另一部分,我还修复了 pFieldInner 的错误,其中 .Value 丢失。
    猜你喜欢
    • 2017-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多