LINQ生成不正确的SQL(对不存在的表的引用)

本文关键字:不存在 引用 SQL 不正确 LINQ | 更新日期: 2023-09-27 18:21:59

MVC3项目,使用LINQ to Entity和Entity Framework 4 Code First。

在另一篇文章(使用LINQ返回属于列表中所有标签的产品)中,我得到了创建LINQ语句以返回数据子集的帮助。

LINQ在语法上是正确的,可以编译,但生成的SQL不正确。具体来说,它引用了一个不存在的表。如果我更正了表名,它将返回正确的数据,因此LINQ似乎是正确的。

请注意,为了避免这篇长帖子变得更长,我不会发布对象类(Product、Tag和ProductTag),但它们在我之前的问题中列出:使用LINQ 返回属于列表中所有标签的产品

LINQ:

var tags = "administration+commerce"
var tagParams = tags.Split('+').ToList();   //used in linq statement below
_repository.Products.Where(p => tagParams.All(tag => p.Tags.Select(x => x.Name).Contains(tag))).Distinct().Take(75).ToList();   


以下是错误且正确的SQL代码。

不正确的SQL引用了不存在的表

[dbo].[TagProduct] 

以及格式错误的字段

[ExtentN].[Tag_TagId]

如果我将它们更正为"[dbo].[ProductTag]"answers"[ExtentN].[TagId]",则SQL将正确执行并返回正确的数据。

LINQ生成(且有故障)SQL

SELECT 
[Extent1].[ProductId] AS [ProductId], 
[Extent1].[Name] AS [Name], 
[Extent1].[ShortDescription] AS [ShortDescription], 
[Extent1].[LongDescription] AS [LongDescription], 
[Extent1].[Price] AS [Price]
FROM [dbo].[Product] AS [Extent1]
WHERE  NOT EXISTS (SELECT 
    1 AS [C1]
    FROM  (SELECT 
        N'administration' AS [C1]
        FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
    UNION ALL
        SELECT 
        N'commerce' AS [C1]
        FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]) AS [UnionAll1]
    WHERE ( NOT EXISTS (SELECT 
        1 AS [C1]
        FROM  [dbo].[TagProduct] AS [Extent2]
        INNER JOIN [dbo].[Tag] AS [Extent3] ON [Extent3].[TagId] = [Extent2].[Tag_TagId]
        WHERE ([Extent1].[ProductId] = [Extent2].[Product_ProductId]) AND ([Extent3].[Name] = [UnionAll1].[C1])
    )) OR (CASE WHEN ( EXISTS (SELECT 
        1 AS [C1]
        FROM  [dbo].[TagProduct] AS [Extent4]
        INNER JOIN [dbo].[Tag] AS [Extent5] ON [Extent5].[TagId] = [Extent4].[Tag_TagId]
        WHERE ([Extent1].[ProductId] = [Extent4].[Product_ProductId]) AND ([Extent5].[Name] = [UnionAll1].[C1])
    )) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
        1 AS [C1]
        FROM  [dbo].[TagProduct] AS [Extent6]
        INNER JOIN [dbo].[Tag] AS [Extent7] ON [Extent7].[TagId] = [Extent6].[Tag_TagId]
        WHERE ([Extent1].[ProductId] = [Extent6].[Product_ProductId]) AND ([Extent7].[Name] = [UnionAll1].[C1])
    )) THEN cast(0 as bit) END IS NULL)
)

已更正的SQL

SELECT 
[Extent1].[ProductId] AS [ProductId], 
[Extent1].[Name] AS [Name], 
[Extent1].[ShortDescription] AS [ShortDescription], 
[Extent1].[LongDescription] AS [LongDescription], 
[Extent1].[Price] AS [Price]
FROM [dbo].[Product] AS [Extent1]
WHERE  NOT EXISTS (SELECT 
    1 AS [C1]
    FROM  (SELECT 
        N'administration' AS [C1]
        FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
    UNION ALL
        SELECT 
        N'commerce' AS [C1]
        FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]) AS [UnionAll1]
    WHERE ( NOT EXISTS (SELECT 
        1 AS [C1]
        FROM  [dbo].[ProductTag] AS [Extent2]
        INNER JOIN [dbo].[Tag] AS [Extent3] ON [Extent3].[TagId] = [Extent2].[TagId]
        WHERE ([Extent1].[ProductId] = [Extent2].[ProductId]) AND ([Extent3].[Name] = [UnionAll1].[C1])
    )) OR (CASE WHEN ( EXISTS (SELECT 
        1 AS [C1]
        FROM  [dbo].[ProductTag] AS [Extent4]
        INNER JOIN [dbo].[Tag] AS [Extent5] ON [Extent5].[TagId] = [Extent4].[TagId]
        WHERE ([Extent1].[ProductId] = [Extent4].[ProductId]) AND ([Extent5].[Name] = [UnionAll1].[C1])
    )) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
        1 AS [C1]
        FROM  [dbo].[ProductTag] AS [Extent6]
        INNER JOIN [dbo].[Tag] AS [Extent7] ON [Extent7].[TagId] = [Extent6].[TagId]
        WHERE ([Extent1].[ProductId] = [Extent6].[ProductId]) AND ([Extent7].[Name] = [UnionAll1].[C1])
    )) THEN cast(0 as bit) END IS NULL)
)


