C# LINQ 嵌套查询

4
我有一个场景需要嵌套处理。
--Orders (List) 
 ----Products (List)
 ------Manufacturers (List) 
       FIELDS 
        -Name
        -Address
        -City 

在这种情况下,我需要执行查询,该查询将根据制造商城市进行过滤,并返回订单、产品和仅匹配城市制造商
我尝试使用以下查询,但即使城市与制造商不匹配,我仍然得到了所有产品的列表。
var filteredOrders = from o in Orders
                    from t in o.Products                           
                    where t.Manufacturers.Any(v => v.City == "Hartford")
                    select o;

即使我将select o更改为 'select t.Manufacturers',我也会得到所有制造商列表,而不考虑城市过滤器。

幸运的是,我找到了适合我的场景的W3school SQL示例。 https://www.w3schools.com/sql/trysql.asp?filename=trysql_op_or

SQL查询:

SELECT o.OrderId, p.ProductName, s.* 
FROM [Orders] o 
JOIN OrderDetails od ON o.OrderId = od.OrderId AND o.orderId = 10248 
JOIN Products p ON od.ProductId = p.ProductId
JOIN Suppliers s ON p.SupplierId = s.SupplierId and s.City ='Singapore'    

p.Products 中的 p 是什么意思? - shole
@AviKenjale,假设订单有3个产品,每个产品有3个制造商,只有一个制造商的城市匹配,您只想选择特定的订单、产品和制造商,而不是所有3个产品/3个制造商,对吗? - shole
@shole,你搞定了! - Avi Kenjale
你正在使它变得不必要的困难。只需使用这个 where t.Manufacturers.City == "Hartford" - Hasan Emrah Süngü
1
@EmrahSüngü 我非常确定Manufactures是一个集合,因此它没有City属性可供过滤。 - juharr
显示剩余7条评论
4个回答

5
我会将所有内容展平,然后只过滤你想要的城市:
class Manufacturer
{
    public string Name;
    public string Address;
    public string City;
}

class Product
{
    public Manufacturer[] Manufacturers;
}

class Order
{
    public Product[] Products;
}

static void Main(string[] args)
{
    var cities = new string[] { "a", "b" };
    Order[] orders = null;
    orders.SelectMany(o => o.Products.SelectMany(p => p.Manufacturers.Select(m => new { o, p, m })))
        .Where(g => cities.Contains(g.m.City))
        .ToList();
    }

如果您想返回新的订单(因为它们有不同的产品),则必须指向一个新分配的对象。您可以使用以下代码:

var newOrders = orders.Select(o => new Order()
{
    Products = o.Products
    .Select(p => new Product()
    {
        Manufacturers = p.Manufacturers.Where(m => cities.Contains(m.City)).ToArray()
    })
    .Where(m => m.Manufacturers.Length > 0).ToArray()
}).Where(p => p.Products.Length > 0).ToArray();

我部分同意你的建议。然而,我不想创建新对象。我只想获取那些来自匹配城市制造商的产品订单。我的意思是,如果我将其与SQL语句相关联,我可以轻松地获取匹配记录。 此外,在您的建议中,如果我扩展'o'和'p',它们就是默认列表中的原样(未经过滤)。 - Avi Kenjale
1
@AviKenjale 我已经编辑了这篇文章。你不能期望Order对象因为你的过滤而改变。你必须要么“删除”项目,要么创建一个新的对象。这有点像byrefbyval - tafia

1
我最终尝试将所有内容整合在一起,并获得了预期的输出结果。
var fp = orders.Select(o =>
            {
                o.products = o.products.Select(p =>
                {
                    p.manufacturers.RemoveAll(m => m.City != "Hartford");
                    return p;
                }).ToList();

                return o;
            });

请建议如果有更好的解决方案。

0

你的城市筛选器应用有误,问题就在这一行。

where t.Manufacturers.Any(v => v.City == "Hartford")

