EF6产生了荒谬的SQL语句

时间:2014-02-24 14:07:37

标签: sql sql-server entity-framework ef-code-first

我可能不是SQL专家,但在进行性能测试时,我看到EF6(Code-First)生成以下语句:

SELECT 
[UnionAll3].[C2] AS [C1], 
[UnionAll3].[C3] AS [C2], 
[UnionAll3].[C4] AS [C3], 
[UnionAll3].[C5] AS [C4], 
[UnionAll3].[C6] AS [C5], 
[UnionAll3].[C7] AS [C6], 
[UnionAll3].[C8] AS [C7], 
[UnionAll3].[C1] AS [C8], 
[UnionAll3].[C9] AS [C9], 
[UnionAll3].[C10] AS [C10], 
[UnionAll3].[C11] AS [C11], 
[UnionAll3].[C12] AS [C12], 
[UnionAll3].[C13] AS [C13], 
[UnionAll3].[C14] AS [C14], 
[UnionAll3].[C15] AS [C15], 
[UnionAll3].[C16] AS [C16], 
[UnionAll3].[C17] AS [C17], 
[UnionAll3].[C18] AS [C18], 
[UnionAll3].[C19] AS [C19], 
[UnionAll3].[C20] AS [C20], 
[UnionAll3].[C21] AS [C21], 
[UnionAll3].[C22] AS [C22], 
[UnionAll3].[C23] AS [C23], 
[UnionAll3].[C24] AS [C24], 
[UnionAll3].[C25] AS [C25], 
[UnionAll3].[C26] AS [C26], 
[UnionAll3].[C27] AS [C27], 
[UnionAll3].[C28] AS [C28], 
[UnionAll3].[C29] AS [C29]
FROM  (SELECT 
    [UnionAll2].[C1] AS [C1], 
    [UnionAll2].[C2] AS [C2], 
    [UnionAll2].[C3] AS [C3], 
    [UnionAll2].[C4] AS [C4], 
    [UnionAll2].[C5] AS [C5], 
    [UnionAll2].[C6] AS [C6], 
    [UnionAll2].[C7] AS [C7], 
    [UnionAll2].[C8] AS [C8], 
    [UnionAll2].[C9] AS [C9], 
    [UnionAll2].[C10] AS [C10], 
    [UnionAll2].[C11] AS [C11], 
    [UnionAll2].[C12] AS [C12], 
    [UnionAll2].[C13] AS [C13], 
    [UnionAll2].[C14] AS [C14], 
    [UnionAll2].[C15] AS [C15], 
    [UnionAll2].[C16] AS [C16], 
    [UnionAll2].[C17] AS [C17], 
    [UnionAll2].[C18] AS [C18], 
    [UnionAll2].[C19] AS [C19], 
    [UnionAll2].[C20] AS [C20], 
    [UnionAll2].[C21] AS [C21], 
    [UnionAll2].[C22] AS [C22], 
    [UnionAll2].[C23] AS [C23], 
    [UnionAll2].[C24] AS [C24], 
    [UnionAll2].[C25] AS [C25], 
    [UnionAll2].[C26] AS [C26], 
    [UnionAll2].[C27] AS [C27], 
    [UnionAll2].[C28] AS [C28], 
    [UnionAll2].[C29] AS [C29]
    FROM  (SELECT 
        [UnionAll1].[C1] AS [C1], 
        [UnionAll1].[Id] AS [C2], 
        [UnionAll1].[Id1] AS [C3], 
        [UnionAll1].[Ident] AS [C4], 
        [UnionAll1].[DescriptionLong] AS [C5], 
        [UnionAll1].[DescriptionShort1] AS [C6], 
        [UnionAll1].[DescriptionShort2] AS [C7], 
        [UnionAll1].[ArticleGroup] AS [C8], 
        [UnionAll1].[Id2] AS [C9], 
        [UnionAll1].[Id3] AS [C10], 
        [UnionAll1].[Barcode] AS [C11], 
        [UnionAll1].[Amount] AS [C12], 
        [UnionAll1].[StorageLocation] AS [C13], 
        [UnionAll1].[Article_Id] AS [C14], 
        [UnionAll1].[C2] AS [C15], 
        [UnionAll1].[C3] AS [C16], 
        [UnionAll1].[C4] AS [C17], 
        [UnionAll1].[C5] AS [C18], 
        [UnionAll1].[C6] AS [C19], 
        [UnionAll1].[C7] AS [C20], 
        [UnionAll1].[C8] AS [C21], 
        [UnionAll1].[C9] AS [C22], 
        [UnionAll1].[C10] AS [C23], 
        [UnionAll1].[C11] AS [C24], 
        [UnionAll1].[C12] AS [C25], 
        [UnionAll1].[C13] AS [C26], 
        [UnionAll1].[C14] AS [C27], 
        [UnionAll1].[C15] AS [C28], 
        [UnionAll1].[C16] AS [C29]
        FROM  (SELECT 
            CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1], 
            [Extent1].[Id] AS [Id], 
            [Extent1].[Id] AS [Id1], 
            [Extent1].[Ident] AS [Ident], 
            [Extent1].[DescriptionLong] AS [DescriptionLong], 
            [Extent1].[DescriptionShort1] AS [DescriptionShort1], 
            [Extent1].[DescriptionShort2] AS [DescriptionShort2], 
            [Extent1].[ArticleGroup] AS [ArticleGroup], 
            [Extent2].[Id] AS [Id2], 
            [Extent2].[Id] AS [Id3], 
            [Extent2].[Barcode] AS [Barcode], 
            [Extent2].[Amount] AS [Amount], 
            [Extent2].[StorageLocation] AS [StorageLocation], 
            [Extent2].[Article_Id] AS [Article_Id], 
            CAST(NULL AS int) AS [C2], 
            CAST(NULL AS int) AS [C3], 
            CAST(NULL AS int) AS [C4], 
            CAST(NULL AS decimal(18,2)) AS [C5], 
            CAST(NULL AS int) AS [C6], 
            CAST(NULL AS int) AS [C7], 
            CAST(NULL AS int) AS [C8], 
            CAST(NULL AS int) AS [C9], 
            CAST(NULL AS decimal(18,2)) AS [C10], 
            CAST(NULL AS datetime2) AS [C11], 
            CAST(NULL AS int) AS [C12], 
            CAST(NULL AS int) AS [C13], 
            CAST(NULL AS int) AS [C14], 
            CAST(NULL AS varchar(1)) AS [C15], 
            CAST(NULL AS int) AS [C16]
            FROM  [dbo].[Articles] AS [Extent1]
            LEFT OUTER JOIN [dbo].[Batches] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Article_Id]
        UNION ALL
            SELECT 
            2 AS [C1], 
            [Extent3].[Id] AS [Id], 
            [Extent3].[Id] AS [Id1], 
            [Extent3].[Ident] AS [Ident], 
            [Extent3].[DescriptionLong] AS [DescriptionLong], 
            [Extent3].[DescriptionShort1] AS [DescriptionShort1], 
            [Extent3].[DescriptionShort2] AS [DescriptionShort2], 
            [Extent3].[ArticleGroup] AS [ArticleGroup], 
            CAST(NULL AS int) AS [C2], 
            CAST(NULL AS int) AS [C3], 
            CAST(NULL AS varchar(1)) AS [C4], 
            CAST(NULL AS decimal(18,2)) AS [C5], 
            CAST(NULL AS int) AS [C6], 
            CAST(NULL AS int) AS [C7], 
            [Extent4].[Id] AS [Id2], 
            [Extent4].[Id] AS [Id3], 
            [Extent4].[Amount] AS [Amount], 
            [Extent4].[PricePerUncorrectedUnit] AS [PricePerUncorrectedUnit], 
            [Extent4].[Type] AS [Type], 
            [Extent4].[Article_Id] AS [Article_Id], 
            CAST(NULL AS int) AS [C8], 
            CAST(NULL AS int) AS [C9], 
            CAST(NULL AS decimal(18,2)) AS [C10], 
            CAST(NULL AS datetime2) AS [C11], 
            CAST(NULL AS int) AS [C12], 
            CAST(NULL AS int) AS [C13], 
            CAST(NULL AS int) AS [C14], 
            CAST(NULL AS varchar(1)) AS [C15], 
            CAST(NULL AS int) AS [C16]
            FROM  [dbo].[Articles] AS [Extent3]
            INNER JOIN [dbo].[ScalePrices] AS [Extent4] ON [Extent3].[Id] = [Extent4].[Article_Id]) AS [UnionAll1]
    UNION ALL
        SELECT 
        3 AS [C1], 
        [Extent5].[Id] AS [Id], 
        [Extent5].[Id] AS [Id1], 
        [Extent5].[Ident] AS [Ident], 
        [Extent5].[DescriptionLong] AS [DescriptionLong], 
        [Extent5].[DescriptionShort1] AS [DescriptionShort1], 
        [Extent5].[DescriptionShort2] AS [DescriptionShort2], 
        [Extent5].[ArticleGroup] AS [ArticleGroup], 
        CAST(NULL AS int) AS [C2], 
        CAST(NULL AS int) AS [C3], 
        CAST(NULL AS varchar(1)) AS [C4], 
        CAST(NULL AS decimal(18,2)) AS [C5], 
        CAST(NULL AS int) AS [C6], 
        CAST(NULL AS int) AS [C7], 
        CAST(NULL AS int) AS [C8], 
        CAST(NULL AS int) AS [C9], 
        CAST(NULL AS int) AS [C10], 
        CAST(NULL AS decimal(18,2)) AS [C11], 
        CAST(NULL AS int) AS [C12], 
        CAST(NULL AS int) AS [C13], 
        [Extent6].[Id] AS [Id2], 
        [Extent6].[Id] AS [Id3], 
        [Extent6].[Amount] AS [Amount], 
        [Extent6].[Date] AS [Date], 
        [Extent6].[Article_Id] AS [Article_Id], 
        CAST(NULL AS int) AS [C14], 
        CAST(NULL AS int) AS [C15], 
        CAST(NULL AS varchar(1)) AS [C16], 
        CAST(NULL AS int) AS [C17]
        FROM  [dbo].[Articles] AS [Extent5]
        INNER JOIN [dbo].[Demands] AS [Extent6] ON [Extent5].[Id] = [Extent6].[Article_Id]) AS [UnionAll2]
