EF Core 2.1随.NET Core 2.1一起发布,本篇文章总结一下EF Core的新增功能,先从简单的开始说。
一、延迟加载
延迟加载不用介绍了吧,直接看一下怎样配置吧。EF Core 2.1默认是不允许延迟加载的,想要使用这个特性必须调用UseLazyLoadingProxies方法,这个扩展方法在 包中。
在Startup中配置:
public void ConfigureServices(IServiceCollection services){ services.AddDbContext(options => { options.UseLazyLoadingProxies().UseSqlServer("yourConnectionString"); });}
也可在DbContext中重写OnConfiguring方法中配置:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){ optionsBuilder.UseLazyLoadingProxies().UseSqlServer("yourConnectionString");}
配置好后在实体类中对应的属性上加上 virtual 关键字就OK,用法和EF6没区别。除了加 virtual 标记外,EF Core 2.1还可以通过 ILazyLoader 类型的对象来实现延迟加载,实现如下:
public class Person{ public Person() { } public Person(ILazyLoader lazyLoader) { LazyLoader = lazyLoader; } public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } private ILazyLoader LazyLoader { get; set; } private ICollection_children; public virtual ICollection Children { get => LazyLoader?.Load(this, ref _children); set => _children = value; }}
ILazyLoader在 Microsoft.EntityFrameworkCore.Abstractions 程序集中。按照官方文档所述,除了注入 ILazyLoader 类型使用LazyLoader外还可以注入Action<object,string>类型来实现LazyLoader,但我尝试的时候报错,如果有尝试成功的希望能在留言区里留言,多谢。
二、支持GroupBy
这个特性就不多说了,使用方法和EF6没区别。
三、支持TransactionScope
这个特性也和EF6一样,终于可以随时随地使用事务了。
四、Data Seeding
这个功能是用来初始化数据的,在进行数据库迁移时,EF会往数据库中插入一些数据,实现如下:
public class PersonConfig : IEntityTypeConfiguration{ public void Configure(EntityTypeBuilder builder) { builder.HasData(new Person[] { new Person { Id = 1, Name ="张三", Age = 30 } }); }}
使用EntityTypeBuilder下的HasData就可以实现该功能。但令人感到奇怪的是,就算Id是自增长的EF也会要求Id有值!而且也不支持同时插入子表的数据。
public class PersonConfig : IEntityTypeConfiguration{ public void Configure(EntityTypeBuilder builder) { builder.HasData(new Person[] { new Person { Id = 1, Name ="张三", Age = 30, Children = new List { new Child { Id = 1, Name = "小张三", Age = 5, PersonId = 1 }, new Child { Id = 2, Name = "小小张三", Age = 1, PersonId = 1 } } } }); } }
上面的代码,在进行数据迁移时,Children的数据不会插入到数据库中,不知道以后是否会支持关联属性的数据导入。
五、值转换
这个功能简单来说就是将属性的类型转换成数据库中的类型(比如枚举转换成字符串),实例中可以这样写:
public class Person{ public Person() { } public Person(ILazyLoader lazyLoader) { LazyLoader = lazyLoader; } public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public Gender Gender { get; set; } private ILazyLoader LazyLoader { get; set; } private ICollection_children; public virtual ICollection Children { get => LazyLoader?.Load(this, ref _children); set => _children = value; }}public enum Gender{ 男, 女}
然后再配置一下:
public class PersonConfig : IEntityTypeConfiguration{ public void Configure(EntityTypeBuilder builder) { builder.Property(p => p.Gender).HasConversion(v => v.ToString(), v => (Gender)Enum.Parse(typeof(Gender), v)).IsRequired().HasMaxLength(2); }}
这里有个问题比较奇怪,我在配置类中加上这段配置并在HasData中给Gender赋值,使用Migration的时候发现如果不加 IsRequired 他居然不给我把Gender的数据给导入到数据库里面!!!
除了基本的枚举转字符串以外,EF Core还提供如下的转换类:
BoolToZeroOneConverter | 将布尔值转换为0或1 |
BoolToStringConverter | 将布尔值转换为字符串(Y或N) |
BoolToTwoValuesConverter | 将布尔值转换为指定的两个值(没搞明白干嘛用的) |
BytesToStringConverter | 将字节数组转换为Base64编码的字符串 |
CastingConverter | 从一种类型转换到另一种类型(可以被C#互相转换的类型) |
CharToStringConverter | char转为string |
DateTimeOffsetToBinaryConverter | DateTimeOffset转为二进制的64位的值 |
DateTimeOffsetToBytesConverter | DateTimeOffset转为字节数组 |
DateTimeOffsetToStringConverter | DateTimeOffset转为字符串 |
DateTimeToBinaryConverter | DateTime转为带有DateTimeKind的64位的值 |
DateTimeToStringConverter | DateTime转为字符串 |
DateTimeToTicksConverter | DateTime转为ticks |
EnumToNumberConverter | 枚举转数字 |
EnumToStringConverter | 枚举转字符串 |
GuidToBytesConverter | Guid转字节数组 |
GuidToStringConverter | Guid转字符串 |
NumberToBytesConverter | 数字转字节数组 |
NumberToStringConverter | 数字转字符串 |
StringToBytesConverter | 字符串转字节数组 |
TimeSpanToStringConverter | TimeSpan转字符串 |
TimeSpanToTicksConverter | TimeSpan转ticks |
上面的这些对象的使用方式如下:
var converter = new EnumToStringConverter();builder.Property(p => p.Gender).HasConversion(converter);
除了这种方式外,EF Core也支持直接指定类型,如:
builder.Property(p => p.Gender).HasConversion(string);
需要注意的是,不能将null进行转换,一个属性只能对应一个列做转换。
六、Query Types
这个功能用来查询数据库视图的。先创建个实体类:
public class Family{ public int ParentId { get; set; } public string ParentName { get; set; } public int ParentAge { get; set; } public int ChildId { get; set; } public string ChildName { get; set; } public int ChildAge { get; set; }}
根据实体类在数据库中创建视图就行(貌似Migration不支持创建视图),SQL我就不写了。然后创建个继承自 IQueryTypeConfiguration<> 的配置类,代码如下:
public class FamilyConfig : IQueryTypeConfiguration{ public void Configure(QueryTypeBuilder builder) { builder.ToView("Family_View"); }}
DbContext中使用 DbQuery<Family> 类型增加一个属性,运行下看看结果:
根据官方文档所述,该功能只能查询,不能增删改,不具有状态跟踪,不能包含具体查询类型的导航属性(既然不能有导航属性为啥这里有个lazyLoader...)。
本文总结的都是我认为有用的功能,EF Core 2.1新增的其他功能可以到移步至官方文档:https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.1
最后补充一个扩展方法,功能和EF6的AddFromAssembly方法相同。
private static bool IsIEntityTypeConfigurationType(Type typeIntf){ return typeIntf.IsInterface && typeIntf.IsGenericType && typeIntf.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>);}public static void ApplyConfigurationsFromAssembly(this ModelBuilder modelBuilder, Assembly assembly){ //筛选出继承自IEntityTypeConfiguration的类型 IEnumerabletypes = assembly.GetTypes().Where(t => !t.IsAbstract && t.GetInterfaces().Any(it => IsIEntityTypeConfigurationType(it))); Type typeModelBuilder = modelBuilder.GetType(); MethodInfo methodNonGenericApplyConfiguration = typeModelBuilder.GetMethods() .Where(m => m.IsGenericMethod && m.Name == nameof(ModelBuilder.ApplyConfiguration) && m.GetParameters().Any(s => s.ParameterType.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>))).First(); foreach (var type in types) { object entityTypeConfig = Activator.CreateInstance(type); //获取实体的类型 Type typeEntity = type.GetInterfaces().First(t => IsIEntityTypeConfigurationType(t)).GenericTypeArguments[0]; //通过MakeGenericMethod转换为泛型方法 MethodInfo methodApplyConfiguration = methodNonGenericApplyConfiguration.MakeGenericMethod(typeEntity); methodApplyConfiguration.Invoke(modelBuilder, new[] { entityTypeConfig }); }}
以上代码根据RuPeng.EFCore.Ext组件修改,该组件暂时不支持EF Core 2.1,GitHub已提交pr,不知道杨老师啥时候更新下。