Entity Framework Core 2.1 Preview 1 新增功能簡介


兩個星期前,微軟發布了EF Core 2.1 Preview 1,同時還發布了.NET Core 2.1 Preview 1ASP.NET Core 2.1 Preview 1;EF Core 2.1 Preview 1 除了許多小改進和超過100種產品錯誤修復之外,還包括幾個常用的新功能,今天我為您詳細介紹這些新功能的部分內容。

實體構造函數參數

EF.Core 2.1開始支持在實體的構造函數的實體中轉入參數,目前支持的類型如下:

  • 實體屬性
  • IOC容器中注冊的服務
  • 當前的DbContext
  • 當前實體的元數據

實體屬性

在某些情況下為了保證數據的安全性,將屬性改為只讀,在構造函數中傳遞屬性的值,框架通過參數與屬性匹配關系,將數據行中屬性的值作為參數傳遞給構造函數。

例如下面的實體:

    public class Order
    {
        public Order(int orderID, string customerID, DateTime? orderDate)
        {
            OrderID = orderID;
            CustomerID = customerID;
            OrderDate = orderDate;
        }

        public int OrderID { get; }
        
        public string CustomerID { get; }

        public DateTime? OrderDate { get; }

    }

其中參數與屬性的配置規則如下:

  • 參數的類型與屬性的類型一致;

  • 屬性名與參數名除首字母不區分大小寫之外,其它字符一致,並且可以使用 _m_做為前綴,使用OrderID屬性來舉例,存在如下匹配規則:

    屬性名 參數名
    OrderID OrderID
    OrderID orderID
    _OrderID orderID
    _OrderID OrderID
    m_OrderID OrderID
    m_OrderID OrderID

具體的匹配規則可以見Github上面的源代碼:https://github.com/aspnet/EntityFrameworkCore/blob/8965f0b91cf89e36abca8636d58420cbd26c22fd/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs#L37-L45
不過我認識后面四種模式有待斟酌的,在.Net開發規范,應該沒有人將公有的屬性名使用 _m_作為前綴。

IOC容器中注冊的服務

在實體的構造函數的中,可以將注冊的服務作為參數。

示例代碼:

    public class Order
    {
        private ILazyLoader _lazyLoader;

        public Order(ILazyLoader lazyLoader)
        {
            this._lazyLoader = lazyLoader;
        }

        public int OrderID { get; set; }
        
        public string CustomerID { get; set; }

        private ICollection<OrderDetail> _orderDetails;

        public ICollection<OrderDetail> OrderDetails
        {
            get => _lazyLoader.Load(this, ref _orderDetails);
            set => _orderDetails = value;
        }
    }

}

其中ILazyLoader是EF Core框架在容器中注冊的一個服務,通過實體的構造函數中傳入,實現導航屬性的賴加載(關於ILazyLoader的具體使用方式在本章的下一節中講解)。

當前的DbContext

在實體的構造函數的參數中,將當前的DbContext作為參數。

示例代碼:

    public class Order
    {
        private NorthwindContext _northwindContext;

        public Order(NorthwindContext northwindContext)
        {
            this._northwindContext = northwindContext;
        }

        public int OrderID { get; set; }
        
        public string CustomerID { get; set; }

        private ICollection<OrderDetail> _orderDetails;

        [NotMapped]
        public ICollection<OrderDetail> OrderDetails
        {
            get
            {
                if (this._orderDetails == null)
                    this._orderDetails = this._northwindContext.Set<OrderDetail>()
                        .Where(item => item.OrderID == this.OrderID).ToList();
                return this._orderDetails;
            }
            set => _orderDetails = value;
        }
    }

當前實體的元數據

在實體的構造函數的參數中,將當前實體的的IEntityType作為參數。

示例代碼:

    public class Order
    {

        private IEntityType _entityType;

        public Order(IEntityType entityType)
        {
            this._entityType = entityType;
        }

        public int OrderID { get; set; }
        
        public string CustomerID { get; set; }

        [NotMapped]
        public IEntityType EntityType
        {
            get { return this._entityType; }
        }

    }

如果實體存在多個構造函數,框架會選擇參數個數最多的那個;如果按參數個數優先選擇后,依然存在多個構造函數,則會拋異常。在當前體驗版本中,暫時無法直接支持自定義參數,不過在下一個發布版本中,會提供解決方案。

懶加載

