1

LINQ to Entity を使用する MVC3 プロジェクト、および Entity Framework 4 Code-First。

別の投稿 ( LINQ を使用してリスト内のすべてのタグに属する製品を返す) で、データのサブセットを返す LINQ ステートメントの作成について支援を受けました。

LINQ は構文的に正しく、コンパイルされますが、正しくない SQL が生成されます。具体的には、存在しないテーブルを参照します。テーブル名を直せば正しいデータが返ってくるので、LINQは正しいようです。

この長い投稿がこれ以上長くならないようにするために、オブジェクト クラス (Product、Tag、および ProductTag) は投稿しませんが、以前の質問にリストされています。リンク

リンク:

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 コードです。

不適切な 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 のバグですか? 私はそれを完全に破棄してストアドプロシージャを作成するだけで大​​丈夫ですが、修正を見つけたいと思っています。

長い投稿に感謝し、お詫び申し上げます。

編集

問題はエンティティ モデルの欠陥であることが判明し、多対多の関係にあるテーブル間に過剰で不必要なナビゲーション プロパティが含まれていました。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; }

}
4

1 に答える 1

4

リンクされた投稿のモデルの 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 */
}

したがって、 との間には対多の関係 ( /* 1 */)、 と の間TagProduct1 対多の関係 ( /* 2 */)、 と の間TagProductTag1 対多の関係 ( /* 3 */) がProductありProductTag、 のナビゲーション プロパティProductは公開されていません。

Fluent API Entity Framework には多対多の関係のマッピングがないため、マッピング規則に従うデータベース テーブルが必要です。つまり、次のようになります。

  • ProductTags または と呼ばれる多対多結合テーブルTagProducts。複数形を無効にしている場合は、ProductTag or TagProductが必要です。名前は、派生コンテキスト内のセットの順序や、おそらくクラス内のナビゲーション プロパティの順序などの要因に依存するため、「または」と言います。そのため、複雑なモデルで名前を予測することは困難です。基本的にはFluent API で常に明示的に多対多の関係を定義することが推奨される理由。

  • 名前を持つテーブル内の 1 つのキー列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")です。

上記の 3 つの関係が本当に必要かどうか疑問に思っています。または、エンティティProductTagに属するテーブル名を期待するのはなぜですか? ProductTagこのテーブルとエンティティは、クエリにはまったく関与していません。

編集

モデルを変更する提案:ProductTagエンティティには、多対多結合テーブルに必要なフィールド以外の追加フィールドが含まれていません。したがって、純粋な多対多の関係としてマッピングします。これの意味は:

  • ProductTagモデルからエンティティ クラスを削除する
  • クラスProductTagsからナビゲーション プロパティを削除するTag
  • 上に示すように、Fluent API でマッピングを定義します ( ProductTag2 つの列ProductIdで名前が付けられ、複合主キーを形成し、それぞれおよびテーブルTagIdの外部キーである結合テーブルに対応します)。ProductTag

Productその結果、3 つの関係ではなく、単一の関係 (との間の多対多Tag) のみが作成され、クエリが機能することが期待されます。

于 2012-01-31T23:54:39.833 に答える