乘風破浪,遇見最佳跨平臺跨終端框架.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
  • 概述:本文代碼示例演示瞭如何在WPF中使用LiveCharts庫創建動態條形圖。通過創建數據模型、ViewModel和在XAML中使用`CartesianChart`控制項,你可以輕鬆實現圖表的數據綁定和動態更新。我將通過清晰的步驟指南包括詳細的中文註釋,幫助你快速理解並應用這一功能。 先上效果: 在 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • 概述:本示例演示了在WPF應用程式中實現多語言支持的詳細步驟。通過資源字典和數據綁定,以及使用語言管理器類,應用程式能夠在運行時動態切換語言。這種方法使得多語言支持更加靈活,便於維護,同時提供清晰的代碼結構。 在WPF中實現多語言的一種常見方法是使用資源字典和數據綁定。以下是一個詳細的步驟和示例源代 ...
  • 描述(做一個簡單的記錄): 事件(event)的本質是一個委托;(聲明一個事件: public event TestDelegate eventTest;) 委托(delegate)可以理解為一個符合某種簽名的方法類型;比如:TestDelegate委托的返回數據類型為string,參數為 int和 ...
  • 1、AOT適合場景 Aot適合工具類型的項目使用,優點禁止反編 ,第一次啟動快,業務型項目或者反射多的項目不適合用AOT AOT更新記錄: 實實在在經過實踐的AOT ORM 5.1.4.117 +支持AOT 5.1.4.123 +支持CodeFirst和非同步方法 5.1.4.129-preview1 ...
  • 總說周知,UWP 是運行在沙盒裡面的,所有許可權都有嚴格限制,和沙盒外交互也需要特殊的通道,所以從根本杜絕了 UWP 毒瘤的存在。但是實際上 UWP 只是一個應用模型,本身是沒有什麼許可權管理的,許可權管理全靠 App Container 沙盒控制,如果我們脫離了這個沙盒,UWP 就會放飛自我了。那麼有沒... ...
  • 目錄條款17:讓介面容易被正確使用,不易被誤用(Make interfaces easy to use correctly and hard to use incorrectly)限制類型和值規定能做和不能做的事提供行為一致的介面條款19:設計class猶如設計type(Treat class de ...
  • title: 從零開始:Django項目的創建與配置指南 date: 2024/5/2 18:29:33 updated: 2024/5/2 18:29:33 categories: 後端開發 tags: Django WebDev Python ORM Security Deployment Op ...
  • 1、BOM對象 BOM:Broswer object model,即瀏覽器提供我們開發者在javascript用於操作瀏覽器的對象。 1.1、window對象 視窗方法 // BOM Browser object model 瀏覽器對象模型 // js中最大的一個對象.整個瀏覽器視窗出現的所有東西都 ...