C#实体框架:Linq过滤掉某些GrandChild元素

如何解决C#实体框架:Linq过滤掉某些GrandChild元素

如何使用Linq EF查询过滤出孙元素?该客户有多个交易,仅需要带有某些ProductTypeId的子元素。当前,它会带来所有ProductType Id,而忽略过滤器。

var result = db.Customer
        .Include(b => b.Transactions)
        .Where(a => a.Transactions.Select(c=> c.ProductTypeId== productTypeId).Any())

我想要的sql查询:

select distinct c.customerName
from dbo.customer customer
inner join dbo.Transactions transaction
    on transaction.customerid = customer.customerid
where transaction.ProductTypeId= 5


Customer (need 7 ProductTypeId)
    Transaction ProductTypeId 2
    Transaction ProductTypeId 4
    Transaction ProductTypeId 5
    Transaction ProductTypeId 7  <--- only need this 7 in the filter,example
    Transaction ProductTypeId 7  <--- only need this 7 in the filter,example
    Transaction ProductTypeId 8
    Transaction ProductTypeId 8
    Transaction ProductTypeId 9

* Mgmt比SQL更喜欢Include语法,而不是linq。

解决方法

一种更好的方法是使用Transactions dbset从Include Customer开始查询,然后对事务应用过滤器,然后从具有Distinct表达式的结果中选择customerName以获得唯一的客户名。

,

因此,请尝试根据SQL的要求编写查询。

var namesAll = 
    from customer in db.Customer
    from transaction in customer.Transactions
    where transaction.ProductTypeId == 5
    select customer.CustomerName;

var result = namesAll.Distinct();

Lambda语法(方法链),恕我直言,这是最糟糕的可读性。

var result = db.Customer
    .SelectMany(customer => customer.Transactions,(customer,transaction) => new {customer,transaction})
    .Where(pair => pair.transaction.ProductTypeId == 5)
    .Select(pair => pair.customer.CustomerName)
    .Distinct();
,

如果我正确理解您的需求,请尝试以下解决方案:

我的测试模型:

public sealed class Person
{
    public Guid Id { get; set; }
    public DateTime? Deleted { get; set; }
    public string Email { get; set; }
    public string Name { get; set; }
    public int? B { get; set; }

    public IList<Vehicle> Vehicles { get; set; } = new List<Vehicle>();
}

public sealed class Vehicle
{
    public Guid Id { get; set; }
    public int ProductTypeId { get; set; }

    public Guid PersonId { get; set; }
    public Person Person { get; set; }
}

查询:

var queueItem = await _context.Persons
            .Where(item => item.Vehicles.Any(i => i.ProductTypeId == 1))
            .Select(item => new Person
            {
                Id = item.Id,//Other props
                Vehicles = item.Vehicles.Where(item2 => item2.ProductTypeId == 1).ToList()
            })
            .ToListAsync();

探查器中的sql:

SELECT [p].[Id],[t].[Id],[t].[PersonId],[t].[ProductTypeId]
FROM [Persons] AS [p]
LEFT JOIN (
   SELECT [v].[Id],[v].[PersonId],[v].[ProductTypeId]
   FROM [Vehicle] AS [v]
   WHERE [v].[ProductTypeId] = 1
) AS [t] ON [p].[Id] = [t].[PersonId]
WHERE EXISTS (
   SELECT 1
   FROM [Vehicle] AS [v0]
   WHERE ([p].[Id] = [v0].[PersonId]) AND ([v0].[ProductTypeId] = 1))
ORDER BY [p].[Id],[t].[Id]

另一个变体:

      var queueItem1 = await _context.Vehicle
        .Where(item2 => item2.ProductTypeId == 1)
        .Include(item => item.Person)
        .Distinct()
        .ToListAsync();
            
       var list = queueItem1
        .GroupBy(item => item.Person)
        .Select(item => new Person
        {
            Id = item.First().Person.Id,//Other props
            Vehicles = item.ToList()
        })
        .ToList();

探查器中的sql:

SELECT [t].[Id],[t].[ProductTypeId],[p].[Id],[p].[B],[p].[Deleted],[p].[Email],[p].[Name]
FROM (
   SELECT DISTINCT [v].[Id],[v].[ProductTypeId]
   FROM [Vehicle] AS [v]
   WHERE [v].[ProductTypeId] = 1
 ) AS [t]
INNER JOIN [Persons] AS [p] ON [t].[PersonId] = [p].[Id]
,

要过滤相关实体,您需要使用投影。 EF实体图的目的是反映 complete 数据状态。您想要的是过滤后的数据状态。通常这是为了向视图提供相关数据。那是一个单独的目的。

