EF 将多个表合并为一个 IQueryable

Posted

技术标签:

【中文标题】EF 将多个表合并为一个 IQueryable【英文标题】:EF Combine several tables into one IQueryable 【发布时间】:2016-09-26 06:22:32 【问题描述】:

我正在使用 OData(WebAPI 和 EF)来查询数据库。现在我必须将三个表“合并”成一个结果。

我有 3 个表和一个界面,如下所示:

public class Authority : IAssociationEntity

    public string Name  get; set; 
    public int AuthorityId  get; set; 


public class Company : IAssociationEntity

    public string Name  get; set; 
    public int CompanyId  get; set; 


public class Organization : IAssociationEntity

    public string Name  get; set; 
    public int OrganizationId  get; set; 


public interface IAssociationEntity

    string Name  get; set; 

如您所见,这三个表之间有一些明显的相似之处,但由于某些原因,它们需要放在不同的表中。我需要的是使用分页并按名称搜索所有三个,并将它们显示在同一个列表中以供用户使用。

我正在寻找在 SQL 中看起来像这样的东西

SELECT TOP 4 a.* FROM
(
    SELECT CompanyID, Name from Company WHERE Name = 'Bob'
    UNION
    SELECT OrganizationID, Name from Organization WHERE Name = 'Bob'
    UNION
    SELECT AuthorityID, Name from Authority WHERE Name = 'Bob'
) AS a

有没有办法将三个表合并为一个IQueryable

我想将这三个表合并成一个IQueryable<IAssociationEntity>。我确实需要使用接口(或可能是基类)并将结果作为IQueryable 用于我的 OData 实现。像这样的东西,但它不编译:

var query = db.Companies
    .Concat(db.Organizations)
    .Concat(db.Authorities);
IQueryable<IAssociationEntity> mergedTables = query.Cast<IAssociationEntity>();

// Here is an EXAMPLE usage.
// What I really need is to return the IQueryable<IAssociationEntity> for my OData.
var result = mergedTables.Where(x => x.Name == "Bob").OrderBy(x => x.Name).Skip(2).Take(10);

还有我对 odata 控制器的使用:

public class AssociationController : ODataController

    [EnableQuery]
    public override IQueryable<IAssociationEntity> Get(ODataQueryOptions<IAssociationEntity> q)
    
        // return my IQueryable here...
    

不用说,我不想在创建IQueryable 时将整个表读入内存。实际上我确实需要使用分页,因为这三个表中有几个有数百万行。

最终解决方案如下:

  var query = db.Companies.Select(x => new AssociationEntity  Name = x.Name )
.Concat(db.Organizations.Select(x => new AssociationEntity  Name = x.Name ))
.Concat(db.Authorities.Select(x => new AssociationEntity  Name = x.Name ));
    return query;

当对可查询对象执行时:

_query.Where(x => x.Name.Contains("M")).OrderBy(x => x.Name).Skip(10).Take(50).ToList();

生成的 SQL:

SELECT 
    [UnionAll2].[C1] AS [C1], 
    [UnionAll2].[Name] AS [C2]
    FROM  (SELECT 
        1 AS [C1], 
        [Extent1].[Name] AS [Name]
        FROM [dbo].[Company] AS [Extent1]
        WHERE [Extent1].[Name] LIKE N'%M%'
    UNION ALL
        SELECT 
        1 AS [C1], 
        [Extent2].[Name] AS [Name]
        FROM [dbo].[Organization] AS [Extent2]
        WHERE [Extent2].[Name] LIKE N'%M%'
    UNION ALL
        SELECT 
        1 AS [C1], 
        [Extent3].[Name] AS [Name]
        FROM [dbo].[Authority] AS [Extent3]
        WHERE [Extent3].[Name] LIKE N'%M%') AS [UnionAll2]
    ORDER BY [UnionAll2].[Name] ASC
    OFFSET 10 ROWS FETCH NEXT 50 ROWS ONLY 

【问题讨论】:

