具有多个连接条件的实体框架查询

Posted

技术标签:

【中文标题】具有多个连接条件的实体框架查询【英文标题】:Entity Framework Query with multiple join conditions 【发布时间】:2020-11-10 02:19:50 【问题描述】:

已编辑 我有表CustomersSitesBuildingsAddresses

每个客户都有零个或多个(一个?)站点,每个站点都是一个客户的站点,即外键 Site.CustomerId 所指的站点。

同样,每个站点都有零个或多个建筑物,每个建筑物都在一个站点上,即外键 Building.SiteId 所指的站点。

最后:每个客户/站点/建筑物只有一个地址,即外键Customer.CustomerPhysicalAddressIdSite.AddressIdBuilding.BuildingAddressId所指的地址。

我还有一个string searchText

我想要至少具有以下一项的所有客户的 ID:

CustomerName 类似于 searchText 至少有一个SiteName 类似于searchText 至少有一个BuildingName,类似于searchText PhysicalAddress 喜欢 searchText 所有Sites 中的至少一个SiteAddress 类似于searchText 所有Buildings 中的至少一个BuildingAddress 类似于searchText

对于上述要求,我有这个 SQL 查询逻辑

SELECT DISTINCT c.customerID 
FROM Customer AS c
LEFT OUTER JOIN Site AS s ON s.customerId = c.customerID
LEFT OUTER JOIN Building AS b ON s.Id = b.siteId
LEFT OUTER JOIN Address AS A ON A.addressId = c.customerPhysicalAddressID 
                             OR A.addressId = s.AddressId 
                             OR A.addressId = b.buildingAddressId
WHERE 
    c.customerName LIKE '%searchText%' 
    OR c.SiteName LIKE '%searchText%' 
    OR b.buildingName LIKE '%searchText%' 
    OR A.Street LIKE '%searchText%'

在编写 linq 查询时,控制器类出现问题。

我的 Linq 查询是这样写的

if (!string.IsNullOrEmpty(searchText))

    var resultQuery = from customer in this.DatabaseContext.Customers
                      join site in this.DatabaseContext.Sites
                           on customer.customerID equals site.CustomerId into customer_site_group
                      from customer_site in customer_site_group.DefaultIfEmpty()
                      join building in this.DatabaseContext.Buildings
                           on customer_site.Id equals building.siteId into site_building_group
                      from site_building in site_building_group.DefaultIfEmpty()
                      join A in this.DatabaseContext.Addresses
                             on new
                               
                                   key1 = customer.customerPhysicalAddressID,
                                   key2 = customer_site.AddressId,
                                   key3 = site_building.buildingAddressID
                               
                               equals new
                               
                                    key1 = A.addressID ||
                                    key2 = A.addressID ||
                                    key3 = A.addressID
                                into Address_site_building
                      where (customer.customerName.Contains(searchText) ||
                             customer_site.siteName.Contains(searchText) ||
                             site_building.buildingName.Contains(searchText) ||
                             A.street.Contains(searchText))
                      select new
                             
                                   customerID = customer.customerID
                             ;

在结果查询中,我只想让客户 ID 满足上述条件。在引入Addresses 实体之前,linq 查询工作正常。面对写入多个on条件,LinqPad报错

连接子句中的表达式之一的类型不正确。调用 GroupJoin 时类型引用失败

我是 EF 和 linq 的新手 - 只是尝试并理解它。

感谢任何有价值的 cmets 和答案。

【问题讨论】:

为什么卡住了?它不做你想做的事吗?你有编译器错误吗? 首先,使用customer.Sitessite.Buildings、...等导航属性,然后只需使用from address in Addresses where ... || ... || ... @GertArnold 你能详细说明一下吗? 【参考方案1】:

如果您看到 SQL 查询可以重写为,答案可能很明显

SELECT DISTINCT c.customerID 
FROM Customer AS c
LEFT OUTER JOIN Site AS s ON s.customerId = c.customerID
LEFT OUTER JOIN Building AS b ON s.Id = b.siteId
, Address AS A
WHERE 
    (c.customerName LIKE '%searchText%' 
    OR c.SiteName LIKE '%searchText%' 
    OR b.buildingName LIKE '%searchText%' 
    OR A.Street LIKE '%searchText%')
AND (A.addressId = c.customerPhysicalAddressID 
    OR A.addressId = s.AddressId
    OR A.addressId = b.buildingAddressId)

即连接变成了WHERE 子句。然后 LINQ 翻译就变成了类似

from customer in this.DatabaseContext.Customers
join site in this.DatabaseContext.Sites
    on customer.customerID equals site.CustomerId into customer_site_group
