EF объединить несколько таблиц в одну IQueryable

Я использую OData (WebAPI и EF) для запроса базы данных. А теперь я должен ... --12-->слияние" три стола в один результат.

у меня есть 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 IQuearyable<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;

и при выполнении против queryable:

_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 

3 ответов


вы должны использовать 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));

почему бы вам просто не сделать следующее:

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 метод базового класса.


попробуйте это:

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