Any返回true,至少有一个制造商的City属性为“哈特福德”,因此基本上您的查询类似于这样

var filteredOrders = from o in Orders
                from t in o.Products                           
                where true//←This is the problem
                select o;

你需要做的实际上是

where t.Manufacturers.City == "Hartford"

希望这能有所帮助

例子:

var cityNames = new List<string> {"New York",
                                  "Atlanta",
                                  "Hartford",
                                  "Chicago"
                                  };
var anyResult = cityNames.Any(x=>x== "Hartford"); //TRUE
var whereResult = cityNames.Where(x => x == "Hartford"); //IEnumerable<string>, in this case only one element

@AviKenjale,很抱歉您是错的。Where 不返回布尔值。它所做的是基于您提供的谓词对元素进行过滤,在这种情况下,where t.Manufacturers.City == "Hartford"。 - Hasan Emrah Süngü
我的意思是,Where方法返回符合布尔条件的集合。 - Avi Kenjale
@AviKenjale,没错,Any不是这样的。Any的作用是返回true或false,如果至少有一个元素与条件匹配,请参考我的答案以更好地理解或直接使用它来获得可工作的代码。 - Hasan Emrah Süngü

0

我无法想到一种完全避免创建新对象的方法,因为父对象的列表属性不能直接进行过滤。不过你可以利用同一个类。

此外,我使用了两个单独的查询来在父/祖父对象中创建新列表。

我制作了一个小演示来展示这个想法(下面有等效代码): http://ideone.com/MO6M6t

我尝试选择的城市是"tmp",它只属于父级p3,而父级p3又只属于祖父级g1g3

预期输出为:

g1
    p3
        tmp

g3
    p3
        tmp

using System;
using System.Collections.Generic;
using System.Linq;

public class Test
{
    public class GrandParent{
        public List<Parent> parentList{ get; set; }
        public string name{ get; set; }
        public GrandParent(string name){
            this.name = name;
            this.parentList = new List<Parent>();
        }
    }
    public class Parent{
        public List<Child> childList{ get; set;}
        public string name{ get; set; }
        public Parent(string name){
            this.name = name;
            this.childList = new List<Child>();
        }
    }
    public class Child{
        public string city{ get; set;}
        public Child(string city){
            this.city = city;
        }
    }
    public static void Main()
    {
        Child c1 = new Child("ABC"), c2 = new Child("123"), c3 = new Child("tmp");
        Parent p1 = new Parent("p1"), p2 = new Parent("p2"), p3 = new Parent("p3");
        GrandParent g1 = new GrandParent("g1"), g2 = new GrandParent("g2"), g3 = new GrandParent("g3");

        p1.childList.Add(c1); p1.childList.Add(c2); 
        p2.childList.Add(c2); 
        p3.childList.Add(c3);

        g1.parentList.Add(p1); g1.parentList.Add(p2); g1.parentList.Add(p3);
        g2.parentList.Add(p2);
        g3.parentList.Add(p3);

        List<GrandParent> repo = new List<GrandParent>{g1, g2, g3};

        var filteredParents = from g in repo
                              from p in g.parentList
                              where p.childList.Any(c => c.city == "tmp")
                              select new Parent(p.name){
                                 childList = p.childList.Where(c => c.city == "tmp").ToList()
                              };

        var filteredGrandParents = from g in repo
                                   from p in g.parentList
                                   where filteredParents.Any(fp => fp.name == p.name)
                                   select new GrandParent(g.name){
                                       parentList = g.parentList.Where(pp => filteredParents.Any(fp => fp.name == pp.name)).ToList()
                                   };

        foreach(var g in filteredGrandParents){
            Console.WriteLine(g.name);
            foreach(var p in g.parentList){
                Console.WriteLine("\t" + p.name);
                foreach(var c in p.childList){
                    Console.WriteLine("\t\t" + c.city);
                }
            }
            Console.WriteLine();
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接