EF простые отношения 1- * приводят к странному запросу SQL

1

Я пытаюсь отладить сгенерированный SQL-запрос EF 6.1.1, и я смог воспроизвести проблему с этим небольшим количеством куде. Это простая односторонняя однонаправленная связь, которую я пытаюсь загрузить:

Модель выглядит следующим образом:

public class Subscription
{
    public Guid Id { get; set; }
    public IList<Recipient> RecipientHistory { get; set; }
    public IList<Payer> PayerHistory { get; set; }
}

public class Recipient
{
    public Guid Id { get; set; }
}

public class Payer
{
    public Guid Id { get; set; }
}

И классы отображения EF:

public class SubscriptionMapping : EntityTypeConfiguration<Subscription>
{
    public SubscriptionMapping()
    {
        ToTable("Subscriptions");
        HasKey(p => p.Id);

        Property(p => p.Id).HasColumnName("subscription_id");

        HasMany(p => p.RecipientHistory)
            .WithRequired()
            .Map(m => m.MapKey("subscription_id"));

        HasMany(p => p.PayerHistory)
            .WithRequired()
            .Map(m => m.MapKey("subscription_id"));
    }
}

public class RecipientMapping : EntityTypeConfiguration<Recipient>
{
    public RecipientMapping()
    {
        ToTable("Subscription_recipients");
        HasKey(p => p.Id);
        Property(p => p.Id).HasColumnName("subscription_recipient_id");
    }
}

public class PayerMapping : EntityTypeConfiguration<Payer>
{
    public PayerMapping()
    {
        ToTable("Subscription_payers");
        HasKey(p => p.Id);
        Property(p => p.Id).HasColumnName("subscription_payer_id");
    }
}

Когда я выполняю следующий запрос в хранилище:

db.Set<Subscription>()
    .Include(s => s.RecipientHistory)
    .Include(s => s.PayerHistory)
    .ToList();

В результате SQL:

SELECT 
    [UnionAll1].[C2] AS [C1], 
    [UnionAll1].[subscription_id] AS [C2], 
    [UnionAll1].[C1] AS [C3], 
    [UnionAll1].[C3] AS [C4], 
    [UnionAll1].[subscription_recipient_id] AS [C5], 
    [UnionAll1].[subscription_id1] AS [C6], 
    [UnionAll1].[C4] AS [C7], 
    [UnionAll1].[C5] AS [C8], 
    [UnionAll1].[C6] AS [C9]
    FROM  (SELECT 
        CASE WHEN ([Extent2].[subscription_recipient_id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1], 
        1 AS [C2], 
        [Extent1].[subscription_id] AS [subscription_id], 
        CASE WHEN ([Extent2].[subscription_recipient_id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C3], 
        [Extent2].[subscription_recipient_id] AS [subscription_recipient_id], 
        [Extent2].[subscription_id] AS [subscription_id1], 
        CAST(NULL AS int) AS [C4], 
        CAST(NULL AS uniqueidentifier) AS [C5], 
        CAST(NULL AS uniqueidentifier) AS [C6]
        FROM  [dbo].[Subscriptions] AS [Extent1]
        LEFT OUTER JOIN [dbo].[Subscription_recipients] AS [Extent2] ON [Extent1].[subscription_id] = [Extent2].[subscription_id]
    UNION ALL
        SELECT 
        2 AS [C1], 
        2 AS [C2], 
        [Extent3].[subscription_id] AS [subscription_id], 
        CAST(NULL AS int) AS [C3], 
        CAST(NULL AS uniqueidentifier) AS [C4], 
        CAST(NULL AS uniqueidentifier) AS [C5], 
        2 AS [C6], 
        [Extent4].[subscription_payer_id] AS [subscription_payer_id], 
        [Extent4].[subscription_id] AS [subscription_id1]
        FROM  [dbo].[Subscriptions] AS [Extent3]
        INNER JOIN [dbo].[Subscription_payers] AS [Extent4] ON [Extent3].[subscription_id] = [Extent4].[subscription_id]) AS [UnionAll1]
    ORDER BY [UnionAll1].[subscription_id] ASC, [UnionAll1].[C1] ASC

И это кажется мне совершенно ненужным для такого простого запроса. Я что-то пропускаю в своих сопоставлениях или это предназначено?

Теги:
entity-framework

1 ответ

1
Лучший ответ

Желательная загрузка двух разных зависимостей довольно сложна. Это безопасный способ сделать именно это, используя как можно меньше места - обратите внимание, что результаты являются null для всех строк, которые не являются "включенной" частью.

Основная проблема заключается в том, что с двумя ассоциациями 1: * вам будет трудно отделить включенные объекты, которые из первой ассоциации связаны с второй. Та же проблема не возникает, если вы имеете дело с 1: (0-) 1, потому что результат будет просто отдельной записью.

Дело в том, что вы думаете об этом как о "простых отношениях от одного до многих". Если вы когда-либо пытались написать аналогичный запрос в SQL, вы должны знать, что 1: * на самом деле довольно сложно работать, особенно если вам нужно объединить много разных 1: * s в одном запросе. Теперь есть альтернативы (например, использование поля xml в результирующем наборе), но это, вероятно, тот, который позволяет серверу делать столько оптимизаций, сколько может, и накладные расходы на самом деле будут на самом деле довольно небольшими на практике. что вы только получаете каждую часть данных один раз - остальное равно null - почему первый запрос выполняет left join (он должен возвращать родителя, даже если в этом отношении нет ничего), тогда как второй свободен делать inner join (и не нужно выбирать какие-либо столбцы из родительского, за исключением первичного ключа). Это можно было бы улучшить, если бы был способ не возвращать родительские данные в каждой строке первого подзапроса, но это было легче сказать, чем сделать (и, вероятно, в конечном итоге будет намного медленнее).

В общем, это немного красноречивый запрос, но он делает то, что он должен делать. У вас проблемы с производительностью, или вам просто интересно?

  • 0
    Большое спасибо, Луаан. Ваше объяснение очень полезно, и я вижу проблемы с более чем одним 1: * в одном запросе. Мне было просто любопытно - никаких проблем с производительностью. Я просто немного волновался в тот момент, думая, что запрос казался слишком сложным для того, что он должен был сделать. Но это имеет смысл сейчас.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню