草庐IT

乘风破浪,遇见最佳跨平台跨终端框架.Net Core/.Net生态 - .NET 7正式发布,看看ASP.NET Core 7.0和EF Core 7新增哪些功能

TaylorShi 2023-03-28 原文

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

安装桌面运行时

安装ASP.NET Core运行时

安装.NET运行时

通过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)

最小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上的连接字符串将被使用,而无需查找新的连接字符串。

更多关于拦截器和事件的信息

参考

有关乘风破浪,遇见最佳跨平台跨终端框架.Net Core/.Net生态 - .NET 7正式发布,看看ASP.NET Core 7.0和EF Core 7新增哪些功能的更多相关文章

  1. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  2. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  3. ruby-on-rails - 如何在发布新的 Ruby 或 Rails 版本时收到通知? - 2

    有人知道在发布新版本的Ruby和Rails时收到电子邮件的方法吗?他们有邮件列表,RubyonRails有一个推特,但我不想听到那些随之而来的喧嚣,我只想知道什么时候发布新版本,尤其是那些有安全修复的版本。 最佳答案 从therailsblog获取提要.http://weblog.rubyonrails.org/feed/atom.xml 关于ruby-on-rails-如何在发布新的Ruby或Rails版本时收到通知?,我们在StackOverflow上找到一个类似的问题:

  4. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  5. ruby-on-rails - 您希望看到哪些 Rails 插件? - 2

    您认为可以作为插件很好地存在于您的Rails应用程序中必须实现的哪些行为?您过去曾搜索过哪些插件功能但找不到?哪些现有的Rails插件可以改进或扩展,如何改进或扩展? 最佳答案 我希望在管理界面中看到一个引擎插件,它提供了应用程序中所有模型的仪表板摘要,以及可配置的事件图表。 关于ruby-on-rails-您希望看到哪些Rails插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questio

  6. ruby-on-rails - rails 功能测试 - 2

    在Rails自动生成的功能测试(test/functional/products_controller_test.rb)中,我看到以下代码:classProductsControllerTest我的问题是:方法调用products()在哪里/如何定义?products(:one)到底是什么意思?看代码,大概意思是“创建一个产品”,但是它是如何工作的呢?注意我是Ruby/Rails的新手,如果这些是微不足道的问题,我深表歉意。 最佳答案 如果您查看test/fixtures文件夹,您会看到一个products.yml文件。这是在您创建

  7. ruby - sinatra 框架的 MVC 模式 - 2

    我想开始使用“Sinatra”框架进行编码,但我找不到该框架的“MVC”模式。是“MVC-Sinatra”模式或框架吗? 最佳答案 您可能想查看Padrino这是一个围绕Sinatra构建的框架,可为您的项目提供更“类似Rails”的感觉,但没有那么多隐藏的魔法。这是使用Sinatra可以做什么的一个很好的例子。虽然如果您需要开始使用这很好,但我个人建议您将它用作学习工具,以对您来说最有意义的方式使用Sinatra构建您自己的应用程序。写一些测试/期望,写一些代码,通过测试-重复:)至于ORM,你还应该结帐Sequel其中(imho

  8. ruby - 实现k最近邻需要哪些数据? - 2

    我目前有一个reddit克隆类型的网站。我正在尝试根据我的用户之前喜欢的帖子推荐帖子。看起来K最近邻或k均值是执行此操作的最佳方法。我似乎无法理解如何实际实现它。我看过一些数学公式(例如k表示维基百科页面),但它们对我来说并没有真正意义。有人可以推荐一些伪代码,或者可以查看的地方,以便我更好地了解如何执行此操作吗? 最佳答案 K最近邻(又名KNN)是一种分类算法。基本上,您采用包含N个项目的训练组并对它们进行分类。如何对它们进行分类完全取决于您的数据,以及您认为该数据的重要分类特征是什么。在您的示例中,这可能是帖子类别、谁发布了该项

  9. ruby-on-rails - 功能测试 Authlogic? - 2

    在我的一些Controller中,我有一个before_filter检查用户是否登录?用于CRUD操作。application.rbdeflogged_in?unlesscurrent_userredirect_toroot_pathendendprivatedefcurrent_user_sessionreturn@current_user_sessionifdefined?(@current_user_session)@current_user_session=UserSession.findenddefcurrent_userreturn@current_userifdefine

  10. ruby-on-rails - 获取 ActionController::RoutingError(当尝试使用 AngularJS 将数据发布到 Rails 服务器时,没有路由匹配 [OPTIONS] "/users" - 2

    尝试从我的AngularJS端将数据发布到Rails服务器时出现问题。服务器错误:ActionController::RoutingError(Noroutematches[OPTIONS]"/users"):actionpack(4.1.9)lib/action_dispatch/middleware/debug_exceptions.rb:21:in`call'actionpack(4.1.9)lib/action_dispatch/middleware/show_exceptions.rb:30:in`call'railties(4.1.9)lib/rails/rack/logg

随机推荐