如何用Linq和EntityFramework6

本文关键字:EntityFramework6 Linq 何用 | 更新日期: 2023-09-27 17:59:26

我有一个用c#、asp.net-mvc-5和entity-framework-6编写的项目,我有以下类:

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。表Professions中有IdDescription列。

这是我的示例项目:

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方法中,我有以下代码片段来显示数据:

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实现这一点

如何用Linq和EntityFramework6

第一级解决方案是将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

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

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

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

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

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

顺便说一句,我最初打印person.GetType().Name的想法有点破碎。EF子类化了你的类,所以你得到的类型和名称将是丑陋的mashup。第三个选项通过显式设置值来解决此问题。