【问题标题】:How to Concat Classes with Inheritance and same Table in Database with Linq and EntityFramework 6?如何使用 Linq 和 EntityFramework 6 在数据库中连接具有继承和相同表的类?
【发布时间】:2014-06-06 03:35:43
【问题描述】:

我有一个用 编写的项目,我有以下课程:

public class Profession
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }  
    public string Description { get; set; }
}

public class Person
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }  
    public string Name { get; set; }
}

public class Individuals : Person
{
    public string CPF { get; set; }
    public int Profession_Id { get; set; }
    [ForeignKey("Profession_Id")]
    public Profession Profession { get; set; }
}

public class Corporate : Person
{
    public string CNPJ { get; set; }
}

而我的DbContext 班级是:

public class Context : DbContext
{
    public Context() : base("AppConnectionString") { }

    public DbSet<Profession> Professions { get; set; }
    public DbSet<Person> People { get; set; }
    public DbSet<Individuals> Individuals { get; set; }
    public DbSet<Corporate> Corporations { get; set; }
}

在运行我的应用程序时,生成的数据库具有以下结构:
People 和列IdNameCNPJCPFDiscriminator。和表 ProfessionsIdDescription 列。

这是我的示例项目:

public class Program
{
    public static void Main(string[] args) {
        var context = new Context();
        GenerateDatabase(context);

        // display the data

        System.Console.ReadKey();
    }

    private static void GenerateDatabase(Context context) 
    {
        if (!context.Database.Exists()) 
        {
            context.Database.Create();

            var p1 = new Profession { Description = "Profission 1" };
            var p2 = new Profession { Description = "Profission 2" };
            context.Professions.Add(p1);
            context.Professions.Add(p2);

            context.SaveChanges();

            context.Individuals.Add(new Individuals { Name = "People 1", CPF = "CPF Teste 1", Profession_Id = p1.Id });
            context.Individuals.Add(new Individuals { Name = "People 2", CPF = "CPF Teste 2", Profession_Id = p2.Id });
            context.Corporations.Add(new Corporate { Name = "Person 1", CNPJ = "CNPJ Teste 1" });
            context.Corporations.Add(new Corporate { Name = "Person 2", CNPJ = "CNPJ Teste 2" });

            context.SaveChanges();
        }
    }
}

好吧,在 Main 方法中,我有以下代码 sn-p 来显示数据:

var items = context.People.OfType<Individuals>();
foreach (var item in items)
    System.Console.WriteLine(item.Id.ToString() + " | " + item.Name + " | " item.Profession.Description + " | " + item.GetType().Name);

var items2 = context.People.OfType<Corporation>();
foreach (var item in items2)
    System.Console.WriteLine(items2.Id.ToString() + " - " + items2.Name);

我需要一个串联来对结果进行排序。
在此示例中,按类的 Name 属性排序没有正确的结果。

+----+----------+--------------+--------------+
| ID | Name     | Profession   | Discriminator|
+----+----------+--------------+--------------+
|  1 | Person 1 | Profission 1 | Individuals  |
|  2 | Person 2 | Profission 1 | Individuals  |
|  3 | Person 1 |              | Corporate    |
|  4 | Person 2 |              | Corporate    |
+----+----------+--------------+--------------+

我需要收集这两种类型的记录以显示在查询页面中。所以试图Concat,被告知要做一个Union all

很快,我测试了类似的东西:

var items = context.People.OfType<Individuals>().Include(x => x.Profession).Concat<Individuals>(
    context.People.OfType<Corporate>().Select(x => new Corporate
    {
        Id = x.Id,
        Name = x.Name,
        CNPJ = x.CNPJ
    }));

items = items.OrderBy(x => x.Name);

但这是错误的,无法编译!

如何使用 Queryable 来做到这一点?

【问题讨论】:

    标签: c# asp.net-mvc-5 entity-framework-6 c# asp.net linq asp.net-mvc-5 entity-framework-6


    【解决方案1】:

    第一级解决方案是将IndividualsCorporate 都转换回基类,如下所示:

    var items = 
        context.Individuals.Select(x => (Person)x)
        .Concat(context.Corporations.Select(x => (Person)x))
        .OrderBy(x => x.Name);
    

    这将为您提供Person 对象的枚举,它可以是子类:IndividualsCorporate。转换为通用基类解决了兼容性问题,但现在您只能使用基类上可用的那些属性。如果您需要访问特定于一个或其他子类的属性(例如,Individuals 对象上的 Profession 引用),则需要对每个项目进行类型检查以确定它是哪种具体类型。

    简单展示:

    foreach (var person in items)
    {
        Console.WriteLine("{0,-6}{1,-12}{2}", person.Id, person.Name, person.GetType().Name);
    }
    

    上面的数据应该会显示出来:

    1     Person 1    Individuals
    3     Person 1    Corporate
    2     Person 2    Individuals
    4     Person 2    Corporate
    

    正如 cmets 中所讨论的,将对象转换为 Person 类型是在我的原始查询中的 Include() 语句之前完成的,当实体框架找不到 Profession 时,这会导致运行时出错People 类的属性。

    我看到了三种方法来处理这个问题:

    1. 让 EF 将其作为延迟加载处理。

    2. 添加一个操作,在转换之前强制数据进入内存

    3. 对结果使用其他类型。

    第一个选项是根本不在查询中包含Profession 属性,让实体框架为您完成工作。这是您期望只返回少量记录的快速脏查询的惰性方法。每当您第一次引用Individuals 对象的Profession 属性时,EF 都会查询数据库服务器。对于大量记录,这是一个坏主意。如果您有 1000 条 Individuals 记录,则需要额外运行 1000 条 SQL 查询。

    第二个选项如下所示:

    var items = 
        context.Individuals.Include("Profession").ToArray().Select(x => (Person)x)
        .Concat(context.Corporations.Select(x => (Person)x)
        .OrderBy(x => x.Name);
    

    表达式中间的 ToArray 操作将告诉 EF 停止构建查询并获取数据,并返回一个 Individuals[] 数组。不幸的是,这对于大型数据集来说效率很低。

    最后的选择:

    var items =
        context.Individuals.Select(x => new 
            { 
                Id = x.Id, 
                Name = x.Name, 
                Profession = x.Profession.Description, 
                Type = "Individual" 
            })
        .Concat(context.Corporations.Select(x => new 
            { 
                Id = x.Id, 
                Name = x.Name, 
                Profession = (string)null, 
                Type = "Corporation" 
            }))
        .OrderBy(x => x.Name).ThenBy(x => x.Id);
    

    我使用了兼容的匿名类型 - 相同的字段名称和类型以相同的顺序 - 但您也可以使用特定的结果记录结构或类。如果所有处理都在本地进行,请使用匿名类型;如果您要在方法之外传递信息,请使用结果结构/类。

    总的来说,我认为最后一个是 3 个中最好的。使用中间类型来存储结果,与使用类型转换、强制数据进入内存等相比,你会得到更好的结果. 您还可以获得更高效的查询,因为只有您在查询中请求的那些字段才会从服务器返回。当您只需要包含大量列的表中的两列时,这很好。当您使用包含 50 多列的表格设计时,它会产生很大的不同。

    顺便说一句,我最初打印person.GetType().Name 的想法有点破灭。 EF 对您的类进行子类化,因此您得到的类型——以及名称——将是丑陋的混搭。第三个选项通过显式设置值来解决这个问题。

    【讨论】:

    • 嗨@Corey,当foreach 运行时,会生成错误:Additional information: A specified Include path is not valid. The EntityType 'TestInheritedClasses.Person' does not declare a navigation property with the name 'Profession'。这发生在两种类型的 Linq 请求中。你能帮帮我吗?
    • 我将其包括在内,因为它在您的原始选择查询中。我假设您已经让Profession 类链接正常工作。你现在修好了吗?
    • 对不起,我没听懂你的意思。看到Individuals 类是唯一有属性Profession
    • 好的,似乎问题在于操作在后台发生的顺序。因为我们正在转换为 Person 并且基本类型没有指向 Profession 的链接,所以此时您需要中断 EF 流。我将更新以向您展示实现此目的的几种方法。
    • 太棒了!既然我选择了第三个选项!非常感谢你! +1 很好的解释。哎呀,我还有点要投票!对不起!但是,谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-20
    • 1970-01-01
    • 2015-07-18
    • 1970-01-01
    • 2011-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多