from customer_site in customer_site_group.DefaultIfEmpty()
join building in this.DatabaseContext.Buildings
    on customer_site.Id equals building.siteId into site_building_group
from site_building in site_building_group.DefaultIfEmpty()
from A in this.DatabaseContext.Addresses
where (customer.customerPhysicalAddressID = A.addressID
       || customer_site.AddressId = A.addressID
       || site_building.buildingAddressID = A.addressID)
where (customer.customerName.Contains(searchText) ||
     customer_site.siteName.Contains(searchText) ||
     site_building.buildingName.Contains(searchText) ||
     A.street.Contains(searchText))
select new

    customerID = customer.customerID
;

一般建议(请参阅我的评论):尝试通过引入导航属性从 LINQ 查询中删除连接。

【讨论】:

我只是从您的代码中复制了它,所以不知道为什么它不应该工作。也许street 不是字符串而是实体?【参考方案2】:

所以你有CustomersSitesBuildingsAddresses的表格。

每个客户都有零个或多个(一个?)站点,每个站点都是一个客户的站点,即外键 Site.CustomerId 所指的站点。

同样,每个站点都有零个或多个建筑物,每个建筑物都在一个站点上,即外键 Building.SiteId 所指的站点。

最后:每个客户/站点/建筑物都只有一个地址,即外键Customer.CustomerPhysicalAddressIdSite.AddressIdBuilding.BuildingAddressId 所指的地址。

您还有一个string searchText

您需要至少具有以下一项的所有客户的 ID:

类似于 searchText 的 CustomerName 至少有一个 SiteName 类似于 searchText 至少有一个 BuildingName 类似于 searchText 像 searchText 这样的 PhysicalAddress 他所有站点中的至少一个 SiteAddress 类似于 searchText 他的所有建筑物中至少有一个 BuildingAddress 类似于 searchText。

我的建议是,从每个客户那里获取他的 ID,以及包含以下字符串的序列:

客户的姓名 他的实际地址 他所有站点和建筑物的名称 他所有站点和建筑物的地址

结果是 [CustomerId,字符串序列] 的序列。您只想保留那些 CustomerId,其中“字符串序列”中至少有一个字符串类似于 searchText。

创建[CustomerId, sequence of strings] 组合并不难。尝试实现“like searchText”时会遇到问题。

让我们首先创建组合。

当您有“项目及其子项目”并且您想将它们视为一个项目序列时,请考虑使用Queryable.SelectMany 的重载之一。

var result = dbContext.Customers.SelectMany(customer => customer.Sites,

// parameter resultSelector: take every Customer with its Site to create one new:
(customer, sitesOfThisCustomer) => new

    Id = customer.Id,

    // the searchTexts: the customer name, his physical address
    // the names and address of of all his Sites
    // and the names and addresses of all the building of each side (inner SelectMany)
    SearchTexts = new string[] customer.CustomerName, customer.PhysicalAddress

    .Concat (sitesOfThisCustomer.SelectMany(site => site.Buildings,
    (site, buildingsOfThisSite) => new string[] site.SiteName, site.SiteAddress
        .Concat(buildingsOfThisSite.SelectMany(building => new string[]
            building.BuildingName, building.BuildingAddress)));

我不确定new string[] ... 是否与 IQueryable 一起使用,如果没有,请考虑另一种方法来制作具有名称和地址的可枚举序列(Enumerable.Repeat?重复计数为 1?)

因此,现在您拥有每位客户的 ID 以及客户、他的站点和这些站点上的建筑物的名称和地址的大序列。您所要做的就是为Like searchText 添加.Where。据我所知,标准 LINQ 没有这个,但也许你可以这样做:

.Where(customeWithSearchTexts => customerWithSearchTexts.SearchTexts
    .Any(text => text.StartsWith(searchText));

在上面的解决方案中,我使用了您在实体框架中看到的virtual ICollection<...>。如果你不能使用它,因为你的班级没有这个,你必须自己做组加入:

var result = customers.SelectMany(

    // the Sites of this customre
    customer => dbContext.Sites.Where(site.CustomerId == customer.Id),

    // resultSelector:
    (customer, sitesOfThisCustomer) => ...

        // inner selectmany
        site.SelectMany(dbContext.Buildings.Where(building.SiteId == site.Id),

        ...

    

【讨论】:

以上是关于具有多个连接条件的实体框架查询的主要内容,如果未能解决你的问题,请参考以下文章

具有多个可能连接(或连接中的条件)的 SQL 查询

在具有分页的单个模型查询代码点火器中获取具有多个条件的多个连接结果

具有多个连接的巨大查询与多个单个查询

实体框架,多个 edmx 共享连接字符串 - 可能吗?

MySQL:连接查询

实体框架 - 将表拆分为具有重叠条件的多个实体