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; }
}
如果链接文章中的模型中没有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 */
}
因此,Tag
和Product
之间存在多对多关系(/* 1 */
),Tag
和ProductTag
之间存在<强>一对多对多关系(/* 3 */
),其中Product
中的导航属性未公开。强>
由于Fluent API实体框架中没有多对多关系的映射,因此需要遵循映射约定的数据库表,即:
称为
ProductTags
或TagProducts
的多对多联接表。如果禁用了复数形式,则应为ProductTag
或TagProduct
。我之所以说"或",是因为名称取决于派生上下文中集合的顺序,甚至可能取决于类中导航属性的顺序等因素。因此,很难预测复杂模型中的名称-基本上就是为什么建议在Fluent API中始终明确定义多对多关系的原因。表中名为
EntityClassName_EntityKeyName
->Tag_TagId
的一个键列- 具有
Product_ProductId
的表中的另一个键列
在您的查询中,只涉及这种多对多关系(您只使用Product.Tags
作为查询中唯一的导航属性)。因此,EF将创建一个SQL查询,其中包括联接表(在您的情况下,它恰好是TagProduct
,但如上所述,这只是偶然的)和联接表的关键列名Tag_TagId
和Product_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
的联接表,具有两列ProductId
和TagId
,这两列构成一个复合主键,分别是Product
和Tag
表的外键)
因此,您将只有一个关系(Product
和Tag
之间的多对多关系),而不是三个关系,我希望您的查询能够工作。