给出一个Customer / Transaction实体,使用一个Customer / Transaction ViewModel,其中仅包含PK和视图/消费者所需的属性。例如:

[Serializable]
public class CustomerViewModel
{
    public int CustomerId { get; set; }
    public string Name { get; set; }
    // ...
    public ICollection<TransactionViewModel> ApplicableTransactions { get; set; } = new List<TransactionViewModel>();
}

[Serializable]
public class TransactionViewModel
{
    public int TransactionId { get; set; }
    // ...
}

然后,当您加载客户和经过过滤的交易时:

var result = db.Customer
    .Where(a => a.Transactions.Select(c=> c.ProductTypeId== productTypeId).Any())
    .Select(a => new CustomerViewModel
    {
        CustomerId = a.CustomerId,Name = a.Name,// ...
        ApplicableTransactions = a.Transactions
            .Where(c => c.ProductTypeId == productTypeId)
            .Select(c => new TransactionViewModel
            {
                TransactionId == c.TransactionId,// ...
            }).ToList();
   }).ToList();

利用Automapper进行投影可以大大简化此过程,因为您可以配置实体以查看模型映射(如果字段命名相同,则为单行),然后调用ProjectTo,Automapper将解析这些字段SQL所需并为您构建视图模型:

var mappingConfig = new MapperConfiguration(cfg => 
{
    cfg.CreateMap<Customer,CustomerViewModel>()
        .ForMember(dest => dest.ApplicableTransactions,opt => opt.MapFrom(src => src.Transactions.Where(t => t.ProductTypeId == productTypeId)
        ));
    cfg.CreateMap<Transaction,TransactionViewModel>();
});

var result = db.Customer
    .Where(a => a.Transactions.Select(c=> c.ProductTypeId== productTypeId).Any())
    .ProjectTo<CustomerViewModel>(mappingConfig)
    .ToList();

对于视图模型,我将使用命名约定来反映您为其提供的视图,因为它们实际上仅适用于服务该视图。例如,如果这是按客户检查交易,则类似ReviewTransactionsCustomerViewModel或ReviewTransactionsCustomerVM。不同的视图可以提供不同的视图模型,而试图使所有视图都适合。

或者,如果您的代码已经在向视图发送实体(我强烈建议不要这样做),则有两种选择,但是它们确实有缺点:

  1. 使用具有过滤子集的包装器视图模型:

例如:

[Serializable] 
public class ReviewTransactionsViewModel
{
    public Customer Customer { get; set; }
    public ICollection<Transaction> ApplicableTransactions { get; set; } = new List<Transaction>();
}

然后在选择时:

var result = db.Customer
    .Where(a => a.Transactions.Select(c=> c.ProductTypeId== productTypeId).Any())
    .Select(a => new ReviewTransactionsViewModel
    {
        Customer = a,ApplicableTransactions = a.Transactions
            .Where(c => c.ProductTypeId == productTypeId)
            .ToList();
   }).ToList();

然后在您的视图中,而不是@Model成为客户,它变为该视图模型,您只需要调整所有引用以使用Model.Customer.{property}而不是Model.{property},重要的是,对Model.Transactions应该更新为Model.ApplicableTransactions,而不是Model.Customer.Transactions

此方法的警告是,为了提高性能,应在DbContext实例上禁用延迟加载,以填充模型以发送回去,并且仅渴望加载视图所需的数据。延迟加载将被代码序列化实体触发,以发送到视图,这很容易成为主要的性能损失。这意味着对Model.Customer.Transactions的任何引用都将为空。这也意味着您的模型将不代表完整的实体,因此,当将此模型传递回控制器时,您需要了解这一事实,而不是尝试将其附加为完整的实体或传递给期望完整的方法实体。

  1. 将数据过滤到新实体中:(将实体作为视图模型处理)

例如:

var result = db.Customer
    .Where(a => a.Transactions.Select(c=> c.ProductTypeId== productTypeId).Any())
    .Select(a => new Customer
    {
        CustomerId = a.CustomerId,// ... Just the values the view will need.
        Transactions = a.Transactions
            .Where(c => c.ProductTypeId == productTypeId)
            .ToList();
   }).ToList();

这可能是一个有吸引力的选项,因为它不需要更改使用方视图,但是您必须谨慎,因为当/如果传递回控制器或任何可能假设提供的客户是完整的代表或跟踪的实体。我相信您可以利用<Customer,Customer>的Automapper配置来帮助简化仅适用列之间的过滤和复制,而忽略不需要的相关实体等。

无论如何,这应该为您提供一些选择来权衡风险与努力。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res