乘風破浪,遇見最佳跨平臺跨終端框架.Net Core/.Net生態 - .NET 7正式發佈,看看ASP.NET Core 7.0和EF Core 7新增哪些功能

来源:https://www.cnblogs.com/taylorshi/archive/2022/11/09/16873788.html
-Advertisement-
Play Games

2022年11月8日.NET 7正式發佈 .NET仍然是最快、最受歡迎、最值得信賴的平臺之一,其龐大的.NET軟體包生態系統包括33萬多個軟體包。 .NET 7為您的應用程式帶來了更高的性能和C# 11/F# 7、.NET MAUI、ASP.NET Core/Blazor、Web APIs、WinF ...


2022年11月8日.NET 7正式發佈

.NET仍然是最快、最受歡迎、最值得信賴的平臺之一,其龐大的.NET軟體包生態系統包括33萬多個軟體包。

image

.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項貢獻!

image

.NET 7的發佈是我們.NET統一之旅的第三個主要版本(自2016年的.NET 5以來)。

有了.NET 7,你可以通過一個SDK、一個運行時、一套基礎庫來構建多種類型的應用程式(雲、網路、桌面、移動、游戲、物聯網和人工智慧),一次學習並重覆使用你的技能。

.NET 7支持周期

image

.NET 7得到了微軟的正式支持。它被標記為一個標準期限支持(STS)版本,將被支持18個月。奇數的.NET版本是STS版本,在隨後的STS或LTS版本之後,可以獲得6個月的免費支持和補丁。

獲取

從官網獲取

https://dotnet.microsoft.com/zh-cn/download/dotnet/7.0

image

安裝SDK

安裝桌面運行時

安裝ASP.NET Core運行時

安裝.NET運行時

通過Nuget獲取

安裝SDK

winget install Microsoft.DotNet.SDK.7

image

安裝桌面運行時

winget install Microsoft.DotNet.DesktopRuntime.7

image

安裝ASP.NET Core運行時

winget install Microsoft.DotNet.AspNetCore.7

image

安裝.NET運行時

winget install Microsoft.DotNet.Runtime.7

image

在.NET 7中ASP.NET Core更新有哪些?

ASP.NET Core 7.0 的新增功能

服務與運行時(Servers and runtime)

最小API(Minimal APIs)

遠程調用(gRPC)

實時應用(SignalR)

MVC

客戶端Web應用(Blazor)

在.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_VALUEJSON_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列的信息

批量更新和刪除

EFCore跟蹤實體的變化,然後在SaveChangesAsync被調用時將更新發送到資料庫。只有那些實際發生了變化的屬性和關係才會被髮送。同時,被跟蹤的實體與發送到資料庫的變化保持同步。這種機制是一種高效、便捷的方式,可以向資料庫發送通用的插入、更新和刪除。這些變化也是分批進行的,以減少資料庫的往返次數。

然而,有時在資料庫上執行更新或刪除命令而不載入實體或涉及到變化跟蹤器是很有用的。EF7通過新的ExecuteUpdateAsyncExecuteDeleteAsync方法實現了這一點。這些方法被應用於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)

更新或刪除單行

雖然ExecuteUpdateAsyncExecuteDeleteAsync通常用於同時更新或刪除許多行(即"批量"更改),但它們對於高效的單行更改也很有用

例如,考慮在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();

這既是更少的代碼,也是明顯的速度,因為它只執行了一次資料庫往返

何時使用批量更新

ExecuteUpdateAsyncExecuteDeleteAsync是簡單、明確的更新和刪除的最佳選擇

然而,請記住,

  • 必須明確指定要做的具體變化;EFCore不會自動檢測到這些變化。
  • 任何被跟蹤的實體都不會保持同步。
  • 可能需要多個命令,而且這些命令的順序要正確,以免違反資料庫約束。例如,在刪除委托人之前,必須先刪除依賴者。
  • ExecuteUpdateAsyncExecuteDeleteAsync的多次調用不會被自動包裹在一個事務中。

所有這些意味著ExecuteUpdateAsyncExecuteDeleteAsync是對現有SaveChanges機制的補充,而不是取代。

更多關於批量更新的信息

保存變更更快

在EF 7中,SaveChangesSaveChangesAsync的性能得到了明顯的改善。在某些情況下,現在保存變化的速度比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上的速度快四倍,這取決於環境因素,如應用程式和資料庫之間的延遲。

這消除了兩個資料庫往返,這會使整體性能產生巨大影響,尤其是在對資料庫的調用延遲較高時。在典型的生產系統中,資料庫不與應用程式位於同一臺電腦上。這意味著延遲通常相對較高,使得這種優化在實際生產系統中特別有效

