【问题标题】:LINQ - Get records from one table, with second table multi record matchingLINQ - 从一个表中获取记录,第二个表多记录匹配
【发布时间】:2021-11-26 15:03:08
【问题描述】:

我正在开发一个系统,在该系统中,我获得了一个没有关系的 SQL 数据库(请不要让我开始这样做)。

我有一张表是银行账户、ID、排序代码、帐号、姓名。 我的第二个表是支付表,它有 6 个字段,用于帐号和排序代码,但我只需要匹配一对(排序代码和帐号)

所以,我有一个查询,可以像这样获取所有银行账户

var bankAccounts =
            _databaseContext.BankAccounts
                .Where(accounts => model.BankAccountIds
                    .Any(x => x == accounts.Id))
                .ToList();

我正在构建一个查询并且

_databaseContext.Payments.Where(x => bankAccounts.Any(b => b.AccountNumber == x.AccountNumber) 
                                  && bankAccounts.Any(b => b.SortCode == x.SortCode));

但是,当我运行它时,我得到了错误

ystem.InvalidOperationException:LINQ 表达式 'DbSet() .Where(p => bankAccounts_0 .Any(b => b.AccountNumber == p.AccountNumber) && bankAccounts_0 .Any(b => b.SortCode == p.SortCode))' 无法翻译。以可翻译的形式重写查询,或通过插入对“AsEnumerable”、“AsAsyncEnumerable”、“ToList”或“ToListAsync”的调用显式切换到客户端评估。请参阅https://go.microsoft.com/fwlink/?linkid=2101038 了解更多信息。

所以,我需要的是能够以这样一种方式编写查询,它可以让我获得所有付款,其中付款表中的排序代码和帐号与银行帐户表匹配

而且我确实意识到,如果我们在表之间建立关系,这会简单得多,但是作为承包商,我对他们如何构建事物几乎没有影响

-- 编辑

OnModelCreating 的 Datacontext 中,我也在这样做,但是当播种数据时,它会死掉 - 现在只是构建以获得它抛出的异常

 modelBuilder.Entity<BankAccount>()
            .HasMany(payment => payment.Payments)
            .WithOne(bankAccount => bankAccount.BankAccountDetails)
            .HasPrincipalKey(x => new {x.AccountNumber, x.SortCode});

然后在运行代码时我得到了这个

System.InvalidOperationException:无法跟踪“BankAccount”类型的实体,因为备用键属性“AccountNumber”为空。如果备用键未在关系中使用,则考虑改用唯一索引。唯一索引可能包含空值,而备用键可能不包含。

【问题讨论】:

  • 即使数据库中没有关系(假设您的意思是整个数据库中没有 FK),您仍然可以在客户端配置导航,以便 EF 知道如何进行连接.. 还想指出,您使用两个 Any 编写的查询不是一对值的连接。 “存在与支付账户匹配的任何银行账户账户并且存在与支付排序代码匹配的任何银行账户排序代码的支付”在上下文中可能只是“真”
  • 你试过Contains,而不是Any吗?
  • @JohnDoe,是的,那时我得到了编译错误
  • @CaiusJard 我也试过了,但确实给我带来了一些问题,因为并非所有字段在支付表中都有值
  • 也试过什么?

标签: c# linq entity-framework-core


【解决方案1】:

你尝试做的相当于ID in (1,2,3,6,..),。要获得该代码必须使用Contains,而不是Any

无法将 List.Any(x.id) 转换为 SQL。

  • 首先,T-SQL 中没有列表或数组,因此 EF Core 无法将该数组发送到服务器。
  • 其次,bankAccounts 包含复杂的对象,而不是值。 EF Core 必须生成包含所有相关字段的表类型并将其发送到服务器以在子查询中使用。

在 T-SQL 中我们会这样写:

Select * 
From Payments
Where AccountNumber in (....) AND SortCode in (...)

要在 LINQ 中做同样的事情,我们需要 Contains。要使用它,值列表应包含单个简单值:

var accNumbers=bankAccounts.Select(b => b.AccountNumber).ToList();
var sortCodes=bankAccounts.Select(b => b.SortCode).ToList();

var payments = _databaseContext.Payments.Where(x =>
        accNumbers.Contains(x.AccountNumber) 
        && sortCodes.Contains(x.SortCode));

【讨论】:

  • 我有类似的东西,但现在会试试这个
  • 返回 0 个结果,我应该有数百条记录返回
  • 您确定条件正确吗?您在问题中发布的查询会在这些列表中查找同时具有帐号排序代码的付款。如果您希望其中任何一个匹配,您需要使用||。如果其中一个可以为 NULL,则需要明确检查。 NULL IN (...) 总是会失败
  • 我需要同时匹配两个,而不仅仅是一个
  • 要使代码必须使用包含,而不是任何。 - 我不同意:i.stack.imgur.com/GgEVb.png
