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

时间:2020-11-10 02:19:50

标签: c# entity-framework linq sql-to-linq-conversion

已编辑 我有表格CustomersSitesBuildingsAddresses

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

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

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

我还有一个string searchText

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

  • 类似于CustomerName的{​​{1}}
  • 至少一个searchText类似于SiteName
  • 至少一个searchText类似于BuildingName
  • searchText的{​​{1}}
  • 所有PhysicalAddress中至少有一个searchText,例如SiteAddress
  • 所有Sites中至少有一个searchText,例如BuildingAddress

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

Buildings

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

我的Linq查询编写如下

searchText

在结果查询中,我只想让客户ID满足以上条件。在引入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查询可以正常工作。面对要写多个条件的情况,LinqPad显示错误

join子句中的表达式之一的类型不正确。调用GroupJoin

时类型引用失败

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

感谢您提出宝贵的意见和答案。

2 个答案:

答案 0 :(得分:1)

因此,您有CustomersSitesBuildingsAddresses表。

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

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

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

您还有一个string searchText

您希望所有具有以下至少一项的客户的ID:

  • 类似于searchText的CustomerName
  • 至少一个类似于searchText的SiteName
  • 至少一个类似于searchText的BuildingName
  • 诸如SearchText之类的PhysicalAddress
  • 他所有站点中的至少一个SiteAddress,例如searchText
  • 他的所有建筑物中至少有一个建筑物地址,例如searchText。

我的建议是,从每个客户那里获得他的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?重复计数为1?)。

因此,现在您每个客户都有一个ID,以及客户,其站点以及这些站点上的建筑物的名称和地址的一个大序列。您要做的就是为.Where添加一个Like searchText。据我所知,标准的LINQ没有这个功能,但是也许您可以做类似的事情:

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

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

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),

        ...

    

答案 1 :(得分: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查询中删除联接。