2012-01-31 59 views
1

MVC3項目,使用LINQ to Entity和Entity Framework 4 Code-First。LINQ生成不正確的SQL(引用不存在的表)

在另一篇文章(Return products which belong to all tags in a list using LINQ)中,我收到了創建LINQ語句以返回數據子集的幫助。

LINQ在語法上是正確的並且編譯,但生成不正確的SQL。具體來說,它引用了一個不存在的表。如果我更正表名,它會返回正確的數據,所以LINQ似乎是正確的。

注意在保持這種長期職位從得到甚至更長的利益,我不會發布對象類(產品,標籤和ProductTag),但他們在我的前面的問題這裏列出:Return products which belong to all tags in a list using 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]」 和「 [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錯誤?我可以完全拋棄它,只是創建一個存儲過程,但我寧願找到一個修復。

非常感謝併爲這篇長文章道歉。

EDIT

問題被證明是一個有缺陷的實體模型,並在一個多對多關係的表之間的過度和不必要的導航屬性。 Slauma的詳細答案是瞭解發生了什麼的關鍵。

新的模型如下:

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; } 

} 
+5

恩,這不是HTML ...這是SQL ......聽起來你的模型可能已經過時了你的模式。 – 2012-01-31 20:48:29

+0

看起來像你改變你的模型,而不重新創建數據庫 – BrokenGlass 2012-01-31 20:50:56

+0

你應該改變了錯誤的LINQ查詢到我給你更正的一個[這裏](http://stackoverflow.com/q/9074875/601179)... – gdoron 2012-01-31 20:59:06

回答

4

如果你沒有在你的鏈接後在模型流利的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 */) ,Product和0123之間TagProductTag一個一對多關係/* 3 */)之間的一個到多關係(/* 2 */)其中Product中的導航屬性未公開。

因爲你沒有在流利的API實體框架的許多一對多關係的映射將預計隨後映射約定的數據庫表 - 那就是:

  • 一個多TO-很多連接表叫做ProductTagsTagProducts。如果您已禁用多元化,則預計ProductTagTagProduct。我說「」,因爲名稱取決於諸如派生環境中集合的順序等因素,甚至可能是類中導航屬性的順序等。因此,很難在複雜模型中預測名稱 - 基本上就是爲什麼建議在Fluent API中明確定義多對多關係的原因。

  • 表中的一個鍵列有姓名EntityClassName_EntityKeyName - >Tag_TagId

  • 在表中的其他鍵列與Product_ProductId

在查詢中只有這許多一對多的關係參與(您只使用Product.Tags作爲查詢中唯一的導航屬性)。因此,EF將創建一個SQL查詢,其中包含連接表(它恰好是您的案例中的TagProduct,但正如所述,只是偶然發生)以及連接表的鍵列名稱爲Tag_TagIdProduct_ProductId

您可以用流利的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導航屬性定義在流利的API映射,如上圖所示(對應於一個名爲ProductTag有一個連接表兩列ProductIdTagId形成一個複合主鍵和分別爲外鍵的ProductTag和表)

因此,您將只有一個關係(ProductTag之間的多對多關係),而不是三個關係,我希望您的查詢能夠正常工作。

+0

我不得不花一點時間來消化你的答案,然後我開始在應用程序的其他領域看到更多的「無效的對象名稱dbo.TagProduct」錯誤。多數民衆贊成在你的答案開始沉入。我結束從產品和標籤中刪除導航屬性。必須重新構建提供的查詢gdoron以反映更改,現在全部正常。我將編輯我的問題以顯示最終產品。感謝您的幫助(和詳細)回覆! – 2012-02-01 20:22:10

+0

@JohnL:我明白了。你的'ProductTag'實體中有額外的標量屬性嗎?否則,你不需要它在'Product'和'Tag'之間進行多對多映射,但它也可以工作:) – Slauma 2012-02-01 21:58:16

+0

爲了學習,我會試試這個。再次感謝。 – 2012-02-01 23:04:58