1. 程式人生 > >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中也同樣對此方法進行了實現,為什麼要拿出來講呢,當然也有其道理,我們一起來看看。在倉儲中我們實現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特性。