更多關於高性能保存變更的信息

每個具體類型的表(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)的信息

定製資料庫逆向工程模板

EF 7支持T4模板,用於在從資料庫逆向工程EF模型時定製支架代碼。預設的模板是通過dotnet命令添加到項目中的。

dotnet new --install Microsoft.EntityFrameworkCore.Templates
dotnet new ef-templates

然後,這些模板可以被定製,並將自動被dotnet ef dbcontext scaffoldScaffold-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 #>>();

由於ObservableCollectionSystem.Collections.ObjectModel命名空間中,我們還需要在腳手架的代碼中添加一個using指令。

var usings = new List<string>
{
    "System",
    "System.Collections.Generic",
    "System.Collections.ObjectModel"
};

更多關於反向工程T4模板的信息

定製建模轉換

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));
}

更多關於建模轉換的信息

插入/更新/刪除的存儲過程映射

預設情況下,EF Core生成的插入、更新和刪除命令是直接與表或可更新的視圖一起工作。

EF 7引入了對這些命令到存儲過程的映射的支持

OnModelCreating中使用InsertUsingStoredProcedureUpdateUsingStoredProcedureDeleteUsingStoredProcedure來映射存儲過程。

例如,要為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;

更多關於存儲過程映射的信息

全新的改進後的攔截器和事件

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上的連接字元串將被使用,而無需查找新的連接字元串。

更多關於攔截器和事件的信息

參考


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這一篇文章主要介紹一些python的基礎知識,包括演算法、數字和表達式、變數、語句、獲取用戶輸入等。 什麼是演算法 什麼是電腦編程呢?簡單的來說,電腦編程就是告訴電腦如何做。 而演算法只不過是流程或菜譜的時髦說法,詳盡的描述瞭如何完成某項任務,以便於電腦更好的執行。 例如下麵的菜譜,雞蛋火腿腸: ...
  • 一個龐大的分散式系統,各個組件間是如何協調工作的?組件是如何解耦的?線程運行如何更高效,減少阻塞帶來的低效問題?本節將對 Yarn 的服務庫和事件庫進行介紹,看看 Yarn 是如何解決這些問題的。 ...
  • 簡介: 原型模式,屬於創建型模式的一種。 主要針對對象進行克隆,把被克隆的對象稱之為原型,原型模式稱之為克隆模式也許更為貼切。 用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。 適用場景: 實例化對象的資源開銷過大時可直接克隆。 需要迴圈創建大量對象,此時用克隆也是一個挺不錯的選擇。 ...
  • 1 文件結構 每個C/C++程式通常分為兩個文件,頭文件(保存程式的聲明)和定義文件(保持程式的實現)。 頭文件以“.h”為尾碼;C程式的定義文件以“.c”為尾碼,C++程式的定義文件通常以“.cpp”為尾碼(也有一些“.cc”、“.cxx”、“.hpp”為尾碼)。 1.1 版權和版本的聲明 每個頭 ...
  • 前言 大家早好、午好、晚好吖 ❤ ~ 最近,一部名叫《點燃我,溫暖你》得電視劇衝進了大家得視野~ 講述得是肆意張揚的編程天才李峋與勇敢堅韌的少女學霸朱韻從青澀校園到職場拼搏幾經波折,依然攜手前行的成長愛情故事! 其中李峋用代碼做出的紅色跳動的愛心,一下子跳到朱韻的心坎里,同樣也跳到我們的心坎 今天, ...
  • 前後端分離開發,必須解決跨域問題! 跨域:對於 url 如 http://localhost:8080,請求協議、ip 地址、埠號,只要發送請求方和接收請求方的這三個數據中,只要有一個不同,就表示是跨域訪問! AJAX 跨域訪問:用戶訪問 A 網站時所產生的對 B 網站的跨域訪問請求均提交到 A ...
  • 逆向目標 猿人學 - 反混淆刷題平臺 Web 第二題:js 混淆,動態 cookie 目標:提取全部 5 頁發佈日熱度的值,計算所有值的加和 主頁:https://match.yuanrenxue.com/match/2 介面:https://match.yuanrenxue.com/api/mat ...
  • gRPC JSON轉碼 gRPC JSON 轉碼允許瀏覽器應用調用 gRPC 服務,就像它們是使用 JSON 的 RESTful API 一樣。 瀏覽器應用不需要生成 gRPC 客戶端或瞭解 gRPC 的任何信息。 通過使用 HTTP 元數據註釋 .proto 文件,可從 gRPC 服務自動創建 R ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...