十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
.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得到了微软的正式支持。它被标记为一个标准期限支持(STS)版本,将被支持18个月。奇数的.NET版本是STS版本,在随后的STS或LTS版本之后,可以获得6个月的免费支持和补丁。
https://dotnet.microsoft.com/zh-cn/download/dotnet/7.0
安装SDK
安装桌面运行时
安装ASP.NET Core运行时
安装.NET运行时
安装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
ASP.NET Core 7.0 的新增功能
[JSImport]
/[JSExport]
支持,在WebAssembly上运行时优化JavaScript互操作调用。大多数关系数据库都支持包含JSON文档的列,这些列中的JSON可以通过查询进行钻取。例如,这允许按文档内的属性进行筛选和排序,以及将文档中的属性投影到结果中。JSON列允许关系数据库具有文档数据库的某些特征,从而在两者之间创建有用的混合;它们还可用于消除查询中的联接,从而提高性能。
EF7包含对JSON列的提供程序无关支持,以及SQLServer的实现。此支持允许将从.NET类型生成的聚合映射到JSON文档。可以在聚合上使用普通的LINQ查询,这些查询将转换为钻取到JSON所需的相应查询构造。EF7还支持保存对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文档的部分。
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;
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 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 DeletePost(int id)
=> await _context.Posts.Where(p => p.Id == id).ExecuteDeleteAsync() == 0
? NotFound()
: Ok();
这既是更少的代码,也是明显的速度,因为它只执行了一次数据库往返。
ExecuteUpdateAsync
和ExecuteDeleteAsync
是简单、明确的更新和删除的最佳选择。
然而,请记住,
ExecuteUpdateAsync
和ExecuteDeleteAsync
的多次调用不会被自动包裹在一个事务中。所有这些意味着ExecuteUpdateAsync
和ExecuteDeleteAsync
是对现有SaveChanges
机制的补充,而不是取代。
在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上的速度快四倍,这取决于环境因素,如应用程序和数据库之间的延迟。
这消除了两个数据库往返,这会使整体性能产生巨大影响,尤其是在对数据库的调用延迟较高时。在典型的生产系统中,数据库不与应用程序位于同一台计算机上。这意味着延迟通常相对较高,使得这种优化在实际生产系统中特别有效。
默认情况下,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数据库模式是非规范化的。
思考以下这样一个示例
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 Humans { get; } = new List();
}
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 Pets { get; } = new List();
}
这是在OnModelCreating
中使用UseTpcMappingStrategy
将其映射到TPC
表的案例:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity().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生成的查询比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]
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 Albums { get; } = new List();
对于大多数应用程序来说,使用List
是一个很好的默认值。然而,如果你使用一个基于XAML的框架,如WPF、WinUI或.NET MAUI,你经常想使用ObservableCollection
来实现数据绑定。
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
{
"System",
"System.Collections.Generic",
"System.Collections.ObjectModel"
};
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 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
中使用InsertUsingStoredProcedure
、UpdateUsingStoredProcedure
和DeleteUsingStoredProcedure
来映射存储过程。
例如,要为Person
实体类型映射存储过程。
modelBuilder.Entity()
.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包括以下拦截器的增强功能
DbUpdateConcurrencyException
)DbConnection
的拦截DbCommand
初始化后的拦截此外,EF7还包括新的传统的.NET事件,用于:
DetectChanges
拦截)新的IMaterializationInterceptor
支持在实体实例被创建前后以及该实例的属性被初始化前后进行拦截。
拦截器可以在每个点上改变或替换实体实例。这允许:
例如,想象一下,我们想跟踪一个实体从数据库中检索的时间,也许这样就可以显示给编辑数据的用户。为此可以创建一个物化拦截器。
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 Customers => Set();
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(
b => b.UseSqlServer();
然后,可以实现IDbConnectionInterceptor
方法之一,在使用连接之前对其进行配置。
ConnectionOpeningAsync
是一个很好的选择,因为它可以执行一个异步操作来获取连接字符串,找到一个访问令牌,等等。
public class ConnectionStringInitializationInterceptor : DbConnectionInterceptor
{
private readonly IClientConnectionStringFactory _connectionStringFactory;
public ConnectionStringInitializationInterceptor(IClientConnectionStringFactory connectionStringFactory)
{
_connectionStringFactory = connectionStringFactory;
}
public override async ValueTask ConnectionOpeningAsync(
DbConnection connection, ConnectionEventData eventData, InterceptionResult result,
CancellationToken cancellationToken = new())
{
if (string.IsNullOrEmpty(connection.ConnectionString))
{
connection.ConnectionString = (await _connectionStringFactory.GetConnectionStringAsync(cancellationToken));
}
return result;
}
}
请注意,只有在第一次使用连接时才会获得连接字符串。在那之后,存储在
DbConnection
上的连接字符串将被使用,而无需查找新的连接字符串。