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

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

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

表:客户

CustomerId | Name
1          | John Smith
2          | Peter James

表格:订单

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

示例客户收件人:

public class CustomerDto
{
    public long CustomerId { get; set; }
    public string Name{ get; set; }
    public CustomerOrder[] CustomerOrderList{ get;set;}
}

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

IQueryable<CustomerDto> search =
            from customer in _database.Customer
            join customerOrder in _database.CustomerOrder on customer.CustomerId equals customerOrder.CustomerId into CS
                from subCustomerSale in CS.DefaultIfEmpty()
            select new CustomerDto
            {
                CustomerId = customer.CustomerId,
                Name = customer.Name,
                CustomerOrderList = subCustomerSale
            };

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

pbossiut

pbossiut1#

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

public class Customer
{
    [Key]
    public int CustomerId { get; set; }

    // other customer fields.

    public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
}

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

IQueryable<CustomerDto> search = _database.Customer
    .Select(c => new CustomerDto
    {
        CustomerId = c.CustomerId,
        Name = c.Name,
        Orders = c.Orders.Select(o => new OrderDto
        {
            OrderId = o.OrderId,
            Total = o.Total
        }).ToList()
    });

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

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

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

IQueryable<CustomerDto> search = _database.Customer
    .Select(c => new CustomerDto
    {
        CustomerId = c.CustomerId,
        Name = c.Name,
        Orders = c.Orders
    });

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

e4yzc0pl

e4yzc0pl2#

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

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

相关问题