UNION ALL
    SELECT 
    4 AS [C1], 
    [Extent7].[Id] AS [Id], 
    [Extent7].[Id] AS [Id1], 
    [Extent7].[Ident] AS [Ident], 
    [Extent7].[DescriptionLong] AS [DescriptionLong], 
    [Extent7].[DescriptionShort1] AS [DescriptionShort1], 
    [Extent7].[DescriptionShort2] AS [DescriptionShort2], 
    [Extent7].[ArticleGroup] AS [ArticleGroup], 
    CAST(NULL AS int) AS [C2], 
    CAST(NULL AS int) AS [C3], 
    CAST(NULL AS varchar(1)) AS [C4], 
    CAST(NULL AS decimal(18,2)) AS [C5], 
    CAST(NULL AS int) AS [C6], 
    CAST(NULL AS int) AS [C7], 
    CAST(NULL AS int) AS [C8], 
    CAST(NULL AS int) AS [C9], 
    CAST(NULL AS int) AS [C10], 
    CAST(NULL AS decimal(18,2)) AS [C11], 
    CAST(NULL AS int) AS [C12], 
    CAST(NULL AS int) AS [C13], 
    CAST(NULL AS int) AS [C14], 
    CAST(NULL AS int) AS [C15], 
    CAST(NULL AS decimal(18,2)) AS [C16], 
    CAST(NULL AS datetime2) AS [C17], 
    CAST(NULL AS int) AS [C18], 
    [Extent8].[ProjectUsageId] AS [ProjectUsageId], 
    [Extent8].[ProjectUsageId] AS [ProjectUsageId1], 
    [Extent8].[ProjectUsageName] AS [ProjectUsageName], 
    [Extent8].[Article_Id] AS [Article_Id]
    FROM  [dbo].[Articles] AS [Extent7]
    INNER JOIN [dbo].[ProjectUsages] AS [Extent8] ON [Extent7].[Id] = [Extent8].[Article_Id]) AS [UnionAll3]