【解决方案2】:

新版本,在 cmets 中澄清后: 我认为您可以将其作为一个查询来执行,而不是先具体化 bankAccounts -list,也许可以尝试这样的一些:

_databaseContext.Payments
    .Where(p=>_databaseContext.BankAccounts.Where(accounts => model.BankAccountIds.Contains(accounts.Id) && accounts.AccountNumber==p.AccountNumber  && accounts.SortCode==p.SortCode ))

我认为问题在于 LINQ 无法将 bankAccounts 列表转换为 SQL,尝试将 AccountNumbers 和 SortCode 列表设置为 List&lt;string&gt; 并执行以下操作:

var bankAccounts =
            _databaseContext.BankAccounts
                .Where(accounts => model.BankAccountIds.Contains(accounts.Id))
                .ToList();

var accountNumbers=bankAccounts.Select(x=>x.AccountNumber).ToList();
var sortCodes=bankAccounts.Select(x=>x.SortCode).ToList();

_databaseContext.Payments.Where(x => accountNumbers.Contains(x.AccountNumber) && sortCodes.Contains(x.SortCode));

【讨论】:

  • 不,我想你已经提取了银行账户,因为你有一个 ToList()。
  • 您知道这并没有在帐号和分类代码之间建立任何关联吗?
  • @CaiusJard - 是的,和问题中的一样
  • 但是,这不是我们要求的......
  • 希望澄清一下,付款表有帐号和分类代码,银行账户也有相同的。我需要在这两个条件上进行匹配才能带回所需的数据。
【解决方案3】:

实际上,您正在寻找这样的 SQL:

SELECT *
FROM Payments p
WHERE 
  EXISTS(
    SELECT null 
    FROM BankAccounts ba
    WHERE  
      ba.BankAccountId IN (some,list,of,guids,from,your,model) AND 
      ba.AccountNumber = p.AccountNumber AND
      ba.SortCode = p.SortCode
  )

您首先下载了银行账户(ToList()ed 他们)实际上削弱了 EF 执行此操作的能力;它再也看不到创建协调子查询的方法,因为它不再是基于数据库的集合,而是来自客户端的集合。据我所知,EF 不支持从复杂的客户端提供的数据构建值集并加入它的概念。

如果您将“这些特定银行帐户”操作保留为基于数据库的可枚举项,您的 EF 可能能够将其转换为类似上述内容:

_databaseContext.Payments.Where(pay => 
    _databaseContext.BankAccounts.Where(
      ba => model.BankAccountIds.Any(mba => mba == ba.Id)
    ).Any(ba => 
        ba.AccountNumber == pay.AccountNumber &&
        ba.SortCode == pay.SortCode
    )
);

.Any(mba =&gt; mba == ba.Id) 将转换为 IN,只要它真的是一个简单的道具;您也可以使用model.BankAccountIds.Contains(ba.Id),但无论哪种情况,模型都需要一个银行账户 guid 列表才能翻译此限制。 .Any(ba =&gt; 可以转换为协调的EXISTS

即使它确实有效,我会说你有一些数据建模问题需要解决 - 必须编写这样一个完整的客户端应用程序是一件令人头疼的事情..


您注意到“它不起作用”,但在我的测试中(我可以让它们尽可能接近您发布的场景)根本没有任何问题:

您可以在左侧看到我的表格:BankAccounts 和 Payments。他们都有 AccountNumber 和 SortCode,它们从来都不是 PK,他们没有 FK,付款 Ac/Sort 可能为空。中间是我推荐给你的查询,上面唯一的骗局是我做了一些类似于我假设你的模型的东西:一个根对象,它包含一个名为 BankAccountIds 的属性,它是一个 guid 的集合。在右侧,您可以看到生成的查询;它与最初发布的 SQL 完全相同。我使用 EF Core 5

检查我设置的内容与您拥有的内容有何不同。发布您的模型。发布不起作用的确切查询

【讨论】:

  • 抱歉昨天没回你,只好匆匆赶路了……我早上第一件事就试试,谢谢
  • 从来没有问题!
  • 仍然给我同样的错误。我有办法做到这一点,通过检查每个银行账户,然后一对一匹配,但这不是我喜欢的方式。我会在公司工作,让他们看看我们是否可以在数据结构中添加更多内容。
  • 那么一定是其他事情,因为我刚刚设置了与您的设置相同的东西,并且工作正常。查看编辑
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-11
  • 2019-12-05
  • 1970-01-01
相关资源
最近更新 更多