懶加載是一個非常有爭論的功能激烈爭論的功能。雖然有些人認為它會導致性能下降或出現意想不到的Bug,但是不影響有些開發人員依舊喜歡它。EF Core 2.1 Preview 1增加了懶加載,提供了兩種實現方式。

使用ILazyLoader接口實現懶加載

在實體的構造函數中傳入ILazyLoader,在導航屬性中,使用接口的Load方法,實現導航屬性的數據加載。

示例代碼:

    public class Order
    {
        private ILazyLoader _lazyLoader;


        public Order(ILazyLoader lazyLoader)
        {
            this._lazyLoader = lazyLoader;
        }

        public int OrderID { get; set; }
        
        public string CustomerID { get; set; }

        public DateTime? OrderDate { get; set; }
   
        private ICollection<OrderDetail> _orderDetails;

        public ICollection<OrderDetail> OrderDetails
        {
            get => this._lazyLoader.Load(this, ref _orderDetails);
            set => _orderDetails = value;
        }
    }

通過代理類實現懶加載

這種方式,需要單獨安裝 Microsoft.EntityFrameworkCore.Proxies Nuget 包,它通過 Castle.Core 框架來生成代理類來實現對導航屬性的延遲加載。

啟用懶加載需要注意以下兩點:

  • 在配置中啟用懶加載;
  • 實體類不能是封閉(sealed)類,導航屬性必須是虛(virtual)屬性。

這種方式,在以前的博客我已經分享過,只不過當時還沒有發布,原文地址:Entity Framework Core 懶加載

值轉換

EF Core 2.1 允許您將插入數據庫的值自定義轉換邏輯。例如:將屬性的值進行加密與解密。

示例,將插入的值進行Base64編碼,在查詢的時候進行Base64解碼。

定義的UserInfo實體,用於保存用戶信息,屬性PhoneNumber表示用戶的手機號碼;為了用戶信息安全,需要將手機號碼進行加密后再保存到數據庫,只是為了達到演示的目的,我們采用Base64進行編碼。

     public class UserInfo
     {
         public int Id { get; set; }

         public string PhoneNumber { get; set; }
     }

Base64ValueConverter表示進行值轉換的具體邏輯,繼承自泛型ValueConverter<string, string>,具體的邏輯非常簡單,不再敘述。

    public class Base64ValueConverter : ValueConverter<string, string>
    {
        public Base64ValueConverter() : base((v) => ToBase64(v), (v) => FromBase64(v))
        {
        }
        private static string ToBase64(string input)
        {
            if (string.IsNullOrEmpty(input))
                return input;

            var bytes = Encoding.UTF8.GetBytes(input);
            return Convert.ToBase64String(bytes);
        }

        private static string FromBase64(string input)
        {
            if (string.IsNullOrEmpty(input))
                return input;

            var bytes = Convert.FromBase64String(input);
            return Encoding.UTF8.GetString(bytes);
        }
    }

SampleDbContext表示數據上下文,在OnModelCreating方法中,定義UserInfo實體的PhoneNumber屬性需要使用Base64進行值轉換。

    public class SampleDbContext : DbContext
    {

        public DbSet<UserInfo> Users { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var sqlConnectionStringBuilder = new SqlConnectionStringBuilder
            {
                DataSource = "*******",
                InitialCatalog = "ValueConverterTest",
                UserID = "sa",
                Password = "sa"
            };
            optionsBuilder.UseSqlServer(sqlConnectionStringBuilder.ConnectionString);

            base.OnConfiguring(optionsBuilder);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<UserInfo>().Property(e => e.PhoneNumber).HasConversion(new Base64ValueConverter());
        }
    }

下面的代碼是對預期的結果進行單測。

    [Fact]
    public async void ValueConverter_Test()
    {
        string phoneNumber = "13658556925";

        using (SampleDbContext dbContext = new SampleDbContext())
        {
            await dbContext.Database.EnsureDeletedAsync();

            await dbContext.Database.EnsureCreatedAsync();

            dbContext.Users.Add(new UserInfo()
            {
                PhoneNumber = phoneNumber
            });

            await dbContext.SaveChangesAsync();
        }

        UserInfo user;

        using (SampleDbContext dbContext = new SampleDbContext())
        {
            user = dbContext.Users.Single();
        }

        Assert.NotNull(user);
        Assert.Equal(phoneNumber, user.PhoneNumber);
    }

運行后,查詢數據庫中保存的結果:

