兩個星期前,微軟發佈了 "EF Core 2.1 Preview 1" ,同時還發佈了 ".NET Core 2.1 Preview 1" 和 "ASP.NET Core 2.1 Preview 1" ;EF Core 2.1 Preview 1 除了 "許多小改進和超過100種產品錯誤修複之外" ...
兩個星期前,微軟發佈了EF Core 2.1 Preview 1,同時還發佈了.NET Core 2.1 Preview 1和ASP.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; }
}
實體Category
的Name
屬性是用枚舉表示的,如果在存儲時用字元串類型表示,我們可以在DbContext
的OnModelCreating
方法中使用如下代碼,
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>().Property(e => e.Name).HasConversion<string>();
}
EF Core 預設提供常用類型的轉換,我們只需指定存儲的類型即可,框架預設支持的類型轉換映射表如下:
源類型 | 目標類型 |
---|---|
enum |
int 、short 、long 、sbyte 、uint 、ushort 、ulong 、byte 、decimal 、double 、float |
bool |
int 、short 、long 、sbyte 、uint 、ushort 、ulong 、byte 、decimal 、double 、float |
bool |
string |
bool |
byte[] |
char |
string |
char |
int 、short 、long 、sbyte 、uint 、ushort 、ulong 、byte 、decimal 、double 、float |
char |
byte[] |
Guid |
byte[] |
Guid |
string |
byte[] |
string |
string |
byte[] |
DateTime 、DateTimeOffset 、TimeSpan |
string 、long 、byte[] |
int 、short 、long 、sbyte 、uint 、ushort 、ulong 、byte 、decimal 、double 、float |
string 、byte[] |
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()
查詢的返回類型
示例,定義一個簡單的Blog
和Post
模型:
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 新增功能的部分內容已經介紹完了,希望對您有幫助。如果文章中描述的功能存在遺漏或錯誤,請在評論中留言,謝謝!