代码改变世界

EF Core 2.1 新特性

2018-06-03 15:42  天新8206  阅读(3993)  评论(0编辑  收藏  举报

EF Core 2.1随.NET Core 2.1一起发布,本篇文章总结一下EF Core的新增功能,先从简单的开始说。

一、延迟加载

延迟加载不用介绍了吧,直接看一下怎样配置吧。EF Core 2.1默认是不允许延迟加载的,想要使用这个特性必须调用UseLazyLoadingProxies方法,这个扩展方法在 Microsoft.EntityFrameworkCore.Proxies 包中

在Startup中配置:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<TestContext>(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<Child> _children;
    
    public virtual ICollection<Child> 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<Person>
{
    public void Configure(EntityTypeBuilder<Person> builder)
    {
        builder.HasData(new Person[]
        {
            new Person
            {
                Id = 1,
                Name ="张三",
                Age = 30
            }
        });
    }
}

使用EntityTypeBuilder下的HasData就可以实现该功能。但令人感到奇怪的是,就算Id是自增长的EF也会要求Id有值!而且也不支持同时插入子表的数据。

public class PersonConfig : IEntityTypeConfiguration<Person>
{
    public void Configure(EntityTypeBuilder<Person> builder)
    {
       builder.HasData(
new Person[] { new Person { Id = 1, Name ="张三", Age = 30, Children = new List<Child> { 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<Child> _children;

    public virtual ICollection<Child> Children
    {
        get => LazyLoader?.Load(this, ref _children);
        set => _children = value;
    }
}

public enum Gender
{
    男,
    女
}

然后再配置一下:

public class PersonConfig : IEntityTypeConfiguration<Person>
{
    public void Configure(EntityTypeBuilder<Person> 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<Person>();
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<Family>
{
    public void Configure(QueryTypeBuilder<Family> 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的类型
    IEnumerable<Type> types = 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,不知道杨老师啥时候更新下。