8

Oracleで確認されたバグレポートは次のとおりです。http://bugs.mysql.com/bug.php?id = 67183

状況

リポジトリ内でチェーンを使用する.Includeと、奇妙な結果が得られることに気付きました。ほとんどの場合、返されるクエリの値は間違ったフィールドからのものでした(たとえば、名前は説明に表示されます)が、データベースではすべての値が正解です。クエリの後でのみ間違って表示されます)。関係がわかりやすくなるように名前を変更しましたが、構造は同じです。関連するCrewMemberとそれらの相対的なランクとクリアランスに対して間違った値を取得し続けます。CrewMemberにRankと同じフィールド名がある場合、Rankのそのフィールドの値は、CrewMemberの値になります。たとえば、ランクに説明があり、CrewMemberにも説明がある場合、CrewMemberのランクの説明はCrewMemberの説明になります。

joinMySQL Connector / NET sqlプロバイダーがステートメントを適切に形成できなかった結果として同様のフィールドが定義されている場合、EntityFrameworkは深さ2を超えて整形式のクエリを作成できません。

定義

これは、データベーステーブルをモデル化するクラス定義です。EntityFramework4.1およびMySQLConnector/NETバージョン6.5でC#ASP.NETMVC3を使用しています

public class Harbor
{
 public int HarborId { get; set; }
 public virtual ICollection<Ship> Ships { get; set; }
 public string Description { get; set; }
}

public class Ship
{
 public int ShipId { get; set; }
 public int HarborId { get; set; }
 public virtual Harbor Harbor { get; set; }
 public virtual ICollection<CrewMember> CrewMembers { get; set; }
 public string Description { get; set; }
} 

public class CrewMember
{
 public int CrewMemberId { get; set; }
 public int ShipId { get; set; }
 public virtual Ship Ship { get; set; }
 public int RankId { get; set; }
 public virtual Rank Rank { get; set; }
 public int ClearanceId { get; set; }
 public virtual Clearance Clearance { get; set; }
 public string Description { get; set; }
}

public class Rank
{
 public int RankId { get; set; }
 public virtual ICollection<CrewMember> CrewMembers { get; set; }
 public string Description { get; set; }
}

public class Clearance
{
 public int ClearanceId { get; set; }
 public virtual ICollection<CrewMember> CrewMembers { get; set; }
 public string Description { get; set; }
}

クエリ

これは、データベースにクエリを実行し、クエリと.Include呼び出しを含むコードです。

DbSet<Harbor> dbSet = context.Set<Harbor>();
IQueryable<Harbor> query = dbSet;
query = query.Include(entity => entity.Ships);
query = query.Include(entity => entity.Ships.Select(s => s.CrewMembers));
query = query.Include(entity => entity.Ships.Select(s => s.CrewMembers.Select(cm => cm.Rank)));
query = query.Include(entity => entity.Ships.Select(s => s.CrewMembers.Select(cm => cm.Clearance)));

これらの.Include呼び出しは整形式ですか?私は何か見落としてますか?

これはかなり複雑なので、ご不明な点がございましたら、コメントでお知らせください。取り残された可能性のあるものをすべて明確にするよう努めます。

MySQL Connector / NETを使用しているときに、Entity Frameworkを使用して、深さ2を超えるオブジェクトグラフで整形式のクエリを取得するにはどうすればよいですか?

編集

生成されたクエリは次のとおりです。

