如何用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
和列Id
、Name
、CNPJ
、CPF
和Discriminator
。表Professions
中有Id
和Description
列。
这是我的示例项目:
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实现这一点
第一级解决方案是将Individuals
和Corporate
都强制转换回基类,如下所示:
var items =
context.Individuals.Select(x => (Person)x)
.Concat(context.Corporations.Select(x => (Person)x))
.OrderBy(x => x.Name);
这将为您提供Person
对象的枚举,这些对象可以是子类:Individuals
或Corporate
。转换为公共基类解决了兼容性问题,但现在您只能使用基类上可用的那些属性。如果您需要访问特定于一个或其他子类的属性(例如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
属性时在运行时导致错误。
我可以看到三种方法来处理这个问题:
让EF将其作为惰性负载处理。
在转换之前,添加一个强制数据进入内存的操作
使用其他类型的结果。
第一个选项是根本不在查询中包含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。第三个选项通过显式设置值来解决此问题。