EF Core 10.0(EF10)于 2025 年 11 月发布,是长期支持(LTS)版本。 EF10 将支持到 2028 年 11 月 10 日。
Tip
可通过从 GitHub 下载示例代码来运行和调试示例。 下面的每个部分都链接到特定于该部分的源代码。
EF10 需要 .NET 10 SDK 才能生成,并且需要运行 .NET 10 运行时。 EF10 不会在早期 .NET 版本上运行,也不会在 .NET Framework 上运行。
以下发行说明列出了版本中的主要改进; 可在此处找到发布问题的完整列表。
Azure SQL 和 SQL Server
矢量搜索支持
EF 10 为最近引入的 向量数据类型 及其支持 VECTOR_DISTANCE() 函数(在 Azure SQL 数据库和 SQL Server 2025 上提供)提供完全支持。 矢量数据类型允许存储 嵌入内容,这些嵌入表示形式意味着可以有效地搜索相似性,为 AI 工作负载(如语义搜索和检索扩充生成(RAG)提供支持。
若要使用 vector 数据类型,只需将类型 SqlVector<float> 为 .NET 属性添加到实体类型:
public class Blog
{
// ...
[Column(TypeName = "vector(1536)")]
public SqlVector<float> Embedding { get; set; }
}
然后,通过填充 Embedding 属性并像往常一样调用 SaveChangesAsync() 来插入嵌入数据:
IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator = /* Set up your preferred embedding generator */;
var embedding = await embeddingGenerator.GenerateVectorAsync("Some text to be vectorized");
context.Blogs.Add(new Blog
{
Name = "Some blog",
Embedding = new SqlVector<float>(embedding)
});
await context.SaveChangesAsync();
最后,使用 EF.Functions.VectorDistance() LINQ 查询中的函数对给定用户查询执行相似性搜索:
var sqlVector = /* some user query which we should search for similarity */;
var topSimilarBlogs = context.Blogs
.OrderBy(b => EF.Functions.VectorDistance("cosine", b.Embedding, sqlVector))
.Take(3)
.ToListAsync();
有关矢量搜索的详细信息, 请参阅文档。
JSON 类型支持
EF 10 还完全支持新的 json 数据类型,也可在 Azure SQL 数据库和 SQL Server 2025 上使用。 虽然 SQL Server 包含多个版本的 JSON 功能,但数据本身存储在数据库中的纯文本列中;新的数据类型提供了显著的效率改进和一种更安全的方式来存储和与 JSON 交互。
使用 EF 10 时,如果已将 EF 配置为 UseAzureSql() 兼容级别为 170 或更高版本(SQL Server 2025),EF 会自动默认使用新的 JSON 数据类型。 例如,以下实体类型具有基元集合(标记、字符串数组)和详细信息(映射为复杂类型):
public class Blog
{
public int Id { get; set; }
public string[] Tags { get; set; }
public required BlogDetails Details { get; set; }
}
public class BlogDetails
{
public string? Description { get; set; }
public int Viewers { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().ComplexProperty(b => b.Details, b => b.ToJson());
}
EF 10 为上面创建下表:
CREATE TABLE [Blogs] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NOT NULL,
[Tags] json NOT NULL,
[Details] json NOT NULL,
CONSTRAINT [PK_Blogs] PRIMARY KEY ([Id])
);
LINQ 查询也完全受支持。 例如,以下博客筛选器包含 3 个以上的查看者:
var highlyViewedBlogs = await context.Blogs.Where(b => b.Details.Viewers > 3).ToListAsync();
这会生成以下 SQL,从而使用新 JSON_VALUE()RETURNING 子句:
SELECT [b].[Id], [b].[Name], [b].[Tags], [b].[Details]
FROM [Blogs] AS [b]
WHERE JSON_VALUE([b].[Details], '$.Viewers' RETURNING int) > 3
请注意,如果 EF 应用程序已通过列使用 JSON nvarchar ,这些列将自动更改为 json 首次迁移。 可以通过手动将列类型设置为 nvarchar(max)或配置低于 170 的兼容级别来选择退出此类型。
自定义默认约束名称
EF 10 现在允许指定默认约束的名称,而不是让数据库生成它们:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.Property(p => b.CreatedDate)
.HasDefaultValueSql("GETDATE()", "DF_Post_CreatedDate");
}
还可以通过 EF 启用所有默认约束的自动命名:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.UseNamedDefaultConstraints();
}
Note
如果有现有迁移,则添加的下一个迁移将重命名模型中的每一个默认约束。
Azure Cosmos DB for NoSQL
全文搜索支持
Azure Cosmos DB 现在支持 全文搜索。 它可实现高效有效的文本搜索,以及评估文档与给定搜索查询的相关性。 它可以与矢量搜索结合使用,以提高某些 AI 方案中的响应的准确性。 EF Core 10 正在添加对此功能的支持,允许在面向 Azure Cosmos DB 的查询中使用启用了全文搜索的属性对数据库建模,并使用全文搜索函数。
下面是一种基本 EF 模型配置,用于对其中一个属性启用全文搜索:
public class Blog
{
...
public string Contents { get; set; }
}
public class BloggingContext
{
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>(b =>
{
b.Property(x => x.Contents).EnableFullTextSearch();
b.HasIndex(x => x.Contents).IsFullTextIndex();
});
}
}
配置模型后,我们可以利用EF.Functions中提供的方法在查询中执行全文搜索操作。
var cosmosBlogs = await context.Blogs.Where(x => EF.Functions.FullTextContains(x.Contents, "cosmos")).ToListAsync();
目前支持以下全文操作:FullTextContains、FullTextContainsAll、FullTextContainsAny、FullTextScore。
有关 Cosmos 全文搜索的详细信息,请参阅 文档。
混合搜索
EF Core 现在支持 RRF (倒数排名融合) 函数,该函数结合了矢量相似性搜索和全文搜索(即混合搜索)。 下面是使用混合搜索的示例查询:
float[] myVector = /* generate vector data from text, image, etc. */
var hybrid = await context.Blogs.OrderBy(x => EF.Functions.Rrf(
EF.Functions.FullTextScore(x.Contents, "database"),
EF.Functions.VectorDistance(x.Vector, myVector)))
.Take(10)
.ToListAsync();
有关 Cosmos 混合搜索的详细信息, 请参阅文档。
矢量相似性搜索退出预览版
在 EF9 中,添加了 对矢量相似性搜索的实验性支持。 在 EF Core 10 中,矢量相似性搜索支持不再具有实验性。 我们还对该功能进行了一些改进:
- EF Core 现在可以使用在拥有的引用实体上定义的矢量属性生成容器。 使用在拥有的集合上定义的矢量属性的容器仍需通过其他方式创建。 但是,可以在查询中使用它们。
- 模型生成 API 已重命名。 现在可以使用该方法配置
IsVectorProperty向量属性,并且可以使用该方法IsVectorIndex配置向量索引。
有关 Cosmos 矢量搜索的详细信息, 请参阅文档。
提升模型进化时的体验
在以前版本的 EF Core 中,使用 Azure Cosmos DB 时,改进模型相当痛苦。 具体而言,在向实体添加新的必需属性时,EF 将无法再具体化该实体。 原因是 EF 预期新属性的值(因为它是必需的),但在更改之前创建的文档不包含这些值。 解决方法是先将属性标记为可选,手动添加属性的默认值,然后才将其更改为必需。
在 EF 10 中,我们改进了此体验 - 如果文档中缺少相关数据,EF 现在将为必需属性生成默认值,而不是抛出异常。
其他 Cosmos 改进
- 使用 ExecutionStrategy 执行查询(重试)(#35692)。
复杂类型
复杂类型用于模型类型,这些类型包含在实体类型中,并且没有自己的标识;虽然实体类型(通常)映射到数据库表,但复杂类型可以映射到其容器表中的列(“表拆分”),或映射到单个 JSON 列。 复杂类型引入了文档建模技术,这能够带来显著的性能优势,因为使用复杂类型可以避免传统 JOIN 的操作,使得数据库建模变得更加简单和自然。
表拆分
例如,以下将客户的地址映射为复杂类型:
modelBuilder.Entity<Customer>(b =>
{
b.ComplexProperty(c => c.ShippingAddress);
b.ComplexProperty(c => c.BillingAddress);
});
在关系数据库上,这会导致地址映射到主 Customers 表中的其他列:
CREATE TABLE [Customers] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NOT NULL,
[BillingAddress_City] nvarchar(max) NOT NULL,
[BillingAddress_PostalCode] nvarchar(max) NOT NULL,
[BillingAddress_Street] nvarchar(max) NOT NULL,
[BillingAddress_StreetNumber] int NOT NULL,
[ShippingAddress_City] nvarchar(max) NOT NULL,
[ShippingAddress_PostalCode] nvarchar(max) NOT NULL,
[ShippingAddress_Street] nvarchar(max) NOT NULL,
[ShippingAddress_StreetNumber] int NOT NULL,
CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
);
请注意,将地址映射到单独的表并使用外键来表示客户/地址关系的默认传统关系行为的差异。
虽然自 EF 8 以来已可能上述内容,但 EF 10 添加了对 可选 类型的支持:
public class Customer
{
...
public Address ShippingAddress { get; set; }
public Address? BillingAddress { get; set; }
}
请注意,可选复杂类型当前要求在复杂类型上定义至少一个必需的属性。
JSON
EF 10 现在允许将复杂类型映射到 JSON:
modelBuilder.Entity<Customer>(b =>
{
b.ComplexProperty(c => c.ShippingAddress, c => c.ToJson());
b.ComplexProperty(c => c.BillingAddress, c => c.ToJson());
});
这会导致 EF 将每个地址映射到客户表中的单个 JSON 列。 使用新的 SQL Server 2025 JSON 列(见上图),这会导致创建下表:
CREATE TABLE [Customers] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NOT NULL,
[ShippingAddress] json NOT NULL,
[BillingAddress] json NOT NULL NULL,
CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
);
与表拆分不同,JSON 映射允许映射类型的集合。 可以像任何其他非 JSON 属性一样查询和更新 JSON 文档中的属性,并通过(ExecuteUpdateAsync)对其执行高效的批量更新。
结构支持
复杂类型还支持映射 .NET 结构,而不是类:
public struct Address
{
public required string Street { get; set; }
public required string City { get; set; }
public required string ZipCode { get; set; }
}
这非常符合复杂类型本身没有标识这一点,并且它们仅存在于具有标识的实体类型中。 但是,目前不支持结构集合。
复杂且拥有的实体类型
在 EF 10 发布之前,表拆分和 JSON 映射已经通过拥有实体建模得到支持。 但是,该建模引发了许多问题,源于拥有的实体类型是实体类型,因此仍然使用引用语义和身份标识进行操作。
例如,尝试将客户的帐单地址指定为与其寄送地址相同的地址会失败,因为拥有实体类型的限制是无法多次引用同一实体类型。
var customer = await context.Customers.SingleAsync(c => c.Id == someId);
customer.BillingAddress = customer.ShippingAddress;
await context.SaveChangesAsync(); // ERROR
相比之下,由于复杂类型具有值语义,因此分配它们只是按预期复制其属性。 出于同样的原因,不支持批量分配自有实体类型,而复杂类型在 EF 10 中完全支持 ExecuteUpdateAsync (请参阅发行说明)。
同样,比较 LINQ 查询中客户的发货地址和帐单地址无法按预期工作,因为实体类型由其标识进行比较;另一方面,复杂类型由其内容进行比较,从而产生预期结果。
这些问题(以及其他各种问题)使复杂类型成为对 JSON 和表拆分进行建模的更好选择,建议已经在使用所有权实体类型的用户切换为复杂类型。
LINQ 和 SQL 翻译
改进了参数化集合的转换
关系数据库的一个臭名昭著的难题是涉及 参数化集合的查询:
int[] ids = [1, 2, 3];
var blogs = await context.Blogs.Where(b => ids.Contains(b.Id)).ToListAsync();
在上述查询中, ids 是参数化集合:可以多次执行同一查询, ids 每次包含不同的值。
由于关系数据库通常不支持将集合直接作为参数发送,因此 EF 在 8.0 版本之前的版本只是将集合内容以内联的方式作为常量嵌入 SQL 中。
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] IN (1, 2, 3)
虽然这很有效,但它会导致为不同集合生成不同的 SQL,从而引起数据库计划缓存未命中和膨胀,并造成各种性能问题(请参阅 #13617,这是当时在存储库中收到最多投票的问题)。 因此,EF 8.0 利用了广泛的 JSON 支持,并更改了参数化集合的转换,以使用 JSON 数组(发行说明):
@__ids_0='[1,2,3]'
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] IN (
SELECT [i].[value]
FROM OPENJSON(@__ids_0) WITH ([value] int '$') AS [i]
)
在这里,集合编码为包含 JSON 数组的字符串,作为单个参数发送,然后使用 SQL Server OPENJSON 函数(其他数据库使用类似的机制)解包。 由于集合现已参数化,因此无论集合包含的值如何,SQL 都保持不变,并且单个查询计划保持不变。 遗憾的是,虽然此翻译很优雅,但它也使数据库查询规划器失去了关于集合基数(或长度)的重要信息,可能导致选择的计划只对少量或大量元素有效。 因此,EF Core 9.0 引入了控制要使用的翻译策略的功能(发行说明)。
EF 10.0 为参数化集合引入了新的默认转换模式,其中集合中的每个值都转换为其自己的标量参数:
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] IN (@ids1, @ids2, @ids3)
这样,集合值就可以更改,而不会产生不同的 SQL-解决计划缓存问题,但同时为查询规划器提供有关集合基数的信息。
由于不同的基数仍然会导致生成不同的 SQL,EF 也“pads”参数列表。 例如,如果 ids 列表包含 8 个值,EF 会生成包含 10 个参数的 SQL:
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] IN (@ids1, @ids2, @ids3, @ids4, @ids5, @ids6, @ids7, @ids8, @ids9, @ids10)
最后两个参数@ids9@ids10由 EF 添加,以减少生成的 SQL 数,并包含与相同的值@ids8,以便查询返回相同的结果。
遗憾的是,参数化集合的情况是 EF 不能始终做出正确的选择:在多个参数(新默认值)、单个 JSON 数组参数(具有e.g. SQL服务器 OPENJSON)或多个内联常量之间进行选择可能需要了解数据库中的数据,不同的选择可能更适用于不同的查询。 因此,EF 使用户能够完全控制翻译策略,可以在全局配置级别进行管理。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer("<CONNECTION STRING>", o => o.UseParameterizedCollectionMode(ParameterTranslationMode.Constant));
...在每查询级别:
var blogs = await context.Blogs.Where(b => EF.Constant(ids).Contains(b.Id)).ToListAsync();
支持 .NET 10 LeftJoin 和 RightJoin 运算符
使用 EF Core 时,LEFT JOIN 是一种常见且有用的操作。 在以前的版本中,在 LINQ 中实现 LEFT JOIN 相当复杂,需要在特定配置 中执行SelectMany、GroupJoin 和 DefaultIfEmpty 操作。
.NET 10 添加了对 LeftJoin 方法的一流 LINQ 支持,使这些查询更易于编写。 EF Core 可识别新方法,因此可以在 EF LINQ 查询中使用,而不是旧构造:
var query = context.Students
.LeftJoin(
context.Departments,
student => student.DepartmentID,
department => department.ID,
(student, department) => new
{
student.FirstName,
student.LastName,
Department = department.Name ?? "[NONE]"
});
Note
EF 10 还支持类似 RightJoin 运算符,该运算符将所有数据从第二个集合中保留,并且只保留第一个集合中的匹配数据。 EF 10 将此转换为数据库中的 RIGHT JOIN 操作。
遗憾的是,C# 查询语法(from x select x.Id)尚不支持以这种方式表达左/右联接作。
拆分查询的更一致排序
拆分查询对于避免与 JOIN 相关的性能问题至关重要,例如所谓的“笛卡尔爆炸”效果(请参阅 “单个查询与拆分查询 ”以了解详细信息)。 但是,由于拆分查询在单独的 SQL 查询中加载相关数据,因此可能会出现一致性问题,这可能导致不可确定性、难以检测的数据损坏。
例如,考虑以下查询:
var blogs = await context.Blogs
.AsSplitQuery()
.Include(b => b.Posts)
.OrderBy(b => b.Name)
.Take(2)
.ToListAsync();
在 EF10 之前,生成了以下两个 SQL 查询:
SELECT TOP(@__p_0) [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
ORDER BY [b].[Name], [b].[Id]
SELECT [p].[Id], [p].[BlogId], [p].[Title], [b0].[Id]
FROM (
SELECT TOP(@__p_0) [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
ORDER BY [b].[Name]
) AS [b0]
INNER JOIN [Post] AS [p] ON [b0].[Id] = [p].[BlogId]
ORDER BY [b0].[Name], [b0].[Id]
请注意,第一个查询(一个阅读博客)作为第二个(一个阅读文章)中的子查询集成。 但是,子查询中的排序会省略 Id 该列,从而导致可能返回不正确的数据。
EF10 通过确保排序在整个查询中保持一致来修复此问题:
SELECT [p].[Id], [p].[BlogId], [p].[Title], [b0].[Id]
FROM (
SELECT TOP(@p) [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
ORDER BY [b].[Name], [b].[Id]
) AS [b0]
INNER JOIN [Post] AS [p] ON [b0].[Id] = [p].[BlogId]
ORDER BY [b0].[Name], [b0].[Id]
其他查询改进
新翻译
- 翻译 DateOnly.ToDateTime() (#35194,由 @mseada94贡献)。
- 转换 SQL Server 和 SQLite 的 DateOnly.DayNumber 和
DayNumber减法 (#36183)。 - 使用
DatePart.Microsecond和DatePart.Nanosecond参数转换日期/时间函数(#34861)。 - 在 SQL Server 上,将
COALESCE翻译为ISNULL,在大多数情况下(#34171,由@ranma42贡献)。 - 支持一些以
char为参数的字符串函数(#34999,由@ChrisJollyAU贡献)。 - 支持
MAX/MIN/ORDER BY在 SQLite 上使用decimal(#35606,由 @ranma42 贡献)。 - 支持通过条件运算符投射不同的导航(但类型相同)(#34589,由 @ranma42 贡献)。
漏洞修复和优化
- 修复了与
DateTime、DateTimeOffset和 UTC 相关的 Microsoft.Data.Sqlite 行为,请参阅重大更改说明(#36195)。 - 修复各种场景中的
DefaultIfEmpty翻译问题: - 优化多个连续的
LIMIT(#35384,由 @ranma42 贡献)。 - 优化
Count上ICollection<T>操作的使用(#35381,由 @ChrisJollyAU 贡献)。 - 优化
MIN/MAX而非DISTINCT(#34699,由 @ranma42 贡献)。 - 简化参数名称(例如 from
@__city_0到@city) (#35200)。
对关系 JSON 列的 ExecuteUpdate 支持
Note
对 JSON 的 ExecuteUpdate 支持需要将类型映射为复杂类型(见上图),并且当类型映射为自有实体时不起作用。
尽管 EF 有一段时间支持 JSON 列,并允许通过 SaveChanges它们进行更新, ExecuteUpdate 但缺乏对它们的支持。 EF10 现在允许引用其中的 ExecuteUpdateJSON 列和属性,从而在关系数据库中高效批量更新文档建模的数据。
例如,给定以下模型,将 BlogDetails 类型映射到数据库中的复杂 JSON 列:
public class Blog
{
public int Id { get; set; }
public BlogDetails Details { get; set; }
}
public class BlogDetails
{
public string Title { get; set; }
public int Views { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().ComplexProperty(b => b.Details, bd => bd.ToJson());
}
现在可以像往常一样使用 ExecuteUpdate ,在以下项中 BlogDetails引用属性:
await context.Blogs.ExecuteUpdateAsync(s =>
s.SetProperty(b => b.Details.Views, b => b.Details.Views + 1));
这会为 SQL Server 2025 生成以下内容,该函数使用高效的新 modify 函数将 JSON 属性 Views 递增一个:
UPDATE [b]
SET [Details].modify('$.Views', JSON_VALUE([b].[Details], '$.Views' RETURNING int) + 1)
FROM [Blogs] AS [b]
也支持较旧版本的 SQL Server,其中 JSON 数据存储在 nvarchar(max) 列中,而不是新的 JSON 数据类型支持。 有关其他数据库的支持,请参阅 EF 提供程序的文档。
命名查询筛选器
EF 的 全局查询筛选器 功能使用户能够将筛选器配置为默认适用于所有查询的实体类型。 这简化了软删除、多租户等常见模式和场景的实现。 但是,到目前为止,EF 仅支持每个实体类型的单个查询筛选器,因此很难有多个筛选器,并选择性地禁用特定查询中的一些筛选器。
EF 10 引入了 命名查询筛选器,允许将名称附加到查询筛选器并单独管理每个筛选器:
modelBuilder.Entity<Blog>()
.HasQueryFilter("SoftDeletionFilter", b => !b.IsDeleted)
.HasQueryFilter("TenantFilter", b => b.TenantId == tenantId);
这值得注意的是,只允许在特定 LINQ 查询中禁用某些筛选器:
var allBlogs = await context.Blogs.IgnoreQueryFilters(["SoftDeletionFilter"]).ToListAsync();
有关命名查询筛选器的详细信息, 请参阅文档。
此功能由 @bittola提供。
ExecuteUpdateAsync 现接受常规的非表达式 lambda
ExecuteUpdateAsync 可用于表示数据库中的任意更新操作。 在以前的版本中,通过表达式树参数提供对数据库行执行的更改;这使得动态生成这些更改非常困难。 例如,假设我们要更新博客的浏览量,但根据条件还需要更新其名称。 由于 setters 参数是表达式树,因此需要编写以下代码:
// Base setters - update the Views only
Expression<Func<SetPropertyCalls<Blog>, SetPropertyCalls<Blog>>> setters =
s => s.SetProperty(b => b.Views, 8);
// Conditionally add SetProperty(b => b.Name, "foo") to setters, based on the value of nameChanged
if (nameChanged)
{
var blogParameter = Expression.Parameter(typeof(Blog), "b");
setters = Expression.Lambda<Func<SetPropertyCalls<Blog>, SetPropertyCalls<Blog>>>(
Expression.Call(
instance: setters.Body,
methodName: nameof(SetPropertyCalls<Blog>.SetProperty),
typeArguments: [typeof(string)],
arguments:
[
Expression.Lambda<Func<Blog, string>>(Expression.Property(blogParameter, nameof(Blog.Name)), blogParameter),
Expression.Constant("foo")
]),
setters.Parameters);
}
await context.Blogs.ExecuteUpdateAsync(setters);
手动创建表达式树非常复杂且容易出错,并且使此常见方案比应该的要困难得多。 从 EF 10 开始,现在可以改为编写以下代码:
await context.Blogs.ExecuteUpdateAsync(s =>
{
s.SetProperty(b => b.Views, 8);
if (nameChanged)
{
s.SetProperty(b => b.Name, "foo");
}
});
感谢 @aradalvand 提出并推动此次更改(在 #32018)。
与安全相关的改进
默认情况下,从日志记录中编辑内联常量
记录执行 SQL 时,EF 默认不会记录参数值,因为这些值可能包含敏感或个人身份信息(PII): EnableSensitiveDataLogging() 可用于在诊断或调试方案中启用日志记录参数值(请参阅文档)。
但是,EF 有时将参数内联到 SQL 语句中,而不是单独发送;在这些情况下,可能敏感的值可能被记录。 EF10 不再执行这样的操作,默认情况下对此类内联参数进行屏蔽,并将其替换为问号字符(?)。 例如,假设我们有一个接受角色列表的函数,并返回具有其中一个角色的用户列表:
Task<List<User>> GetUsersByRoles(BlogContext context, string[] roles)
=> context.Users.Where(b => roles.Contains(b.Role)).ToListAsync();
尽管此处的角色已参数化(在调用之间可能有所不同),但我们知道查询的实际角色集将相当有限。 因此,我们可以指示 EF 将角色内联,以便数据库的规划器可以对其特定角色集单独规划此查询的执行。
Task<List<User>> GetUsersByRoles(BlogContext context, string[] roles)
=> context.Users.Where(b => EF.Constant(roles).Contains(b.Role)).ToListAsync();
在以前版本的 EF 上,这生成了以下 SQL:
SELECT [u].[Id], [u].[Role]
FROM [Users] AS [u]
WHERE [u].[Role] IN (N'Administrator', N'Manager')
EF10 将相同的 SQL 命令发送到数据库,但记录以下 SQL 命令,其中角色信息已被删去:
SELECT [b].[Id], [b].[Role]
FROM [Blogs] AS [b]
WHERE [b].[Role] IN (?, ?)
如果角色表示敏感信息,这会阻止该信息泄漏到应用程序日志中。 与常规参数一样,可以通过 EnableSensitiveDataLogging() 重新启用日志记录的完整功能。
对使用原始 SQL API 进行字符串串联发出警告
SQL 注入是数据库应用程序中最严重的安全漏洞之一;如果将未批准的外部输入插入到 SQL 查询中,恶意用户可能能够执行 SQL 注入,对数据库执行任意 SQL。 有关 EF 的 SQL 查询 API 和 SQL 注入的详细信息, 请参阅文档。
尽管 EF 用户很少直接处理 SQL,但 EF 确实为需要 SQL 的情况提供基于 SQL 的 API。 面对 SQL 注入,其中大多数 API 都是安全的;例如 FromSql ,接受 .NET FormattableString,并且任何参数将自动作为单独的 SQL 参数发送,从而阻止 SQL 注入。 但是,在某些情况下,有必要通过将多个片段拼凑在一起来构建动态 SQL 查询; FromSqlRaw 专为此设计,需要用户确保所有片段都受信任或正确清理。
从 EF10 开始,EF 具有一个分析器,在“原始”SQL 方法调用中执行串联时发出警告:
var users = context.Users.FromSqlRaw("SELECT * FROM Users WHERE [" + fieldName + "] IS NULL");
如果 fieldName 受信任或已正确清理,则可以安全地取消警告。