{SELECT
[Project1].[HarborId], 
[Project1].[Description], 
[Project1].[C2] AS [C1], 
[Project1].[ShipId], 
[Project1].[HarborId1], 
[Project1].[Description1], 
[Project1].[C1] AS [C2], 
[Project1].[CrewMemberId], 
[Project1].[ShipId1], 
[Project1].[ClearanceId], 
[Project1].[RankId], 
[Project1].[Description2], 
[Project1].[RankId1], 
[Project1].[Description3], 
[Project1].[ClearanceId1], 
[Project1].[Description4], 
FROM (SELECT
[Extent1].[HarborId], 
[Extent1].[Description], 
[Join3].[ShipId], 
[Join3].[HarborId] AS [HarborId1], 
[Join3].[Description]AS [Description1], 
[Join3].[CrewMemberId], 
[Join3].[ShipId]AS [ShipId1], 
[Join3].[ClearanceId], 
[Join3].[RankId], 
[Join3].[Description] AS [Description2], 
[Join3].[RankId] AS [RankId1], 
[Join3].[Description] AS [Description3], 
[Join3].[ClearanceId] AS [ClearanceId1], 
[Join3].[Description] AS [Description4], 
CASE WHEN ([Join3].[ShipId] IS  NULL) THEN (NULL)  WHEN ([Join3].[CrewMemberId] IS  NULL) THEN (NULL)  ELSE (1) END AS [C1], 
CASE WHEN ([Join3].[ShipId] IS  NULL) THEN (NULL)  ELSE (1) END AS [C2]
FROM [Harbor] AS [Extent1] LEFT OUTER JOIN (SELECT
[Extent2].[ShipId], 
[Extent2].[HarborId], 
[Extent2].[Description], 
[Join2].[CrewMemberId], 
[Join2].[ShipId] AS [ShipID1], 
[Join2].[ClearanceId], 
[Join2].[RankId], 
[Join2].[Description] AS [DESCRIPTION1], 
[Join2].[RankID1], 
[Join2].[DESCRIPTION1] AS [DESCRIPTION11], 
[Join2].[ClearanceID1], 
[Join2].[DESCRIPTION2], 
FROM [Ship] AS [Extent2] LEFT OUTER JOIN (SELECT
[Extent3].[CrewMemberId], 
[Extent3].[ShipId], 
[Extent3].[ClearanceId], 
[Extent3].[RankId], 
[Extent3].[Description], 
[Extent4].[RankId] AS [RankID1], 
[Extent4].[Description] AS [DESCRIPTION1], 
[Extent5].[ClearanceId] AS [ClearanceID1], 
[Extent5].[Description] AS [DESCRIPTION2], 
FROM [CrewMember] AS [Extent3] INNER JOIN [Rank] AS [Extent4] ON [Extent3].[RankId] = [Extent4].[RankId] LEFT OUTER JOIN [Clearance] AS [Extent5] ON [Extent3].[ClearanceId] = [Extent5].[ClearanceId]) AS [Join2] ON [Extent2].[ShipId] = [Join2].[ShipId]) AS [Join3] ON [Extent1].[HarborId] = [Join3].[HarborId]
 WHERE [Extent1].[HarborId] = @p__linq__0) AS [Project1]
 ORDER BY 
[Project1].[HarborId] ASC, 
[Project1].[C2] ASC, 
[Project1].[ShipId] ASC, 
[Project1].[C1] ASC}

明確化

このように「ドリルダウン」する場合、1対1の関係でincludeを使用しても問題はありません。ただし、この問題は、掘削の一部として1対多の関係がある場合に発生するようです。負荷を熱くするために穴あけが必要です。

最初のプロジェクションはentity => entity.Ships.Select(s => s.CrewMembers、各船に関連するCrewMembersのリストを返します。これにより、港に船のリストが含まれ、それぞれに乗組員のリストが含まれるグラフが適切に返されます。

ただし、2番目の射影CrewMembers.Select(cm => cm.Rankは、実際にはグラフの適切な部分を返しません。フィールドが混在し始め、同じ名前を共有するフィールドは、何らかの理由で親フィールドにデフォルト設定されます。これにより、結果に一貫性がなくなり、さらに重要なことにデータが不良になります。エラーがスローされないという事実は、実行時の検査によってのみ判断できるため、さらに悪化します。

最初の射影から(リストではなく)強く型付けされた単一の応答を何らかの方法で取得する方法があれば、おそらく2番目の射影は必要ないでしょう。今のところ、問題はリストを返す最初の予測にあると思います。2番目の投影が単一のオブジェクトからではなく、そのリストに基づいて投影しようとすると、論理エラーが発生します。

CrewMembersがICollectionである代わりに、それが1つのCrewMemberのみであった場合、このネストされたプロジェクションは実際に正しいデータを返します。ただし、これはこの問題の簡略化されたバージョンであり、残念ながら、この問題を解決するためにレビューしたさまざまなブログ、チュートリアル、投稿、記事、およびドキュメントから、ほとんどすべてのテストが行​​われたようです。

4

3 に答える 3

8

編集

以下のテストは、SQL Server をSqlClientプロバイダーとして使用して行われました。MySql問題が SQL Server で再現できないという事実は、使用しているプロバイダーにバグがあり、LINQ クエリに対して正しくない SQL を作成するかどうかという疑問を提起します。この質問と同じ問題のように見えますが、問題はプロバイダーでも発生し、/SQL Server ではMySql再現できませんでした。SqlClient


関連する CrewMember と、それらの相対的なランクとクリアランスの値が間違っています。CrewMember に Rank と同じフィールド名がある場合、Rank のそのフィールドの値は CrewMember の値になります。たとえば、Rank に説明があり、CrewMember にも説明がある場合、CrewMember の Rank の説明は CrewMember の説明になります。

太字の例 (EF 4.3.1 を使用) をテストしましたが、問題を再現できません。

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;

namespace EFInclude
{
    public class Harbor
    {
        public int HarborId { get; set; }
        public virtual ICollection<Ship> Ships { get; set; }

        public string Description { get; set; }
    }

    public class Ship
    {
        public int ShipId { get; set; }
        public int HarborId { get; set; }
        public virtual Harbor Harbor { get; set; }
        public virtual ICollection<CrewMember> CrewMembers { get; set; }

        public string Description { get; set; }
    }

    public class CrewMember
    {
        public int CrewMemberId { get; set; }
        public int ShipId { get; set; }
        public virtual Ship Ship { get; set; }
        public int RankId { get; set; }
        public virtual Rank Rank { get; set; }
        public int ClearanceId { get; set; }
        public virtual Clearance Clearance { get; set; }

        public string Description { get; set; }
    }

    public class Rank
    {
        public int RankId { get; set; }
        public virtual ICollection<CrewMember> CrewMembers { get; set; }

        public string Description { get; set; }
    }

    public class Clearance
    {
        public int ClearanceId { get; set; }
        public virtual ICollection<CrewMember> CrewMembers { get; set; }

        public string Description { get; set; }
    }

    public class MyContext : DbContext
    {
        public DbSet<Harbor> Harbors { get; set; }
        public DbSet<Ship> Ships { get; set; }
        public DbSet<CrewMember> CrewMembers { get; set; }
        public DbSet<Rank> Ranks { get; set; }
        public DbSet<Clearance> Clearances { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());

            using (var context = new MyContext())
            {
                context.Database.Initialize(true);

                var harbor = new Harbor
                {
                    Ships = new HashSet<Ship>
                    {
                        new Ship
                        {
                            CrewMembers = new HashSet<CrewMember>
                            {
                                new CrewMember
                                {
                                    Rank = new Rank { Description = "Rank A" },
                                    Clearance = new Clearance { Description = "Clearance A" },
                                    Description = "CrewMember A"
                                },
                                new CrewMember
                                {
                                    Rank = new Rank { Description = "Rank B" },
                                    Clearance = new Clearance { Description = "Clearance B" },
                                    Description = "CrewMember B"
                                }
                            },
                            Description = "Ship AB"
                        },
                        new Ship
                        {
                            CrewMembers = new HashSet<CrewMember>
                            {
                                new CrewMember
                                {
                                    Rank = new Rank { Description = "Rank C" },
                                    Clearance = new Clearance { Description = "Clearance C" },
                                    Description = "CrewMember C"
                                },
                                new CrewMember
                                {
                                    Rank = new Rank { Description = "Rank D" },
                                    Clearance = new Clearance { Description = "Clearance D" },
                                    Description = "CrewMember D"
                                }
                            },
                            Description = "Ship CD"
                        }
                    },
                    Description = "Harbor ABCD"
                };

                context.Harbors.Add(harbor);
                context.SaveChanges();
            }

            using (var context = new MyContext())
            {
                DbSet<Harbor> dbSet = context.Set<Harbor>();
                IQueryable<Harbor> query = dbSet;
                query = query.Include(entity => entity.Ships);
                query = query.Include(entity => entity.Ships.Select(s => s.CrewMembers));
                query = query.Include(entity => entity.Ships.Select(s => s.CrewMembers.Select(cm => cm.Rank)));
                query = query.Include(entity => entity.Ships.Select(s => s.CrewMembers.Select(cm => cm.Clearance)));

                var sqlString = query.ToString();
                // see below for the generated SQL query

                var harbor = query.Single();

                Console.WriteLine("Harbor {0} Description = \"{1}\"",
                    harbor.HarborId, harbor.Description);
                foreach (var ship in harbor.Ships)
                {
                    Console.WriteLine("- Ship {0} Description = \"{1}\"",
                        ship.ShipId, ship.Description);
                    foreach (var crewMember in ship.CrewMembers)
                    {
                        Console.WriteLine("-- CrewMember {0} Description = \"{1}\"", 
                            crewMember.CrewMemberId, crewMember.Description);
                        Console.WriteLine("-- CrewMember {0} Rank Description = \"{1}\"",
                            crewMember.CrewMemberId, crewMember.Rank.Description);
                        Console.WriteLine("-- CrewMember {0} Clearance Description = \"{1}\"",
                            crewMember.CrewMemberId, crewMember.Clearance.Description);
                    }
                }

                Console.ReadLine();
            }
        }
    }
}

出力は次のとおりです。

ここに画像の説明を入力

太字のあなたの説明によると、次のようにすべきです: CrewMember 1 Description = "Rank A"そして、他の 3 人の乗組員についても同じ混乱です。しかし、私はこれを持っていません。

エラーが発生したコードと比較して、私のテストプログラムに何か違いがありますか?

編集

クエリに対して生成された SQL (var sqlString = query.ToString();上記のソース コードの行を参照してください。以下は の内容ですsqlString) は次のとおりです。

SELECT 
[Project1].[HarborId] AS [HarborId], 
[Project1].[Description] AS [Description], 
[Project1].[C2] AS [C1], 
[Project1].[ShipId] AS [ShipId], 
[Project1].[HarborId1] AS [HarborId1], 
[Project1].[Description1] AS [Description1], 
[Project1].[C1] AS [C2], 
[Project1].[CrewMemberId] AS [CrewMemberId], 
[Project1].[ShipId1] AS [ShipId1], 
[Project1].[RankId] AS [RankId], 
[Project1].[ClearanceId] AS [ClearanceId], 
[Project1].[Description2] AS [Description2], 
[Project1].[RankId1] AS [RankId1], 
[Project1].[Description3] AS [Description3], 
[Project1].[ClearanceId1] AS [ClearanceId1], 
[Project1].[Description4] AS [Description4]
FROM ( SELECT 
    [Extent1].[HarborId] AS [HarborId], 
    [Extent1].[Description] AS [Description], 
    [Join3].[ShipId1] AS [ShipId], 
    [Join3].[HarborId] AS [HarborId1], 
    [Join3].[Description1] AS [Description1], 
    [Join3].[CrewMemberId] AS [CrewMemberId], 
    [Join3].[ShipId2] AS [ShipId1], 
    [Join3].[RankId1] AS [RankId], 
    [Join3].[ClearanceId1] AS [ClearanceId], 
    [Join3].[Description2] AS [Description2], 
    [Join3].[RankId2] AS [RankId1], 
    [Join3].[Description3] AS [Description3], 
    [Join3].[ClearanceId2] AS [ClearanceId1], 
    [Join3].[Description4] AS [Description4], 
    CASE WHEN ([Join3].[ShipId1] IS NULL) THEN CAST(NULL AS int) WHEN ([Join3].[CrewMemberId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1], 
    CASE WHEN ([Join3].[ShipId1] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2]
    FROM  [dbo].[Harbors] AS [Extent1]
    LEFT OUTER JOIN  (SELECT [Extent2].[ShipId] AS [ShipId1], [Extent2].[HarborId] AS [HarborId], [Extent2].[Description] AS [Description1], [Join2].[CrewMemberId], [Join2].[ShipId2], [Join2].[RankId1], [Join2].[ClearanceId1], [Join2].[Description2], [Join2].[RankId2], [Join2].[Description3], [Join2].[ClearanceId2], [Join2].[Description4]
        FROM  [dbo].[Ships] AS [Extent2]
        LEFT OUTER JOIN  (SELECT [Extent3].[CrewMemberId] AS [CrewMemberId], [Extent3].[ShipId] AS [ShipId2], [Extent3].[RankId] AS [RankId1], [Extent3].[ClearanceId] AS [ClearanceId1], [Extent3].[Description] AS [Description2], [Extent4].[RankId] AS [RankId2], [Extent4].[Description] AS [Description3], [Extent5].[ClearanceId] AS [ClearanceId2], [Extent5].[Description] AS [Description4]
            FROM   [dbo].[CrewMembers] AS [Extent3]
            INNER JOIN [dbo].[Ranks] AS [Extent4] ON [Extent3].[RankId] = [Extent4].[RankId]
            LEFT OUTER JOIN [dbo].[Clearances] AS [Extent5] ON [Extent3].[ClearanceId] = [Extent5].[ClearanceId] ) AS [Join2] ON [Extent2].[ShipId] = [Join2].[ShipId2] ) AS [Join3] ON [Extent1].[HarborId] = [Join3].[HarborId]
)  AS [Project1]
ORDER BY [Project1].[HarborId] ASC, [Project1].[C2] ASC, [Project1].[ShipId] ASC, [Project1].[C1] ASC
于 2012-08-01T21:19:56.437 に答える
8
query.Include(entity => entity.Ships);
query.Include(entity => entity.Ships.Select(s => s.CrewMembers));
query.Include(entity => entity.Ships.Select(s => s.CrewMembers.Select(cm => cm.Rank)));
query.Include(entity => entity.Ships.Select(s => s.CrewMembers.Select(cm => cm.Clearance)));

まず第一に、あなたはそれが でなければならないことを知っていますよquery = query.Include(...).Include(...)ね?

最後の2を実行している限り、最初の2は必要ありません。2番目の船と乗組員の両方が2番目からロードされます。これを試しましたか?

//query.Include(entity => entity.Ships);
//query.Include(entity => entity.Ships.Select(s => s.CrewMembers));
query = query.Include(entity => entity.Ships.Select(s => s.CrewMembers.Select(cm => cm.Rank)))
    .Include(entity => entity.Ships.Select(s => s.CrewMembers.Select(cm => cm.Clearance)));

また、いつでもSQLプロファイラーを起動して、efがdbに送信しているクエリを正確に確認できます。3 番目と 4 番目のインクルードのみを実行した場合、グラフ内の異なるオブジェクトのプロパティ値を交換するバグはないと思います。

于 2012-07-26T22:23:36.150 に答える
0

現状では、MySQLConnector / NETを使用している場合、EFを使用して1回のトリップでグラフを取得することはできません。Orcaleで確認されたこのバグレポートを参照してください。しなければならないことは

DbSet<Harbor> dbSet = context.Set<Harbor>();
IQueryable<Harbor> query = dbSet;
query = query.Include(entity => entity.Ships.Select(s => s.CrewMembers));
var Harbor = query.ToList();
foreach (var S in Harbor.Ships)
{
 foreach (var CM in S.CrewMembers)
 {
  CM.Rank = //get Rank where RankId == CM.RankId
  CM.Clearance = //get Clearance where ClearanceId == CM.ClearanceId
 }
}

このコードは例と一致していますが、明らかに単なる例であり、実際に実行するにはより適切な実装が必要です。.Includeこれは、グラフ全体を1回のトリップで取得するために、EF機能をオーバーロードまたは改善できるようになるまで、私が使用しているアプローチです。

複数のトリップでデータを取得することは理想的ではありませんが、機能します。

于 2012-07-29T23:54:51.663 に答える