ORDER BY [UnionAll3].[C3] ASC, [UnionAll3].[C1] ASC

我不敢相信,当它只是从5个表中获取数据时(这可以很好地连接并且定义了主键+关系),这在任何方面都是有效的。以下是导致此SQL语句的语句:

IQueryable<Article> articles = context.Articles
  .Include("Batches")
  .Include("ScalePrices")
  .Include("Demands")
  .Include("ProjectUsages");

var actualQuery = context.Articles.Where(d => d.DescriptionLong.Contains(searchTermList[0]) || d.Ident.Contains(searchTermList[0]));

表格定义如下(示例):

[Table("Articles")]
public class Article
{
    [Key]
    public int Id { get; set; }

    [Display(Name = "Article", ResourceType = typeof(Resources))]
    [StringLength(32), Required]
    public string Ident { get; set; }

    [Display(Name = "Description", ResourceType = typeof(Resources))]
    [StringLength(4000)]
    public string DescriptionLong { get; set; }
}

[Serializable]
[Table ("Batches")]
public class ArticleBatch
{
    [Key]
    public int Id { get; set; }

    [StringLength(32)]
    public string Barcode { get; set; }

    public decimal Amount { get; set; }
    public ArticleStorageLocation StorageLocation { get; set; }

    public override string ToString()
    {
        return String.Format("{0} ({1}/{2})", Barcode, Amount, StorageLocation);
    }
}

