从左外联接返回Linq查询内的列表

3duebb1j  于 2022-12-06  发布在  其他
关注(0)|答案(2)|浏览(169)

我是Linq的新手,希望能对某个特定的查询有一些明确的了解。
我有两个表(为了演示而简化):

表:客户

  1. CustomerId | Name
  2. 1 | John Smith
  3. 2 | Peter James

表格:订单

  1. id | CustomerId | Total
  2. 1 | 1 | $100
  3. 2 | 1 | $200

示例客户收件人:

  1. public class CustomerDto
  2. {
  3. public long CustomerId { get; set; }
  4. public string Name{ get; set; }
  5. public CustomerOrder[] CustomerOrderList{ get;set;}
  6. }

Linq example for left outer join,它们返回string.empty如果连接失败,我不确定当我返回一个对象而不是一个字符串时,它们的等价性。
我的Linq查询如下所示。我已经使用DefaultIfEmpty()来帮助左外连接,但是考虑到我正在处理我的对象,我不确定如果什么都没有的话如何返回null。

  1. IQueryable<CustomerDto> search =
  2. from customer in _database.Customer
  3. join customerOrder in _database.CustomerOrder on customer.CustomerId equals customerOrder.CustomerId into CS
  4. from subCustomerSale in CS.DefaultIfEmpty()
  5. select new CustomerDto
  6. {
  7. CustomerId = customer.CustomerId,
  8. Name = customer.Name,
  9. CustomerOrderList = subCustomerSale
  10. };

如前所述,我希望返回一个订单列表,而不是每个订单一行。因此,应该返回两条记录(两个客户),一条包含订单列表,另一条不包含任何订单。
我该如何做到这一点?

pbossiut

pbossiut1#

使实体更易于使用的第一步是确保导航属性已设置。如果表名为“Order”,则实体可以是Order,也可以根据需要重命名为CustomerOrder。Customer实体可以有一个ICollection<Order>集合,如果使用了一致的命名约定和规范化,EF可以自动Map该集合。(否则可以提供显式Map)
例如:

  1. public class Customer
  2. {
  3. [Key]
  4. public int CustomerId { get; set; }
  5. // other customer fields.
  6. public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
  7. }

从这里,您将Customers投影到CustomerDTO,因此我们还应该将Orders投影到OrderDTO。请注意,使用导航属性时,我们不必显式连接实体。我们甚至不必通过Include()立即加载相关数据。如果/当我们希望使用实体而不是投影时,后者将适用。
产生的查询结果如下所示:

  1. IQueryable<CustomerDto> search = _database.Customer
  2. .Select(c => new CustomerDto
  3. {
  4. CustomerId = c.CustomerId,
  5. Name = c.Name,
  6. Orders = c.Orders.Select(o => new OrderDto
  7. {
  8. OrderId = o.OrderId,
  9. Total = o.Total
  10. }).ToList()
  11. });

其优点是不需要显式地编写连接表达式。EF可以帮助大大简化访问相关数据,而不仅仅是方便使用Linq作为SQL的替代。如果该客户没有订单,这将返回一个空列表而不是#null。如果没有任何订单,可能会替换为#null,但最坏的情况是,它可能会在结果具体化后进行后处理,即:

  1. var customers = await search.ToListAsync();
  2. var noOrderCustomers = customers.Where(c => !c.Orders.Any()).ToList();
  3. foreach(var customer in noOrderCustomers)
  4. customer.Orders = null;

实际上,它只是归结为消费者是否知道总是存在一个Orders集合,如果没有订单,该集合将为空,或者只有在有订单时,Orders集合中才存在。(通过JSON等序列化)
需要考虑的重要细节:在进行筛选时,如填写搜索条件、请在Select之前执行此操作,因为IQueryable正在处理实体,因此您对表字段具有完全访问权限。在Select之后添加Where子句会将可用字段限制为您为DTO选择的字段。Select中有一个ToList用于构建Orders集合。但只有当主查询为时才会执行。(例如IQueryable上等待的async操作)
投影到DTO时,请确保不要将DTO和实体混合,例如:

  1. IQueryable<CustomerDto> search = _database.Customer
  2. .Select(c => new CustomerDto
  3. {
  4. CustomerId = c.CustomerId,
  5. Name = c.Name,
  6. Orders = c.Orders
  7. });

...这可能很诱人。这里的问题是“c.Orders”将返回Order实体的集合。这些Orders可能引用了您不需要/不想向使用者公开的其他实体或信息。访问引用可能会导致延迟加载成本、空引用或异常(即已处理的DbContext),具体取决于它们发生的时间/位置。

展开查看全部
e4yzc0pl

e4yzc0pl2#

只需将三元条件设置为如下所示:

  1. IQueryable<CustomerDto> search =
  2. from customer in _database.Customer
  3. join customerOrder in _database.CustomerOrder on customer.CustomerId equals customerOrder.CustomerId into CS
  4. from subCustomerSale in CS.DefaultIfEmpty()
  5. select new CustomerDto
  6. {
  7. CustomerId = customer.CustomerId,
  8. Name = customer.Name,
  9. CustomerOrderList = subCustomerSale == null ? null : subCustomerSale // add this line and you will get the null as well if there is no record
  10. };

相关问题