2022年11月8日.NET 7正式發布
.NET仍然是最快、最受歡迎、最值得信賴的平臺之一,其龐大的.NET軟體包生態系統包括33萬多個軟體包,

.NET 7為您的應用程式帶來了更高的性能和C# 11/F# 7、.NET MAUI、ASP.NET Core/Blazor、Web APIs、WinForms、WPF等的新功能,有了.NET 7,你還可以輕松地將你的.NET 7專案容器化,在GitHub行動中設定CI/CD作業流程,并實作云原生的可觀察性,
感謝開源的.NET社區為幫助塑造這個.NET 7版本所作的大量貢獻,在整個.NET 7版本中,有超過8900名貢獻者做出了28000項貢獻!

.NET 7的發布是我們.NET統一之旅的第三個主要版本(自2016年的.NET 5以來),
有了.NET 7,你可以通過一個SDK、一個運行時、一套基礎庫來構建多種型別的應用程式(云、網路、桌面、移動、游戲、物聯網和人工智能),一次學習并重復使用你的技能,
.NET 7支持周期

.NET 7得到了微軟的正式支持,它被標記為一個標準期限支持(STS)版本,將被支持18個月,奇數的.NET版本是STS版本,在隨后的STS或LTS版本之后,可以獲得6個月的免費支持和補丁,
獲取
從官網獲取
https://dotnet.microsoft.com/zh-cn/download/dotnet/7.0

安裝SDK
- sdk-7.0.100-windows-x64-installer
- sdk-7.0.100-windows-x86-installer
- sdk-7.0.100-windows-arm64-installer
安裝桌面運行時
- runtime-desktop-7.0.0-windows-x64-installer
- runtime-desktop-7.0.0-windows-x86-installer
- runtime-desktop-7.0.0-windows-arm64-installer
安裝ASP.NET Core運行時
- runtime-aspnetcore-7.0.0-windows-x64-installer
- runtime-aspnetcore-7.0.0-windows-x86-installer
- runtime-aspnetcore-7.0.0-windows-hosting-bundle-installer
安裝.NET運行時
- runtime-7.0.0-windows-x64-installer
- runtime-7.0.0-windows-x86-installer
- runtime-7.0.0-windows-arm64-installer
通過Nuget獲取
安裝SDK
winget install Microsoft.DotNet.SDK.7

安裝桌面運行時
winget install Microsoft.DotNet.DesktopRuntime.7

安裝ASP.NET Core運行時
winget install Microsoft.DotNet.AspNetCore.7

安裝.NET運行時
winget install Microsoft.DotNet.Runtime.7

