前言
在项目中用到EntityFramework Core都是现学现用,及时发现问题及时测试,私下利用休闲时间也会去学习其他未曾遇到过或者用过的特性,本节我们来讲讲在EntityFramework Core 1.1中出现了哪些新特性供我们使用。
EntityFramework Core 1.1新特性探讨
DbSet.Find
在EF 6.x中也有此方法的实现,在EF Core 1.1中也同样对此方法进行了实现,为什么要拿出来讲呢,当然也有其道理,我们一起来看看。在仓储中我们实现Find这个方法,如下:
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 );
总结
本节我们比较详细的讲解了EF Core 1.1中新添加或改善的特性,我们重点讲述了Backing Fileds特性。