手機號碼 13658556925 在數據庫保存的值是 MTM2NTg1NTY5MjU=

使用值轉換的另一個常用場景是將枚舉的值存儲為字符串類型,默認情況下,枚舉的值保存到數據庫中是通過整數表示的,如果需要在值存儲為字符串類型。

   public enum CategoryName
   {
       Clothing,
       Footwear,
       Accessories
   }
   public class Category
   {
       public int Id { get; set; }
 
       public CategoryName Name { get; set; }
   }

實體CategoryName屬性是用枚舉表示的,如果在存儲時用字符串類型表示,我們可以在DbContextOnModelCreating方法中使用如下代碼,

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Category>().Property(e => e.Name).HasConversion<string>();
    }

EF Core 默認提供常用類型的轉換,我們只需指定存儲的類型即可,框架默認支持的類型轉換映射表如下:

源類型 目標類型
enum intshortlongsbyteuintushortulongbytedecimaldoublefloat
bool intshortlongsbyteuintushortulongbytedecimaldoublefloat
bool string
bool byte[]
char string
char intshortlongsbyteuintushortulongbytedecimaldoublefloat
char byte[]
Guid byte[]
Guid string
byte[] string
string byte[]
DateTimeDateTimeOffsetTimeSpan stringlongbyte[]
intshortlongsbyteuintushortulongbytedecimaldoublefloat stringbyte[]

LINQ GroupBy 解析

在版本2.1之前,在EF Core中,GroupBy 表達式運算符總是在內存中進行計算的。現在支持在大多數情況下將其轉換為SQL GROUP BY子句。

var query = context.Orders
    .GroupBy(o => new { o.CustomerId, o.EmployeeId })
    .Select(g => new
        {
          g.Key.CustomerId,
          g.Key.EmployeeId,
          Sum = g.Sum(o => o.Amount),
          Min = g.Min(o => o.Amount),
          Max = g.Max(o => o.Amount),
          Avg = g.Average(o => Amount)
        });

相應的SQL解析如下所示:

SELECT [o].[CustomerId], [o].[EmployeeId],
    SUM([o].[Amount]), MIN([o].[Amount]), MAX([o].[Amount]), AVG([o].[Amount])
FROM [Orders] AS [o]
GROUP BY [o].[CustomerId], [o].[EmployeeId];

查詢類型

EF Core 模型現在可以包含查詢類型。與實體類型不同,查詢類型沒有定義主鍵,也不能插入、刪除或更新操作(即它們是只讀的),但它們可以直接由查詢返回。查詢類型的一些使用場景:

  • 映射到沒有主鍵的視圖
  • 映射到沒有主鍵的表
  • 映射到模型中定義的查詢
  • 作為FromSql()查詢的返回類型

示例,定義一個簡單的BlogPost模型:

    public class Blog
    {
        public int BlogId { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
        public ICollection<Post> Posts { get; set; }
    }
    
    public class Post
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public int BlogId { get; set; }
    }

定義一個簡單的數據庫視圖,能夠查詢每博客與文章數:

    db.Database.ExecuteSqlCommand(
        @"CREATE VIEW View_BlogPostCounts AS 
            SELECT Name, Count(p.PostId) as PostCount from Blogs b
            JOIN Posts p on p.BlogId = b.BlogId
            GROUP BY b.Name");

定義一個類映射的數據庫視圖的結果:

    public class BlogPostsCount
    {
        public string BlogName { get; set; }
        public int PostCount { get; set; }
    }

DbContext類的OnModelCreating使用modelBuilder.Query<T>API。 我們可以使用標准 fluent 配置 Api 來配置查詢類型的映射:

    public class SampleDbContext : DbContext
    {
        public DbQuery<BlogPostsCount> BlogPostCounts { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder
                .Query<BlogPostsCount>().ToTable("View_BlogPostCounts")
                .Property(v => v.BlogName).HasColumnName("Name");
        }
    }

查詢數據庫視圖中的標准方式:

    var postCounts = db.BlogPostCounts.ToList();
    
    foreach (var postCount in postCounts)
    {
        Console.WriteLine($"{postCount.BlogName} has {postCount.PostCount} posts.");
        Console.WriteLine();
    }

最后

EF Core 2.1 Preview1 新增功能的部分內容已經介紹完了,希望對您有幫助。如果文章中描述的功能存在遺漏或錯誤,請在評論中留言,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM