简体   繁体   English

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

[英]How can I use Entity Framework on an object graph past a depth of 2 with MySQL Connector / NET?

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

Situation 情况

When using an .Include chain inside of my repository, I noticed that I was getting strange results - mostly that the values queried that were being returned were from the wrong fields (name would end up in description for example - but in the database all the values are correct, they only show up wrong after the query). 当我在我的存储库中使用.Include链时,我注意到我得到了奇怪的结果 - 主要是被查询的值是从错误的字段返回的(例如,名称最终会在描述中出现 - 但在数据库中所有的值是正确的,它们只在查询后显示错误)。 I changed the names so the relationships are more obvious, but the structure is the same. 我更改了名称,因此关系更加明显,但结构是一样的。 I keep getting the wrong values for the associated CrewMember and their relative Rank and Clearance. 我一直在为相关的CrewMember及其相对排名和清除得到错误的值。 It seems if there is a field name which is the same in CrewMember as Rank, then the value of that field in Rank becomes what the value was in CrewMember. 看起来如果CrewMember中的字段名称与Rank相同,则Rank中该字段的值将变为CrewMember中的值。 For example, if Rank had a description, and so did CrewMember, then the description of Rank for the CrewMember would be the CrewMember's description. 例如,如果Rank有描述,CrewMember也是如此,那么CrewMember的Rank描述将是CrewMember的描述。

Entity Framework fails to make well formed queries past a depth of 2 when there are similar fields defined as a result of the MySQL Connector/NET sql provider failing to properly form join statements. 当由于MySQL Connector / NET sql提供程序无法正确形成join语句而导致类似字段定义时,实体框架无法在2的深度进行格式良好的查询。

Definitions 定义

This is a class definition which models a database table. 这是一个为数据库表建模的类定义。 I am using C# ASP.NET MVC 3 with the Entity Framework 4.1 and the MySQL Connector/NET version 6.5 我正在使用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<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; }
}

Query 询问

This is the code which queries the database and has the query and .Include calls. 这是查询数据库并具有查询和.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)));

Are these .Include calls well formed? 这些.Include电话是否形成良好? Did I miss something? 我错过了什么?

This is rather complex, so if you have any questions please let me know in comments and I will try to clarify anything I may have left out. 这是相当复杂的,所以如果您有任何问题,请在评论中告诉我,我会尽力澄清我可能遗漏的任何内容。

How can I use Entity Framework to get a well formed query on an object graph past a depth of 2 when using MySQL Connector / NET? 当使用MySQL Connector / NET时,如何使用Entity Framework在深度为2的对象图上获得格式良好的查询?

Edits 编辑

Here is the generated query: 这是生成的查询:

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

Clarification 澄清

Using include on 1-1 relationships poses no problem when "drilling down" in this fashion it seems. 使用包含在1-1关系时,以这种方式“向下钻取”似乎没有任何问题。 However, the issue seems to arise when there are 1-many relations as part of the drilling. 然而,当钻探中存在一对多的关系时,似乎会出现这个问题。 The drilling is necessary in order to eager load. 钻孔是必要的,以便加载。

