如何使用MySQL Connector / NET在深度为2的对象图上使用Entity Framework?

以下是Oracle确认的错误报告: http : //bugs.mysql.com/bug.php?id = 67183

情况

当我在我的存储库中使用.Include链时,我注意到我得到了奇怪的结果 – 主要是被查询的返回值来自错误的字段(例如,名称最终会出现在描述中 – 但在数据库中所有的值是正确的,它们只在查询后显示错误)。 我更改了名称,因此关系更加明显,但结构是一样的。 我不断为相关的CrewMember及其相对排名和清除得到错误的值。 看起来如果CrewMember中的字段名称与Rank相同,则Rank中该字段的值将变为CrewMember中的值。 例如,如果Rank有描述,CrewMember也是如此,那么CrewMember的Rank描述将是CrewMember的描述。

当由于MySQL Connector / NET sql提供程序无法正确形成join语句而导致类似字段定义时,entity framework无法在2的深度处形成格式良好的查询。

定义

这是一个为数据库表建模的类定义。 我正在使用C#ASP.NET MVC 3与Entity Framework 4.1和MySQL Connector / NET 6.5版

 public class Harbor { public int HarborId { get; set; } public virtual ICollection 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 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 CrewMembers { get; set; } public string Description { get; set; } } public class Clearance { public int ClearanceId { get; set; } public virtual ICollection CrewMembers { get; set; } public string Description { get; set; } } 

询问

这是查询数据库并具有查询和.Include调用的代码。

 DbSet dbSet = context.Set(); IQueryable 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关系时,以这种方式“向下钻取”似乎没有任何问题。 然而,当钻井中存在一对多的关系时,似乎会出现这个问题。 钻孔是必要的,以便加载。

第一个投影, entity => entity.Ships.Select(s => s.CrewMembers ,将返回与每艘船相关的CrewMembers列表。这将正确返回一个图表,其中一个港口包含一个船只列表,每个船只都有一个机组人员名单。

但是,第二个投影CrewMembers.Select(cm => cm.Rank ,实际上并没有返回图形的正确部分。字段开始混合,并且任何共享相同名称的字段都会因任何原因而默认为父字段这会导致结果不一致,更重要的是导致数据不好。没有错误的事实会使情况变得更糟,因为这只能通过运行时检查来确定。

如果有办法以某种方式从第一个投影获得强类型单个响应(而不是列表),则可能第二个没有必要。 就像现在一样,我认为问题在于第一次投射返回一个清单。 当第二个投影尝试基于该列表而不是单个对象进行投影时,会引入逻辑错误。

如果,而不是CrewMembers是ICollection,它只有一个CrewMember,那么这个嵌套投影实际上将返回正确的数据。 然而,这是这个问题的简化版本,不幸的是,几乎所有的测试似乎都是从我试过解决这个问题的各种博客,教程,post,文章和文档中完成的。

编辑

下面的测试是使用SQL Server和SqlClient作为提供程序进行的。 如果您使用的MySql提供程序存在一个错误,那么问题是无法通过SQL Server重现这一事实会引发一个问题,即为您的LINQ查询创建了错误的SQL。 看起来像在这个问题中的问题一样, MySql提供程序也出现了问题,无法用SqlClient / SQL Server重现。


我不断为相关的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 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 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 CrewMembers { get; set; } public string Description { get; set; } } public class Clearance { public int ClearanceId { get; set; } public virtual ICollection CrewMembers { get; set; } public string Description { get; set; } } public class MyContext : DbContext { public DbSet Harbors { get; set; } public DbSet Ships { get; set; } public DbSet CrewMembers { get; set; } public DbSet Ranks { get; set; } public DbSet Clearances { get; set; } } class Program { static void Main(string[] args) { Database.SetInitializer(new DropCreateDatabaseAlways()); using (var context = new MyContext()) { context.Database.Initialize(true); var harbor = new Harbor { Ships = new HashSet { new Ship { CrewMembers = new HashSet { 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 { 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 dbSet = context.Set(); IQueryable 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 
 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.你试过这个吗?

 //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 profiler以查看ef正在向db发送的查询。 如果你只运行第3和第4个包含,那么我不会指望会从图表中的不同对象交换属性值的错误。

就目前而言,使用MySQLConnector / NET时,无法使用EF在一次行程中检索图形。 使用Orcale查看此已确认的错误报告 。 必须做的是

 DbSet dbSet = context.Set(); IQueryable 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 EFfunction,以便在一次旅行中获得整个图形。

在多次旅行中获取数据并不理想,但是,它可以工作。