同样,SQL中唯一的更改是

[dbo].[TagProduct] changed to [dbo].[ProductTag]
[ExtentN].[Tag_TagId] changed to [ExtentN].[TagId]

注意,我已经确保数据库中没有名为dbo的对象。TagProduct,并且在我的代码中不存在对TagProduct的引用(也从来没有)。

我的LINQ语句中是否有问题,或者这是LINQ错误?我可以完全废弃它,只创建一个存储过程,但我宁愿找到一个修复方法。

感谢并为这篇冗长的帖子道歉。

编辑

事实证明,问题是一个有缺陷的实体模型,在多对多关系中的表之间存在过多且不必要的导航属性。斯拉马的详细回答是理解发生了什么的关键。

新型号如下:

public class Product
{
    .
    . 
    //public virtual List<Tag> Tags { get; set; }             // <--removed
    public virtual List<ProductTag> ProductTags { get; set; }
}
public class ProductTag
{
    .
    . 
    public virtual Product Product { get; set; }
    public virtual Tag Tag { get; set; }
}
public class Tag
{
    .
    . 
    //public virtual List<Product> Products { get; set; }      // <--removed
    public virtual List<ProductTag> ProductTags { get; set; }
}

LINQ生成不正确的SQL(对不存在的表的引用)

如果链接文章中的模型中没有Fluent API中的任何附加映射,则生成的SQL是正确的,也是预期的。为什么?

为了清楚起见,我复制了你的模型和相关的导航属性和标记,它们属于一起:

public class Tag
{
    public int TagId { get; set; }
    public virtual List<Product> Products { get; set; }         /* 1 */
    public virtual List<ProductTag> ProductTags { get; set; }   /* 2 */
}
public class Product
{
    public int ProductId { get; set; }
    public virtual List<Tag> Tags { get; set; }                 /* 1 */
}
public class ProductTag
{
    public int ProductTagId { get; set; }
    public int ProductId { get; set; }
    public int TagId { get; set; }
    public virtual Product Product { get; set; }                /* 3 */
    public virtual Tag Tag { get; set; }                        /* 2 */
}

因此,TagProduct之间存在多对多关系(/* 1 */),TagProductTag之间存在<强>一对多对多关系(/* 3 */),其中Product中的导航属性未公开。

由于Fluent API实体框架中没有多对多关系的映射,因此需要遵循映射约定的数据库表,即:

  • 称为ProductTagsTagProducts的多对多联接表。如果禁用了复数形式,则应为ProductTagTagProduct。我之所以说"",是因为名称取决于派生上下文中集合的顺序,甚至可能取决于类中导航属性的顺序等因素。因此,很难预测复杂模型中的名称-基本上就是为什么建议在Fluent API中始终明确定义多对多关系的原因。

  • 表中名为EntityClassName_EntityKeyName->Tag_TagId 的一个键列

  • 具有Product_ProductId的表中的另一个键列

在您的查询中,只涉及这种多对多关系(您只使用Product.Tags作为查询中唯一的导航属性)。因此,EF将创建一个SQL查询,其中包括联接表(在您的情况下,它恰好是TagProduct,但如上所述,这只是偶然的)和联接表的关键列名Tag_TagIdProduct_ProductId

您可以通过以下方式定义Fluent API中的多对多映射:

modelBuilder.Entity<Product>()
    .HasMany(p => p.Tags)
    .WithMany(t => t.Products)
    .Map(x =>
    {
        x.MapLeftKey("ProductId");
        x.MapRightKey("TagId");
        x.ToTable("ProductTag");
    });

但这会产生问题,因为您已经有了一个ProductTag实体,它显然已经有了相应的表ProductTag。这不可能同时成为多对多关系的联接表。联接表必须有另一个名称,如x.ToTable("ProductTagJoinTable")

我想知道你是否真的想要上面提到的三种关系。或者,为什么希望表名ProductTag属于ProductTag实体?此表和实体根本不涉及您的查询。

编辑

更改模型的建议:除了多对多联接表所需的字段外,ProductTag实体不包含任何其他字段。因此,我会将其映射为一种纯粹的多对多关系。这意味着:

  • 从模型中删除ProductTag实体类
  • Tag类中删除ProductTags导航属性
  • 在Fluent API中定义映射,如上图所示(对应一个名为ProductTag的联接表,具有两列ProductIdTagId,这两列构成一个复合主键,分别是ProductTag表的外键)

因此,您将只有一个关系(ProductTag之间的多对多关系),而不是三个关系,我希望您的查询能够工作。