DbContext看起来像这样:

public class ArticleDataDbContext : DbContext
{
    public ArticleDataDbContext(string connectionString) : base(connectionString)
    {
        Database.SetInitializer(new CreateDatabaseIfNotExists<ArticleDataDbContext>());
        this.Configuration.LazyLoadingEnabled = false;
        this.Configuration.ProxyCreationEnabled = false;
    }

    public DbSet<Article> Articles { get; set; }
    public DbSet<ArticleBatch> Batches { get; set; }
    public DbSet<ArticleDemand> Demands { get; set; }
    public DbSet<ArticleScalePrice> ScalePrices { get; set; }
    public DbSet<ProjectUsage> ProjectUsages { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Article>().HasMany(d => d.Batches).WithRequired();
        modelBuilder.Entity<Article>().HasMany(d => d.Demands).WithRequired();
        modelBuilder.Entity<Article>().HasMany(d => d.ScalePrices).WithRequired();
        modelBuilder.Entity<Article>().HasMany(d => d.ProjectUsages).WithRequired();
    }
}

这是SQL Management Studio中的ER模型图:

ER Model

您是否知道此陈述是否“正确”或是否完全无稽之谈。我做错了什么?

1 个答案:

答案 0 :(得分:7)

实体框架会创建这样一个复杂的查询,因为简单查询会返回重复数据的批次。假设每个子表包含特定文章的10条记录,并假设您在文章ID上左连接所有四个子表。您现在得到的结果集为10 * 10 * 10 * 10 = 10000行。实体框架的查询仅提供1 + 10 + 10 + 10 + 10 = 41行,但仍然为您提供所需的所有数据。

根据评论,如果查询确实导致性能问题,您可以将其拆分为多个单独的简单查询:

var articles = context.Articles.Where(d =>
  d.DescriptionLong.Contains(searchTermList[0])
  || d.Ident.Contains(searchTermList[0])).ToList();
// Either loop, or find one specific article for which
// you want to load the details. I'll use a loop.
foreach (var article in articles) {
  article.Batches.Load();
  article.ScalePrices.Load();
  article.Demands.Load();
  article.ProjectUsages.Load();
}

您甚至可以将其包装在可序列化/快照事务中,以确保数据的一致性。