The first projection, entity => entity.Ships.Select(s => s.CrewMembers , will return a list of CrewMembers which are related to each ship. This properly returns the graph where a harbor contains a list of ships, each with a list of crew members. 第一个投影, entity => entity.Ships.Select(s => s.CrewMembers ,将返回与每艘船相关的CrewMembers列表。这将正确返回一个图表,其中一个港口包含一个船只列表,每个都有一个船员名单。

However, the second projection CrewMembers.Select(cm => cm.Rank , does not in fact return the proper piece of the graph. Fields begin to be mixed, and any fields sharing the same name will default for whatever reason to the parent field. This results in inconsistent results and more importantly bad data. The fact that no errors are thrown makes it worse, as this can only be determined through runtime inspection. 但是,第二个投影CrewMembers.Select(cm => cm.Rank ,实际上并没有返回图形的正确部分。字段开始混合,并且任何共享相同名称的字段都会因任何原因而默认为父字段这会导致结果不一致,更重要的是导致数据不好。没有错误的事实会使情况变得更糟,因为这只能通过运行时检查来确定。

If there were a way to somehow get a strongly typed single response (as opposed to a list) from the first projection, perhaps the second would not be necessary. 如果有办法以某种方式从第一个投影获得强类型的单个响应(而不是列表),则可能第二个没有必要。 As it is now, I believe that the issue lies in the first projection returning a list. 就像现在一样,我认为问题在于第一个投影返回一个列表。 When the second projection attempts to project based on that list instead of from a single object, the logical error is introduced. 当第二个投影尝试基于该列表而不是单个对象进行投影时,会引入逻辑错误。

If, instead of CrewMembers being an ICollection, it was only one CrewMember, then this nested projection will in fact return the correct data. 如果,而不是CrewMembers是ICollection,它只有一个CrewMember,那么这个嵌套投影实际上将返回正确的数据。 However, that is a simplified version of this problem and unfortunately it is what almost all testing seems to have been done on from the various blogs, tutorials, posts, articles, and documents which I reviewed trying to solve this issue. 然而,这是这个问题的简化版本,不幸的是,几乎所有测试似乎都是从我审查过的各种博客,教程,帖子,文章和文档中完成的,试图解决这个问题。

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

First of all, you know it has to be query = query.Include(...).Include(...) , right? 首先,你知道它必须是query = query.Include(...).Include(...) ,对吧?

As long as you are executing the last 2, you don't need the first 2. Both Ships and CrewMembers will be loaded from the second 2. Have you tried just this? 只要您执行最后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)));

Also, you can always fire up sql profiler to see exactly what query ef is sending to the db. 此外,您始终可以启动sql profiler以查看发送到db的确切查询。 I wouldn't expect a bug that would swap property values from different objects in the graph if you only run the 3rd and 4th Includes. 如果你只运行第3和第4个包含,我不会指望会在图表中交换不同对象的属性值的错误。

Edit 编辑

The test below was made with SQL Server and SqlClient as provider. 下面的测试是使用SQL Server和SqlClient作为提供程序进行的。 The fact that the problem is not reproducable with SQL Server raises the question if the MySql provider you are using has a bug in that is creates incorrect SQL for your LINQ query. 如果您使用的MySql提供程序有一个错误,那么问题是无法通过SQL Server重现这一问题会引发一个问题,即为您的LINQ查询创建不正确的SQL。 It looks like the same problem as in this question where the problem occured with a MySql provider as well and couldn't be reproduced with SqlClient /SQL Server. 看起来像这个问题中的问题一样, MySql提供程序也出现问题,无法用SqlClient / SQL Server重现。


I keep getting the wrong values for the associated CrewMember and their relative Rank and Clearance. 我一直在为相关的CrewMember及其相对排名和清除得到错误的值。 It seems if there is a field name which is the same in CrewMember as Rank, then the value of that field in Rank becomes what the value was in CrewMember. 看起来如果CrewMember中的字段名称与Rank相同,则Rank中该字段的值将变为CrewMember中的值。 For example, if Rank had a description, and so did CrewMember, then the description of Rank for the CrewMember would be the CrewMember's description. 例如,如果Rank有描述,CrewMember也是如此,那么CrewMember的Rank描述将是CrewMember的描述。

I have tested the example in bold (with EF 4.3.1) and can't reproduce the problem: 我用粗体测试了这个例子(使用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();
            }
        }
    }
}

The output is: 输出是:

在此输入图像描述

According to your description in bold I should have: CrewMember 1 Description = "Rank A" and the same mess for the other 3 crew members. 根据你的粗体描述,我应该有: CrewMember 1 Description =“Rank A”和其他3名船员一样混乱。 But I haven't this. 但我没有这个。

Is something different in my test program compared to your code where you have the error? 与您遇到错误的代码相比,我的测试程序有什么不同吗?

Edit 编辑

The generated SQL for the query (see line var sqlString = query.ToString(); in source code above, the following is the content of sqlString ) is: 为查询生成的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

As it stands, it is not possible to retrieve the graph in one trip with EF when using the MySQLConnector/NET. 就目前而言,使用MySQLConnector / NET时,无法使用EF检索一次行程中的图形。 See this confirmed bug report with Orcale . 使用Orcale查看此已确认的错误报告 What must be done is to 必须做的是

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

This code is in line with the example, but is obviously just as an example and would need better implementation to actually run. 此代码与示例一致,但显然只是作为示例,需要更好的实现才能实际运行。 This is the approach I am using until I can overload or improve on the .Include EF functionality in order to get the whole graph in one trip. 这是我使用的方法,直到我可以重载或改进.Include EF功能,以便在一次旅行中获得整个图形。

Getting the data in multiple trips is not ideal, however, it works. 在多次旅行中获取数据并不理想,但是,它可以工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何为包含数据库连接器的程序集设置绝对路径? 带有实体框架4.1的MySQL Connector 6.4.3 - How do I set the absolute path for a assembly containing Database connector? MySQL Connector 6.4.3 with Entity Framework 4.1 实体框架如何与我的分离对象图一起使用? - How can I have Entity Framework work with my detached object graph? .NET-如何将实体框架(数据实体模型)用于WCF项目? - .NET - How can I use Entity Framework (Data Entity Model) to a WCF Project? 使用.NET 3.5实体框架,如何保存实体? - Using the .NET 3.5 Entity Framework how can I save an entity? 如何从实体框架实体(对象)创建列表? - How can I create a List off of an Entity Framework entity (object)? 如何从LuisResult对象获取实体的值? (带有Bot Framework .NET的LUIS) - How can I get the value of an entity from LuisResult object? (LUIS with Bot Framework .NET) C#中的MYSQL实体框架连接器 - MYSQL Entity framework connector in C# MySQL 连接器 6.7.4 和实体框架 5 异常 - MySQL connector 6.7.4 and Entity Framework 5 exceptions C#中带有MySQL连接器的实体框架 - Entity Framework with MySQL connector in c# MySQL实体框架连接器是否与OrderBy()不兼容? - Is the MySQL Entity Framework Connector Incompatible with OrderBy()?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM