首页 > 代码库 > EntityFramework Core 1.1有哪些新特性呢?

EntityFramework Core 1.1有哪些新特性呢?

前言

在项目中用到EntityFramework Core都是现学现用,及时发现问题及时测试,私下利用休闲时间也会去学习其他未曾遇到过或者用过的特性,本节我们来讲讲在EntityFramework Core 1.1中出现了哪些新特性供我们使用。

EntityFramework Core 1.1新特性探讨

DbSet.Find

在EF 6.x中也有此方法的实现,在EF Core 1.1中也同样对此方法进行了实现,为什么要拿出来讲呢,当然也有其道理,我们一起来看看。在

        public virtual T Find(int Key)
        {            return _context.Set<T>().Find(Key);
        }

此时我们来查询Blog中主键等于1的数据。

    var blog1 = _blogRepository.Find(1);

此时我们通过SQL Server Profiler监控得到如下SQL。

技术分享

我们看到通过Find方法来查询主键等于1的数据时,声明了一个变量然后再来进行设置变量值进行查询,没毛病,上述我们是直接通过Find方法来实现,下面我们通过其他几种方法来实现。如下:

        public T GetSingle(int id)
        {            return _context.Set<T>().FirstOrDefault(x => x.Id == id);
        }
 var blog = _blogRepository.GetSingle(1);

技术分享

此时和上述Find方法执行的SQL无任何区别,我们先别着急下结论,我们再来通过lambda表达式来实现看看。

        public T GetSingle(Expression<Func<T, bool>> predicate)
        {            return _context.Set<T>().FirstOrDefault(predicate);
        }
 var blog = _blogRepository.GetSingle(d => d.Id == 1);

此时我们再来看看生成的SQL语句。

技术分享

此时生成的SQL语句没有声明变量看起来非常清爽,同时看过dudu老大刚不久写过在EF Core中我们声明的的lambda表达式中的参数就是我们查询表的别名,确实是如此,不知道你发现了没有。既然以上有多种实现且利用lambda表达式实现更加清爽,那么为何还要搞出一个Find方法呢,请继续往下看。

  var blog = _blogRepository.GetSingle(d => d.Id == 1);  var blog1 = _blogRepository.Find(1);

当我们第一次查询了主键等于1的数据时,我们第二次通过Find方法再来进行查询时通过监控SQL Server Profiler,你会发现并未生成任何SQL语句,这说明什么呢,说明EF Core团队给出Find方法的目的在于:当实体已经被加载到上下文中时,我们通过Find方法再去查询数据时此时不会再去数据库中进行查询。所以当我们利用主键查询数据时利用Find方法会减少对数据库的多次请求。

ICollection<T>(集合类型映射支持)

在之前EF版本中我们都是进行如下声明字段

技术分享

    public class Blog : IEntityBase
    {        public virtual int Id { get; set; }        public virtual string Name { get; set; }        public virtual string Url { get; set; }        public virtual ICollection<Post> Posts { get; set; }
    }

技术分享

我们知道在EF Core中已经不存在延迟加载这一概念,所以请用了EF Core的童鞋将virtual关键字去掉。同时我们在映射集合时一直以来都统一用的ICollection<T>,但是在EF Core中不再有此局限性,我们进行如下定义:

技术分享

    public class Blog : IEntityBase
    {        public int Id { get; set; }        public string Name { get; set; }        public string Url { get; set; }        public IEnumerable<Post> Posts { get; set; }
    }

技术分享

技术分享 

通过如上我们知道现在支持了IEnumerable<T>集合的映射。当然这里有个前提,其具体集合类必须实现ICollection接口,否则EntityFramework Core将无法进行填充。

Mapping to Fileds(映射到字段)

这个特性应该是前所未有,只有在EF Core 1.1中才出现,我们详细讲解下Backing Fileds(我们暂且将其翻译为返回字段)特性。自从有了如下自动属性的出现,就方便了我们许多。

 public string Url { get; set; }

什么是返回字段(Backing Fileds)特性,我们先看下原始为字段配置属性的情况如下:

技术分享

private string _url;public string Url
{     get { return _url; }     set { _url = value; }
}

技术分享

Backing Fileds特性允许EF Core读或者写数据到字段中而不是属性中。也就是说如上EF Core将数据读写到_url字段中而不是Url中。默认情况下满足以下四种规则都会配置成Backing Fileds。

  • _<camel-cased property name>

  • _<property name>

  • m_<camel-cased property name>

  • m_<property name>

比如属性为UserName,那么对应的Backing Fileds则依次是:_userName,_UserName,m_userName,m_UserName。配置Backing Fileds后,当从数据库查询类实例后将直接将其对应数据写到字段中,在其他时候当EF Core需要读或者写值时有可能使用属性,例如EF需要更新一个属性上的值时,此时将使用属性的set访问器,如果属性仅仅只是只读,那么将值写到字段中。例如如下配置Backing Fileds即_validateUrl。

技术分享

class MyContext : DbContext
{    public DbSet<Blog> Blogs { get; set; }    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.Url)
            .HasField("_validatedUrl");
    }
}public class Blog
{    private string _validatedUrl;    public int BlogId { get; set; }    public string Url
    {        get { return _validatedUrl; }
    }    public void SetUrl(string url)
    {        using (var client = new HttpClient())
        {            var response = client.GetAsync(url).Result;
            response.EnsureSuccessStatusCode();
        }

        _validatedUrl = url;
    }
}