首先想到的是分别查询所有三个,然后合并结果。分页、排序等,如何跨多个表完成? 你能告诉我为什么会有这种差异:WHERE Name LIKE '%Bob%' 在一个地方,另一个地方你用过Where(x =&gt; x.Name == "Bob") 为什么? @Sampath - 好收获!这更像是一个可能的 SQL 输出示例,而不是我真正想要的。我已经更新了问题。 好的,那我们可以给***.com/a/39696353/1077309一个反馈吗? @Sampath,是的。耐心我的朋友。我正在尝试。 【参考方案1】:

您必须使用Class 而不是Interface IAssociationEntity。我将其命名为AssociationEntity

我已将您原来的 TSQL 查询转换为:

SELECT TOP 4 a.* FROM
(
    SELECT CompanyID, Name from Company WHERE Name = 'Bob'
    UNION
    SELECT OrganizationID, Name from Organization WHERE Name = 'Bob'
    UNION
    SELECT AuthorityID, Name from Authority WHERE Name = 'Bob'
) AS a

Linq To Entity Query 如下图所示。

var queryKey ="Bob";

var query = ((from c in db.Company  where (c.Name = queryKey) select new AssociationEntity  Name = c.Name ).Take(4))
.Concat((from o in db.Organization  where (o.Name = queryKey) select new AssociationEntity  Name = o.Name ).Take(4))
.Concat((from a in db.Authority  where (a.Name = queryKey) select new AssociationEntity  Name = a.Name ).Take(4));

【讨论】:

【参考方案2】:

你为什么不这样做:

var result = db.Companies.Where(x => x.Name == "Bob").Select(x => new  x.Name )
             .Concat(db.Organizations.Where(y => y.Name == "Bob").Select(y => new  y.Name ))
             .Concat(db.Authorities.Where(z => z.Name == "Bob").Select(z => new  z.Name )
             .OrderBy(x => x.Name).Skip(2).Take(10);

您可以将Select 方法中的匿名对象替换为基类。

【讨论】:

【参考方案3】:

试试这个:

var query = db.Companies.Select(x => new  Id = x.CompanyId, x.Name )
            .Concat(db.Organizations.Select(x => new  Id = x.OrganizationId, x.Name ))
            .Concat(db.Authorities.Select(x => new  Id = x.AuthorityId, x.Name ));

var result = query.Where(x => x.Name == "Bob").OrderBy(x => x.Name).Skip(2).Take(10);

生成的SQL是:

SELECT
    [UnionAll2].[CompanyId] AS [C1],
    [UnionAll2].[CompanyId1] AS [C2],
    [UnionAll2].[Name] AS [C3]
    FROM  (SELECT
        [Extent1].[CompanyId] AS [CompanyId],
        [Extent1].[CompanyId] AS [CompanyId1],
        [Extent1].[Name] AS [Name]
        FROM [dbo].[Companies] AS [Extent1]
        WHERE N'Bob' = [Extent1].[Name]
    UNION ALL
        SELECT
        [Extent2].[OrganizationId] AS [OrganizationId],
        [Extent2].[OrganizationId] AS [OrganizationId1],
        [Extent2].[Name] AS [Name]
        FROM [dbo].[Organizations] AS [Extent2]
        WHERE N'Bob' = [Extent2].[Name]
    UNION ALL
        SELECT
        [Extent3].[AuthorityId] AS [AuthorityId],
        [Extent3].[AuthorityId] AS [AuthorityId1],
        [Extent3].[Name] AS [Name]
        FROM [dbo].[Authorities] AS [Extent3]
        WHERE N'Bob' = [Extent3].[Name]) AS [UnionAll2]
    ORDER BY [UnionAll2].[Name] ASC
    OFFSET 2 ROWS FETCH NEXT 10 ROWS ONLY

【讨论】:

感谢您的回答。它与其他的非常相似,但我需要将我的查询作为 IQueryable 返回,然后我无法使用匿名类型。

以上是关于EF 将多个表合并为一个 IQueryable的主要内容,如果未能解决你的问题,请参考以下文章

Perl如何将两个或多个excel文件合并为一个(多个工作表)?

EF基础知识小记七(拆分实体到多个表以及拆分表到多个实体)

将多个 Hive 表合并为 Hadoop 中的单个表

将多个工作簿合并为一个工作簿,所有工作簿为工作表

ef核心 - 如何将类映射的多个变体放到同一个表中

将多个 CASE 语句合并为一个并 SELECT INTO 临时表