Entity Framework 具有多个连接条件的查询

Entity Framework Query with multiple join conditions

已编辑 我有表 CustomersSitesBuildingsAddresses.

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

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

最后:每个customer/site/building只有一个地址,即外键Customer.CustomerPhysicalAddressIdSite.AddressIdBuilding.BuildingAddressId所指向的地址。

我也有一个string searchText

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

对于上述要求,我有这个 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%'

控制器 class 在编写 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
                             };
}

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

The type of one of the expression in the join clause is incorrect. Type reference failed in the call to GroupJoin

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

感谢任何宝贵的意见和回答。

所以你有 CustomersSitesBuildingsAddresses 的表格。

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

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

最后:每个Customer/Site/Building都有一个Address,即外键Customer.CustomerPhysicalAddressIdSite.AddressIdBuilding.BuildingAddressId所指的Address。

你还有一个string searchText.

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

  • 类似于 searchText 的 CustomerName
  • 至少一个类似于 searchText
  • 的站点名称
  • 至少一个 BuildingName 类似于 searchText
  • 像搜索文本这样的物理地址
  • 他的所有站点中至少有一个站点地址类似于 searchText
  • 他所有建筑物中至少有一个类似于 searchText 的 BuildingAddress。

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

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

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

创建 [CustomerId, 字符串序列] 组合并不困难。尝试实现“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? with repeat数 1?)

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

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

在上面的解决方案中,我使用了您在 entity framework 中看到的 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 查询可以重写为

,答案可能很明显
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 查询中删除连接。