技术分享

我们也可以在映射中配置使用属性还是字段,如下:

modelBuilder.Entity<Blog>()
    .Property(b => b.Url)
    .HasField("_validatedUrl")
    .UsePropertyAccessMode(PropertyAccessMode.Field);

若我们在实体中没有属性,此时我们可以通过字段来存储数据。我们通过映射中的Porperty(...)来指定字段名称,若没有属性,此时EF Core将会查找字段,如下:

技术分享

class MyContext : DbContext
{    public DbSet<Blog> Blogs { get; set; }    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property("_validatedUrl");
    }
}public class Blog
{    private string _validatedUrl;    public int BlogId { get; set; }    public string GetUrl()
    {        return _validatedUrl; 
    }    public void SetUrl(string url)
    {        using (var client = new HttpClient())
        {            var response = client.GetAsync(url).Result;
            response.EnsureSuccessStatusCode();
        }

        _validatedUrl = url;
    }
}

技术分享

讲了这么多Backing Fileds特性,不知道看到本篇文章的你清楚了它的作用是什么,为什么要提出Backing Fileds特性,它存在的价值或者说用途是做什么呢,就我个人的理解的话,提出Backing Fileds的多数场景在:如果属性只读,我们需要通过其他逻辑操作来获取其值,但是我们没有一个桥梁来赋予其值,此时我们就需要Backing Fileds来完成。希望看到此文的你有更多见解的话,请留下评论,一起探讨。这里我们结合上述IEnumerable<T>来进一步讲解Backing Fileds。我们在Blog类中是如下定义。

技术分享

    public class Blog : IEntityBase
    {        public int Id { get; set; }        public string Name { get; set; }        public string Url { get; set; }        public IEnumerable<Post> Posts { get; set; }
    }

技术分享

我们知道对于导航属性Posts更多的是通过Inlcude来查询出Posts,所以在这里我们完全不需要set访问器以便减少对Posts反编译为Set方法,我们完全可以改造如下:

 public IEnumerable<Post> Posts { get; } =  new List<Post>();

话又说回来了,如果我们万一需要对Post进行一些操作,那么在这种情况下该如何是好呢,此时我们通过暴露IEnumerable<Blog>导航属性,然后借助该导航属性的Backing Fileds来对Post进行操作,改造如下:

技术分享

    public class Blog : IEntityBase
    {        private readonly List<Post> _posts = new List<Post>();        public int Id { get; set; }        public string Name { get; set; }        public string Url { get; set; }        public IEnumerable<Post> Posts => _posts;        public void AddPost(Post post)
        {            // Do some buisness your logic             _posts.Add(post);
        }
    }

技术分享

我们实际来操作一下,查询Blog数据以及导航属性Post数据。

技术分享

        public virtual IEnumerable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties)
        {
            IQueryable<T> query = _context.Set<T>();            foreach (var includeProperty in includeProperties)
            {
                query = query.Include(includeProperty);
            }            return query.AsEnumerable();
        }

技术分享

我们进行如下查询:

 var blog = _blogRepository.GetSingle(d => d.Id == 1, d => d.Posts);

我们上述稍微改造了一下,为了以免查询出现错误,测试查询一下,如下,没毛病。

技术分享 

显式加载(Explicit Loading) 

貌似显式加载没有什么应用的场景,不知道是否是应对某些特定的场景而给,它只是加载被上下文跟踪实体的导航属性,通过Include我们也可以实现,如下:

            var blog = _efCoreContext.Set<Blog>().Find(1);
            _efCoreContext.Entry(blog).Collection(b => b.Posts).Load();
            _efCoreContext.Entry(blog).Reference(b => b.Posts).Load();

连接弹性(Connection resiliency) 

所谓的连接弹性则是执行数据库命令失败时我们可以重试,我们可以在OnConfiguring或者Startup.cs中设置,如下:

技术分享

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(            "connection string",
            options => options.EnableRetryOnFailure());
}

技术分享

SQL Server内存优化表支持

内存优化表是SQL Server的一个特性,它将整个表驻留在内存中,在磁盘上保留着对表的副本,主要是用于持久化,在数据库恢复时(比如重启)在内存优化表中的数据从磁盘上仅仅只是进行读取。比如对Blog表进行内存优化设置,如下:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ForSqlServerIsMemoryOptimized();
}

将实体映射到内存优化中的表,当使用EF Core基于我们的模型创建数据库时,此时这些实体也将在内存优化表中创建一份。

简化服务更换(Simplify switch services)

在EF Core 1.0中就可以实现服务更换,但是略显复杂,在EF Core 1.1中替换服务类似于依赖注入一样,如下:

技术分享

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlServer("connection string");

    optionsBuilder.ReplaceService<SqlServerTypeMapper, MyCustomSqlServerTypeMapper>();
}

技术分享

在EF 6.x之前版本中因为导航属性的存在很容易导致循环引用,所以对于EF Core同样是如此我们需要在Startup.cs中忽略循环引用,如下:

 services.AddMvc()
        .AddJsonOptions(
            options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );


EntityFramework Core 1.1有哪些新特性呢?