在.NET 7中ASP.NET Core更新有哪些?
ASP.NET Core 7.0 的新增功能
服務與運行時(Servers and runtime)
- 速率限制(Rating limiting):使用靈活的端點配置和策略限制處理請求的速率,
- 輸出快取(Output caching):為回應配置快取以更有效地處理請求,
- 請求解壓縮(Request decompression):接受具有壓縮內容的請求,
- HTTP/3:內置支持HTTP/3,這是基于新的QUIC復用傳輸協議的最新HTTP版本,
- Http/2 WebSockets支持: 使用WebSockets over HTTP/2連接,
- WebTransport(實驗性):通過對WebTransport的實驗性支持,在HTTP/3上創建流和資料克,
最小API(Minimal APIs)
- 終結點篩選器(Endpoint filters):使用端點過濾器在路由處理程式之前或之后運行橫切代碼,
- 型別化結果(Typed results):從最小的API回傳強型別的結果,
- 路由組(Route groups):用一個共同的前綴來組織端點組,
遠程呼叫(gRPC)
- JSON轉碼(JSON transcoding):通過將你的gRPC服務暴露為基于JSON的API來擴大它們的覆寫范圍
- JSON轉碼檔案與Swagger/OpenAPI(實驗性的):使用實驗性支持,為你的gRPC JSON轉碼服務生成OpenAPI規范,
- gRPC運行狀況檢查:報告和檢查gRPC服務器應用程式的健康狀況,
- gRPC客戶端添加持有者令牌:創建使用承載令牌發送授權請求的客戶端,
實時應用(SignalR)
- 客戶端結果(Client results):回應服務器的請求,向服務器回傳客戶結果,
MVC
- MVC視圖和Razor頁面中的可為空模型(Nullable view and page models):現在支持Nullable頁面和視圖模型,以改善使用空狀態檢查時的體驗,
客戶端Web應用(Blazor)
- 自定義元素(Custom elements):用Blazor構建標準的HTML自定義元素,將Blazor組件與任何基于JavaScript的應用程式整合起來,
- 處理位置更改事件(Handle location changing events):攔截位置變化事件,以創建導航時的自定義用戶體驗,
- 資料系結(之后/獲取/設定)修改器(Bind after/get/set modifiers):在資料系結后運行異步邏輯,并獨立控制資料系結如何獲取和設定資料,
- 動態認證請求(Dynamic authentication requests):在運行時用自定義引數創建動態認證請求,以處理Blazor WebAssembly應用程式中的高級認證場景,
- 在WebAssembly中改進的JavaScript互操作(Improved JavaScript interop on WebAssembly):使用新的
[JSImport]/[JSExport]支持,在WebAssembly上運行時優化JavaScript互操作呼叫, - WebAssembly的SIMD和例外處理(WebAssembly SIMD & exception handling):使用WebAssembly SIMD和例外處理支持,提高.NET WebAssembly超時(AOT)編譯的性能,
在.NET 7中Entity Framework Core 7更新有哪些?
JSON列
大多數關系資料庫都支持包含JSON檔案的列,這些列中的JSON可以通過查詢進行鉆取,例如,這允許按檔案內的屬性進行篩選和排序,以及將檔案中的屬性投影到結果中,JSON列允許關系資料庫具有檔案資料庫的某些特征,從而在兩者之間創建有用的混合;它們還可用于消除查詢中的聯接,從而提高性能,
EF7包含對JSON列的提供程式無關支持,以及SQLServer的實作,此支持允許將從.NET型別生成的聚合映射到JSON檔案,可以在聚合上使用普通的LINQ查詢,這些查詢將轉換為鉆取到JSON所需的相應查詢構造,EF7還支持保存對JSON檔案所做的更改,
使用Linq來查詢Json
使用示例
var postsWithViews = await context.Posts.Where(post => post.Metadata!.Views > 3000)
.AsNoTracking()
.Select(
post => new
{
post.Author!.Name,
post.Metadata!.Views,
Searches = post.Metadata.TopSearches,
Commits = post.Metadata.Updates
})
.ToListAsync();
EFCore 7翻譯后的SQL陳述句為
SELECT [a].[Name],
CAST(JSON_VALUE([p].[Metadata],'$.Views') AS int),
JSON_QUERY([p].[Metadata],'$.TopSearches'),
[p].[Id],
JSON_QUERY([p].[Metadata],'$.Updates')
FROM [Posts] AS [p]
LEFT JOIN [Authors] AS [a] ON [p].[AuthorId] = [a].[Id]
WHERE CAST(JSON_VALUE([p].[Metadata],'$.Views') AS int) > 3000
注意的是,JSON_VALUE、JSON_QUERY被用來查詢Json檔案的部分,
在保存時更新Json
EF7的更改跟蹤查找需要更新的JSON檔案中最小的單個部分,并發送SQL命令以有效地相應地更新列,例如,考慮修改嵌入在JSON檔案中的單個屬性的代碼:
var arthur = await context.Authors.SingleAsync(author => author.Name.StartsWith("Arthur"));
arthur.Contact.Address.Country = "United Kingdom";
await context.SaveChangesAsync();
EFCore 7僅為修改的值生成的一個SQL引數
@p0='["United Kingdom"]' (Nullable = false) (Size = 18)
然后使用這個引數,結合JSON_MODIFY命令進行修改
UPDATE [Authors] SET [Contact] = JSON_MODIFY([Contact], 'strict $.Address.Country', JSON_VALUE(@p0, '$[0]'))
OUTPUT 1
WHERE [Id] = @p1;
更多關于Json列的資訊
- What’s New: JSON Columns
- .NET Data Community Standup Video: JSON Columns
- Sample code: JSON Columns
批量更新和洗掉
EFCore跟蹤物體的變化,然后在SaveChangesAsync被呼叫時將更新發送到資料庫,只有那些實際發生了變化的屬性和關系才會被發送,同時,被跟蹤的物體與發送到資料庫的變化保持同步,這種機制是一種高效、便捷的方式,可以向資料庫發送通用的插入、更新和洗掉,這些變化也是分批進行的,以減少資料庫的往返次數,
然而,有時在資料庫上執行更新或洗掉命令而不加載物體或涉及到變化跟蹤器是很有用的,EF7通過新的ExecuteUpdateAsync和ExecuteDeleteAsync方法實作了這一點,這些方法被應用于LINQ查詢,并根據查詢的結果立即更新或洗掉資料庫中的物體,許多物體可以用一個命令來更新,而且這些物體不會被載入記憶體,
批量洗掉
使用了ExecuteDeleteAsync的示例代碼
var priorToDateTime = new DateTime(priorToYear, 1, 1);
await context.Tags.Where(t => t.Posts.All(e => e.PublishedOn < priorToDateTime)).ExecuteDeleteAsync();
這將生成“立即從資料庫中洗掉所有在給定年份之前發表的帖子的標簽”的SQL,
DELETE FROM [t]
FROM [Tags] AS [t]
WHERE NOT EXISTS (
SELECT 1
FROM [PostTag] AS [p]
INNER JOIN [Posts] AS [p0] ON [p].[PostsId] = [p0].[Id]
WHERE [t].[Id] = [p].[TagsId] AND [p0].[PublishedOn] < @__priorToDateTime_1)
批量更新
使用ExecuteUpdateAsync與使用ExecuteDeleteAsync非常相似,只是它需要額外的引數來指定對每條記錄的修改,
例如,考慮下面這個以呼叫ExecuteUpdateAsync結束的LINQ查詢,
var priorToDateTime = new DateTime(priorToYear, 1, 1);
await context.Tags
.Where(t => t.Posts.All(e => e.PublishedOn < priorToDateTime))
.ExecuteUpdateAsync(s => s.SetProperty(t => t.Text, t => t.Text + " (old)"));
這將生成“立即更新給定年份之前發布的帖子的所有標簽的"文本"列”的SQL,
UPDATE [t]
SET [t].[Text] = [t].[Text] + N' (old)'
FROM [Tags] AS [t]
WHERE NOT EXISTS (
SELECT 1
FROM [PostTag] AS [p]
INNER JOIN [Posts] AS [p0] ON [p].[PostsId] = [p0].[Id]
WHERE [t].[Id] = [p].[TagsId] AND [p0].[PublishedOn] < @__priorToDateTime_1)
更新或洗掉單行
雖然ExecuteUpdateAsync和ExecuteDeleteAsync通常用于同時更新或洗掉許多行(即"批量"更改),但它們對于高效的單行更改也很有用,
例如,考慮在ASP.NETCore應用程式中洗掉一個物體的常見模式,
public async Task<ActionResult> DeletePost(int id)
{
var post = await _context.Posts.FirstOrDefaultAsync(p => p.Id == id);
if (post == null)
{
return NotFound();
}
_context.Posts.Remove(post);
await _context.SaveChangesAsync();
return Ok();
}
如果使用EF 7將可以寫成
public async Task<ActionResult> DeletePost(int id)
=> await _context.Posts.Where(p => p.Id == id).ExecuteDeleteAsync() == 0
? NotFound()
: Ok();
這既是更少的代碼,也是明顯的速度,因為它只執行了一次資料庫往返,
何時使用批量更新
ExecuteUpdateAsync和ExecuteDeleteAsync是簡單、明確的更新和洗掉的最佳選擇,
然而,請記住,
- 必須明確指定要做的具體變化;EFCore不會自動檢測到這些變化,
- 任何被跟蹤的物體都不會保持同步,
- 可能需要多個命令,而且這些命令的順序要正確,以免違反資料庫約束,例如,在洗掉委托人之前,必須先洗掉依賴者,
- 對
ExecuteUpdateAsync和ExecuteDeleteAsync的多次呼叫不會被自動包裹在一個事務中,
所有這些意味著ExecuteUpdateAsync和ExecuteDeleteAsync是對現有SaveChanges機制的補充,而不是取代,
更多關于批量更新的資訊
- What’s New: ExecuteUpdate and ExecuteDelete (Bulk updates)
- .NET Data Community Standup Video: Bulk updates
- Sample code: ExecuteUpdateAsync
- Sample code: ExecuteDeleteAsync
保存變更更快
在EF 7中,SaveChanges和SaveChangesAsync的性能得到了明顯的改善,在某些情況下,現在保存變化的速度比EF Core 6快四倍,這些改進來自于執行更少的往返于資料庫和生成更有效的SQL,
避免非必要的事務
所有現代關系型資料庫都保證了(大多數)單一SQL陳述句的事務性,也就是說,即使發生錯誤,該陳述句也不會只完成一部分,
EF 7避免在這些情況下啟動顯式事務,
例如,考慮以下對SaveChangesAsync的呼叫,它插入了一個單一物體,
await context.AddAsync(new Blog { Name = "MyBlog" });
await context.SaveChangesAsync();
在EF Core 6中,INSERT命令被開始和提交事務的命令所包裹,
dbug: 9/29/2022 11:43:09.196 RelationalEventId.TransactionStarted[20200] (Microsoft.EntityFrameworkCore.Database.Transaction)
Began transaction with isolation level 'ReadCommitted'.
info: 9/29/2022 11:43:09.265 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (27ms) [Parameters=[@p0='MyBlog' (Nullable = false) (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [Blogs] ([Name])
VALUES (@p0);
SELECT [Id]
FROM [Blogs]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
dbug: 9/29/2022 11:43:09.297 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committed transaction.
EF 7檢測到這里不需要事務,所以洗掉了這些呼叫,
info: 9/29/2022 11:42:34.776 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (25ms) [Parameters=[@p0='MyBlog' (Nullable = false) (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
INSERT INTO [Blogs] ([Name])
OUTPUT INSERTED.[Id]
VALUES (@p0);
插入多行
在EF Core 6中,插入多條記錄的默認方法是由SQLServer對帶有觸發器的表的支持的限制所驅動的,這意味著EF Core 6不能使用一個簡單的OUTPUT子句,相反,當插入多個物體時,EF Core 6產生了一些相當復雜的涉及到臨時表的SQL,
例如,考慮對SaveChangesAsync的呼叫,
for (var i = 0; i < 4; i++)
{
await context.AddAsync(new Blog { Name = "Foo" + i });
}
await context.SaveChangesAsync();
由EF Core 6生成的SQL如下
dbug: 9/30/2022 17:19:51.919 RelationalEventId.TransactionStarted[20200] (Microsoft.EntityFrameworkCore.Database.Transaction)
Began transaction with isolation level 'ReadCommitted'.
info: 9/30/2022 17:19:51.993 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (27ms) [Parameters=[@p0='Foo0' (Nullable = false) (Size = 4000), @p1='Foo1' (Nullable = false) (Size = 4000), @p2='Foo2' (Nullable = false) (Size = 4000), @p3='Foo3' (Nullable = false) (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]);
MERGE [Blogs] USING (
VALUES (@p0, 0),
(@p1, 1),
(@p2, 2),
(@p3, 3)) AS i ([Name], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Name])
VALUES (i.[Name])
OUTPUT INSERTED.[Id], i._Position
INTO @inserted0;
SELECT [i].[Id] FROM @inserted0 i
ORDER BY [i].[_Position];
dbug: 9/30/2022 17:19:52.023 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committed transaction.
相比之下,EF 7在針對一個沒有觸發器的表時,會生成一條更簡單的命令,
info: 9/30/2022 17:40:37.612 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (4ms) [Parameters=[@p0='Foo0' (Nullable = false) (Size = 4000), @p1='Foo1' (Nullable = false) (Size = 4000), @p2='Foo2' (Nullable = false) (Size = 4000), @p3='Foo3' (Nullable = false) (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
MERGE [Blogs] USING (
VALUES (@p0, 0),
(@p1, 1),
(@p2, 2),
(@p3, 3)) AS i ([Name], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Name])
VALUES (i.[Name])
OUTPUT INSERTED.[Id], i._Position;
事務沒有了,就像單次插入的情況一樣,因為MERGE是一個受隱式事務保護的單一陳述句,另外,臨時表也沒有了,OUTPUT子句現在直接把生成的ID發回給客戶端,這可能比EF Core 6上的速度快四倍,這取決于環境因素,如應用程式和資料庫之間的延遲,
這消除了兩個資料庫往返,這會使整體性能產生巨大影響,尤其是在對資料庫的呼叫延遲較高時,在典型的生產系統中,資料庫不與應用程式位于同一臺計算機上,這意味著延遲通常相對較高,使得這種優化在實際生產系統中特別有效,
更多關于高性能保存變更的資訊
- What’s New: Faster SaveChanges
- .NET Blog: Announcing Entity Framework Core 7 Preview 6–Performance Edition
- .NET Data Community Standup Video: Performance Improvements to the EF7 Update Pipeline
- Sample code: SaveChanges performance
每個具體型別的表(TPC)的繼承映射
默認情況下,EF Core將一個.NET型別的繼承層次映射到一個資料庫表,這被稱為"每層表"(TPH)的映射策略,EF Core 5引入了table-per-type(TPT)策略,它支持將每個.NET型別映射到一個不同的資料庫表中,EF 7引入了table-per-concrete-type(TPC)策略,TPC也是將.NET型別映射到不同的表,但其方式是解決TPT策略的一些常見的性能問題,
TPC策略與TPT策略類似,只是為層次結構中的每個具體型別創建不同的表,但不為抽象型別創建表——因此被稱為"每具體型別表",與TPT一樣,表本身表明了保存物件的型別,然而,與TPT映射不同,每個表都包含了具體型別及其基礎型別中每個屬性的列,因此,TPC資料庫模式是非規范化的,
每具體型別表(TPC)
思考以下這樣一個示例
public abstract class Animal
{
public int Id { get; set; }
public string Name { get; set; }
public abstract string Species { get; }
public Food? Food { get; set; }
}
public abstract class Pet : Animal
{
public string? Vet { get; set; }
public ICollection<Human> Humans { get; } = new List<Human>();
}
public class FarmAnimal : Animal
{
public override string Species { get; }
public decimal Value { get; set; }
}
public class Cat : Pet
{
public string EducationLevel { get; set; }
public override string Species => "Felis catus";
}
public class Dog : Pet
{
public string FavoriteToy { get; set; }
public override string Species => "Canis familiaris";
}
public class Human : Animal
{
public override string Species => "Homo sapiens";
public Animal? FavoriteAnimal { get; set; }
public ICollection<Pet> Pets { get; } = new List<Pet>();
}
這是在OnModelCreating中使用UseTpcMappingStrategy將其映射到TPC表的案例:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>().UseTpcMappingStrategy();
}
當使用SQL Server時,為這個層次結構創建的表是:
CREATE TABLE [Cats] (
[Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
[Name] nvarchar(max) NOT NULL,
[FoodId] uniqueidentifier NULL,
[Vet] nvarchar(max) NULL,
[EducationLevel] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Cats] PRIMARY KEY ([Id]));
CREATE TABLE [Dogs] (
[Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
[Name] nvarchar(max) NOT NULL,
[FoodId] uniqueidentifier NULL,
[Vet] nvarchar(max) NULL,
[FavoriteToy] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Dogs] PRIMARY KEY ([Id]));
CREATE TABLE [FarmAnimals] (
[Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
[Name] nvarchar(max) NOT NULL,
[FoodId] uniqueidentifier NULL,
[Value] decimal(18,2) NOT NULL,
[Species] nvarchar(max) NOT NULL,
CONSTRAINT [PK_FarmAnimals] PRIMARY KEY ([Id]));
CREATE TABLE [Humans] (
[Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
[Name] nvarchar(max) NOT NULL,
[FoodId] uniqueidentifier NULL,
[FavoriteAnimalId] int NULL,
CONSTRAINT [PK_Humans] PRIMARY KEY ([Id]));
每具體型別表(TPC)的查詢
TPC生成的查詢比TPT更有效,需要從更少的表中獲取資料,并且利用UNION ALL代替JOIN,
例如,對于查詢整個層次結構,EF7會產生:
SELECT [f].[Id], [f].[FoodId], [f].[Name], [f].[Species], [f].[Value], NULL AS [FavoriteAnimalId], NULL AS [Vet], NULL AS [EducationLevel], NULL AS [FavoriteToy], N'FarmAnimal' AS [Discriminator]
FROM [FarmAnimals] AS [f]
UNION ALL
SELECT [h].[Id], [h].[FoodId], [h].[Name], NULL AS [Species], NULL AS [Value], [h].[FavoriteAnimalId], NULL AS [Vet], NULL AS [EducationLevel], NULL AS [FavoriteToy], N'Human' AS [Discriminator]
FROM [Humans] AS [h]
UNION ALL
SELECT [c].[Id], [c].[FoodId], [c].[Name], NULL AS [Species], NULL AS [Value], NULL AS [FavoriteAnimalId], [c].[Vet], [c].[EducationLevel], NULL AS [FavoriteToy], N'Cat' AS [Discriminator]
FROM [Cats] AS [c]
UNION ALL
SELECT [d].[Id], [d].[FoodId], [d].[Name], NULL AS [Species], NULL AS [Value], NULL AS [FavoriteAnimalId], [d].[Vet], NULL AS [EducationLevel], [d].[FavoriteToy], N'Dog' AS [Discriminator]
FROM [Dogs] AS [d]
當查詢一個型別的子集時,情況會變得更好:
SELECT [c].[Id], [c].[FoodId], [c].[Name], [c].[Vet], [c].[EducationLevel], NULL AS [FavoriteToy], N'Cat' AS [Discriminator]
FROM [Cats] AS [c]
UNION ALL
SELECT [d].[Id], [d].[FoodId], [d].[Name], [d].[Vet], NULL AS [EducationLevel], [d].[FavoriteToy], N'Dog' AS [Discriminator]
FROM [Dogs] AS [d]
TPC查詢在對單一葉子型別進行查詢時真的很有優勢:
SELECT [c].[Id], [c].[FoodId], [c].[Name], [c].[Vet], [c].[EducationLevel]
FROM [Cats] AS [c]
更多關于每具體型別表(TPC)的資訊
- What’s New: Table-per-concrete-type (TPC) inheritance mapping
- .NET Data Community Standup Video: TPH, TPT, and TPC Inheritance mapping with EF Core
- Sample code: TPC inheritance
定制資料庫逆向工程模板
EF 7支持T4模板,用于在從資料庫逆向工程EF模型時定制支架代碼,默認的模板是通過dotnet命令添加到專案中的,
dotnet new --install Microsoft.EntityFrameworkCore.Templates
dotnet new ef-templates
然后,這些模板可以被定制,并將自動被dotnet ef dbcontext scaffold和Scaffold-DbContext所使用,
自定義生成后的物體型別
讓我們來看看定制模板是什么樣子的,
默認情況下,EF Core為集合導航屬性生成了以下代碼,
public virtual ICollection<Album> Albums { get; } = new List<Album>();
對于大多數應用程式來說,使用List<T>是一個很好的默認值,然而,如果你使用一個基于XAML的框架,如WPF、WinUI或.NET MAUI,你經常想使用ObservableCollection<T>來實作資料系結,
EntityType.t4模板可以被編輯來做這個改變,
例如,下面的代碼包含在默認模板中,
if (navigation.IsCollection)
{
#>
public virtual ICollection<<#= targetType #>> <#= navigation.Name #> { get; } = new List<<#= targetType #>>();
<#
}
這可以很容易地改成使用ObservableCollection
public virtual ICollection<<#= targetType #>> <#= navigation.Name #> { get; } = new ObservableCollection<<#= targetType #>>();
由于ObservableCollection在System.Collections.ObjectModel命名空間中,我們還需要在腳手架的代碼中添加一個using指令,
var usings = new List<string>
{
"System",
"System.Collections.Generic",
"System.Collections.ObjectModel"
};
更多關于反向工程T4模板的資訊
- What’s New: Custom Reverse Engineering Templates
- .NET Data Community Standup Video: Database-first with T4 Templates in EF7–An early look
定制建模轉換
EF Core使用一個元資料"模型"來描述應用程式的物體型別是如何映射到底層資料庫的,這個模型是通過一組大約60個"約定"建立的,然后可以使用映射屬性(又稱"資料注釋")和/或在OnModelCreating中呼叫ModelBuilder API來定制由慣例構建的模型,
從EF 7開始,應用程式可以洗掉或替換這些約定,也可以添加新的約定,
模型構建約定是控制模型配置的一個強有力的方法,
移除轉換約定
EF 7允許洗掉EF Core使用的默認約定,
例如,為外鍵(FK)列創建索引通常是有意義的,因此,有一個內置的約定用于此,
外鍵索引公約(ForeignKeyIndexConvention),然而,在更新行的時候,索引會增加開銷,而且為所有的FK列創建索引可能并不總是合適,
為了達到這個目的,在建立模型時可以洗掉ForeignKeyIndexConvention,
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.Remove(typeof(ForeignKeyIndexConvention));
}
增加轉換約定
EF Core用戶的一個共同要求是為所有字串屬性設定一個默認長度,這在EF 7中可以通過撰寫一個公約來實作,
public class MaxStringLengthConvention : IModelFinalizingConvention
{
private readonly int _maxLength;
public MaxStringLengthConvention(int maxLength)
{
_maxLength = maxLength;
}
public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
{
foreach (var property in modelBuilder.Metadata.GetEntityTypes()
.SelectMany(
e => e.GetDeclaredProperties()
.Where(p => p.ClrType == typeof(string))))
{
property.Builder.HasMaxLength(_maxLength);
}
}
}
這個約定是非常簡單的,
它找到模型中的每個字串屬性,并將其最大長度設定為指定值,
然而,使用這樣的約定的關鍵之處在于,那些使用[MaxLength]或[StringLength]屬性或OnModelCreating中的HasMaxLength明確設定其最大長度的屬性將保留其明確值,
換句話說,只有在沒有指定其他長度的情況下,才會使用該約定所設定的默認值,
這個新約定可以通過在ConfigureConventions中添加它來使用,
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.Add(_ => new MaxStringLengthConvention(256));
}
更多關于建模轉換的資訊
- What’s New: Model building conventions
- .NET Data Community Standup Video: EF7 Custom Model Conventions
- Sample code: Model building conventions
插入/更新/洗掉的存盤程序映射
默認情況下,EF Core生成的插入、更新和洗掉命令是直接與表或可更新的視圖一起作業,
EF 7引入了對這些命令到存盤程序的映射的支持,
在OnModelCreating中使用InsertUsingStoredProcedure、UpdateUsingStoredProcedure和DeleteUsingStoredProcedure來映射存盤程序,
例如,要為Person物體型別映射存盤程序,
modelBuilder.Entity<Person>()
.InsertUsingStoredProcedure(
"People_Insert",
storedProcedureBuilder =>
{
storedProcedureBuilder.HasParameter(a => a.Name);
storedProcedureBuilder.HasResultColumn(a => a.Id);
})
.UpdateUsingStoredProcedure(
"People_Update",
storedProcedureBuilder =>
{
storedProcedureBuilder.HasOriginalValueParameter(person => person.Id);
storedProcedureBuilder.HasOriginalValueParameter(person => person.Name);
storedProcedureBuilder.HasParameter(person => person.Name);
storedProcedureBuilder.HasRowsAffectedResultColumn();
})
.DeleteUsingStoredProcedure(
"People_Delete",
storedProcedureBuilder =>
{
storedProcedureBuilder.HasOriginalValueParameter(person => person.Id);
storedProcedureBuilder.HasOriginalValueParameter(person => person.Name);
storedProcedureBuilder.HasRowsAffectedResultColumn();
});
在使用SQL Server時,該配置映射到以下存盤程序,
針對插入場景
CREATE PROCEDURE [dbo].[People_Insert]
@Name [nvarchar](max)
AS
BEGIN
INSERT INTO [People] ([Name])
OUTPUT INSERTED.[Id]
VALUES (@Name);
END
針對更新場景
CREATE PROCEDURE [dbo].[People_Update]
@Id [int],
@Name_Original [nvarchar](max),
@Name [nvarchar](max)
AS
BEGIN
UPDATE [People] SET [Name] = @Name
WHERE [Id] = @Id AND [Name] = @Name_Original
SELECT @@ROWCOUNT
END
針對洗掉場景
CREATE PROCEDURE [dbo].[People_Delete]
@Id [int],
@Name_Original [nvarchar](max)
AS
BEGIN
DELETE FROM [People]
OUTPUT 1
WHERE [Id] = @Id AND [Name] = @Name_Original;
END
然后在呼叫SaveChangesAsync時使用這些存盤程式,
SET NOCOUNT ON;
EXEC [People_Update] @p1, @p2, @p3;
EXEC [People_Update] @p4, @p5, @p6;
EXEC [People_Delete] @p7, @p8;
EXEC [People_Delete] @p9, @p10;
更多關于存盤程序映射的資訊
- What’s New: Stored procedure mapping
- Sample code: Stored procedure mapping
全新的改進后的攔截器和事件
EF Core攔截器能夠攔截、修改和/或抑制EFCore操作,EF Core還包括傳統的.NET事件和日志記錄,
EF 7包括以下攔截器的增強功能
- 攔截創建和填充新的物體實體(又稱"物體化")
- 攔截器可以在編譯查詢之前修改LINQ運算式樹
- 攔截樂觀的并發性處理(
DbUpdateConcurrencyException) - 在檢查連接字串是否已被設定之前,對連接進行攔截
- 當EF Core消耗完一個結果集后,在該結果集被關閉之前進行攔截
- 對EF Core創建
DbConnection的攔截 - 對
DbCommand初始化后的攔截
此外,EF7還包括新的傳統的.NET事件,用于:
- 當一個物體即將被追蹤或改變狀態時,但在它實際被追蹤或改變狀態之前
- 在EF Core檢測到物體和屬性的變化之前和之后(又稱
DetectChanges攔截)
Materialization攔截器
新的IMaterializationInterceptor支持在物體實體被創建前后以及該實體的屬性被初始化前后進行攔截,
攔截器可以在每個點上改變或替換物體實體,這允許:
- 設定未映射的屬性或呼叫驗證、計算值或標志所需的方法,
- 使用一個工廠來創建實體,
- 創建與EF通常創建的不同的物體實體,如來自快取的實體,或代理型別的實體,
- 將服務注入到物體實體中,
例如,想象一下,我們想跟蹤一個物體從資料庫中檢索的時間,也許這樣就可以顯示給編輯資料的用戶,為此可以創建一個物化攔截器,
public class SetRetrievedInterceptor : IMaterializationInterceptor
{
public object InitializedInstance(MaterializationInterceptionData materializationData, object instance)
{
if (instance is IHasRetrieved hasRetrieved)
{
hasRetrieved.Retrieved = DateTime.UtcNow;
}
return instance;
}
}
在配置DbContext時,這個攔截器的一個實體被注冊,
public class CustomerContext : DbContext
{
private static readonly SetRetrievedInterceptor _setRetrievedInterceptor = new();
public DbSet<Customer> Customers => Set<Customer>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.AddInterceptors(_setRetrievedInterceptor)
.UseSqlite("Data Source = customers.db");
}
現在,每當從資料庫查詢一個客戶時,Retrieved屬性將被自動設定,比如說,
await using (var context = new CustomerContext())
{
var customer = await context.Customers.SingleAsync(e => e.Name == "Alice");
Console.WriteLine($"Customer '{customer.Name}' was retrieved at '{customer.Retrieved.ToLocalTime()}'");
}
行程的輸出為
Customer 'Alice' was retrieved at '9/22/2022 5:25:54 PM'
連接字串的延遲初始化
連接字串通常是從組態檔中讀取的靜態資產,在配置DbContext時,這些可以很容易地傳遞給UseSqlServer或類似的東西,然而,連接字串有時可以為每個背景關系實體而改變,
例如,在一個多租戶系統中,每個租戶可能有不同的連接字串,
EF 7通過對IDbConnectionInterceptor的改進,使其更容易處理動態連接和連接字串,
這首先是在沒有任何連接字串的情況下配置DbContext的能力,比如說:
services.AddDbContext<CustomerContext>(
b => b.UseSqlServer();
然后,可以實作IDbConnectionInterceptor方法之一,在使用連接之前對其進行配置,
ConnectionOpeningAsync是一個很好的選擇,因為它可以執行一個異步操作來獲取連接字串,找到一個訪問令牌,等等,
public class ConnectionStringInitializationInterceptor : DbConnectionInterceptor
{
private readonly IClientConnectionStringFactory _connectionStringFactory;
public ConnectionStringInitializationInterceptor(IClientConnectionStringFactory connectionStringFactory)
{
_connectionStringFactory = connectionStringFactory;
}
public override async ValueTask<InterceptionResult> ConnectionOpeningAsync(
DbConnection connection, ConnectionEventData eventData, InterceptionResult result,
CancellationToken cancellationToken = new())
{
if (string.IsNullOrEmpty(connection.ConnectionString))
{
connection.ConnectionString = (await _connectionStringFactory.GetConnectionStringAsync(cancellationToken));
}
return result;
}
}
請注意,只有在第一次使用連接時才會獲得連接字串,在那之后,存盤在
DbConnection上的連接字串將被使用,而無需查找新的連接字串,
更多關于攔截器和事件的資訊
- What’s New: New and improved interceptors and events
- .NET Data Community Standup Video: Intercept this EF7 Preview 6 Event
- Sample code: Materialization interception
- Sample code: Connection string interception
參考
- ASP.NET Core 7.0 的新增功能
- 從ASP.NET Core 6.0遷移到7.0
- .NET Live TV
- .NET 7 is Available Today
- Announcing ASP.NET Core in .NET 7
- Announcing .NET MAUI for .NET 7 General Availability
- What’s new for WPF in .NET 7
- What’s new in Windows Forms in .NET 7.0
- Entity Framework Core 7 (EF7) is available today
- Welcome to C# 11
- Announcing F# 7
- What’s new in Orleans 7.0
- 使用Windows包管理器 (winget) 進行安裝
- 乘風破浪,遇見最佳跨平臺跨終端框架.Net Core/.Net生態 - 獲取.Net 7并查看.Net 7中的性能提升(簡中譯文)
- 乘風破浪,遇見最佳跨平臺跨終端框架.Net Core/.Net生態 - 伴隨.Net6/7與時俱進的.Net CLI命令列介面
- 乘風破浪,遇見最佳跨平臺跨終端框架.Net Core/.Net生態 - .NET 7預覽和RC1,內置MAUI、幫助.NET應用程式現代化升級
- 乘風破浪,遇見最佳跨平臺跨終端框架.Net Core/.Net生態 - 淺析ASP.NET Core遠程程序呼叫,HttpClientFactory和gRPC最佳實踐
- 統一的開發平臺.NET 7正式發布
- 微軟發布.NET 7,Visual Studio 2022 17.4原生支持Arm64架構
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/530489.html
標籤:.NET技术
下一篇:C# GDI+ 畫心形 跳動影片
