This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
在“.NET”和“ORMS与绘图战略比较”中的数据访问@(_Part @1}{-}实体框架核心=)
Wednesday, 03 December 2025
当建设“.”NET应用程序时, “,”将做出最重要的建筑决定之一 是如何处理数据存取和天体映射”“MS K3”“.”网域生态系统提供了多种多样的方法,}从完整的@--功能性ORM 到光速-金属SQL 执行“MSG8” 每一种方法都有自己的交易\□-偏差 在性能方面,开发者生产率*,}类型安全=,}以及可维护性#.}
在此综合的两张“MSMK0”指南中,我们“, '”将探索在 “.NET.}最受欢迎的数据访问模式。 我们用 PostgreSQL 和 Npgsql 在“MS K5 ”的例子中使用了PostgresQL与Npgskql 一样的数据访问模式,因为既然如此,“MSKO6”这个博客拥有什么权力?“), ” 这个部落格里, MSSK8 型号@, 和 Trade-offers 平等适用于 SQL Server @, MySQRQLK12SQ Lite+, 和其他关联数据库。原则仍然是相同的“MSG15 QZ” 只有SQKQL方言和某些具体特点不同的“MSC16”
本篇文章=) 关注实体框架核心-,SQL Disease_,和共同陷阱=. 部分#2 将覆盖 Dapper, 原始 ADOMS K1NET}, 物体绘图库 <, 和混合方法.
相关条款
如果你对实际的EF核心执行感兴趣的话 , 看看我的其他文章
- 添加博客日志的实体框架@(+Part @1)+% 建立从零开始的EF核心
- EF 移徙的正确途径 如何正确处理生产过程中的迁移
- 完整文本搜索@ ( @ pt @ MS K1} 使用 EF 核心的文本搜索
- 现代CQRS和事件观察 具有EF核心的高级建筑图案
目录目录
数据获取方法的光谱
“.NET”数据访问图景可视化为频谱@:
Full Abstraction Full Control
↓ ↓
[EF Core] → [EF Core Raw SQL] → [Dapper] → [Npgsql ADO.NET]
当您从左向右移动时, , @ 得到性能和控制, 但失去方便和自动功能@ MS K1 @ Let' @ service 详细检查了每种方法@ MPK3}
数据获取流程比较
这里% 1\ ' @ action 比较每个方法如何处理典型的查询@ MS K1}
graph TB
subgraph "EF Core Flow"
A1[LINQ Query] -->|Compile| B1[Expression Tree]
B1 -->|Translate| C1[SQL Query]
C1 -->|Execute| D1[PostgreSQL]
D1 -->|Results| E1[DbDataReader]
E1 -->|Materialize| F1[Tracked Entities]
F1 -->|Return| G1[Application]
end
subgraph "Dapper Flow"
A2[SQL String] -->|Parameterize| B2[DbCommand]
B2 -->|Execute| C2[PostgreSQL]
C2 -->|Results| D2[DbDataReader]
D2 -->|Map| E2[POCOs]
E2 -->|Return| F2[Application]
end
subgraph "Raw Npgsql Flow"
A3[SQL + Parameters] -->|Build Command| B3[NpgsqlCommand]
B3 -->|Execute| C3[PostgreSQL]
C3 -->|Results| D3[NpgsqlDataReader]
D3 -->|Manual Mapping| E3[Objects]
E3 -->|Return| F3[Application]
end
style A1 stroke:#2563eb,stroke-width:2px
style B1 stroke:#2563eb,stroke-width:2px
style C1 stroke:#2563eb,stroke-width:2px
style D1 stroke:#2563eb,stroke-width:2px
style E1 stroke:#2563eb,stroke-width:2px
style F1 stroke:#2563eb,stroke-width:2px
style G1 stroke:#2563eb,stroke-width:2px
style A2 stroke:#059669,stroke-width:2px
style B2 stroke:#059669,stroke-width:2px
style C2 stroke:#059669,stroke-width:2px
style D2 stroke:#059669,stroke-width:2px
style E2 stroke:#059669,stroke-width:2px
style F2 stroke:#059669,stroke-width:2px
style A3 stroke:#dc2626,stroke-width:2px
style B3 stroke:#dc2626,stroke-width:2px
style C3 stroke:#dc2626,stroke-width:2px
style D3 stroke:#dc2626,stroke-width:2px
style E3 stroke:#dc2626,stroke-width:2px
style F3 stroke:#dc2626,stroke-width:2px
实绩与开发者生产率贸易
graph LR
A[High Productivity<br/>Low Performance] --> B[EF Core<br/>Full Tracking]
B --> C[EF Core<br/>No Tracking]
C --> D[EF Core<br/>Raw SQL]
D --> E[Dapper]
E --> F[Raw Npgsql]
F --> G[Low Productivity<br/>High Performance]
style A stroke:#2563eb,stroke-width:2px
style B stroke:#2563eb,stroke-width:2px
style C stroke:#3b82f6,stroke-width:2px
style D stroke:#059669,stroke-width:2px
style E stroke:#059669,stroke-width:2px
style F stroke:#dc2626,stroke-width:2px
style G stroke:#dc2626,stroke-width:2px
实体框架核心@: @ full @ -}指定ORM
实体框架核心 是 微软\ '}旗舰ORM,}为您的数据库提供一个完整的抽象@.}它支持 PostgreSQL 通过 Npgsql . - Entity FrameworkCore\ MS K1 - PostgreSQL 提供商 @.
有关在您的项目中建立EF核心的实用指南“,”见我的文章 添加实体博客日志框架.
密钥特征
- 跟踪变化情况: 自动跟踪实体变化并生成相应的 SQL
- 移徙移民:Dode-第一种方案管理和版本控制@(se EF 移徙的正确途径)
- LINQ 提供者@ :_ type @ MPK1} 使用 C## 语言构造的安全查询
- Lazy/ 高级装入相关实体的灵活装载战略
- 高级邮后SQL特性:#full_-}文本搜索@(参见我的文章), JSON 列, 数组MS K2 范围类型
- 拦截者和事件“:” 交叉问题的扩展点
具有EF核心基本CRUD
public class BlogDbContext : DbContext
{
public DbSet<BlogPost> BlogPosts { get; set; }
public DbSet<Comment> Comments { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql("Host=localhost;Database=blog;Username=postgres;Password=secret");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// PostgreSQL-specific: Full-text search
modelBuilder.Entity<BlogPost>()
.HasGeneratedTsVectorColumn(
p => p.SearchVector,
"english",
p => new { p.Title, p.Content })
.HasIndex(p => p.SearchVector)
.HasMethod("GIN");
// PostgreSQL array type
modelBuilder.Entity<BlogPost>()
.Property(p => p.Tags)
.HasPostgresArrayConversion(
tag => tag.ToLowerInvariant(),
tag => tag);
}
}
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public string[] Tags { get; set; }
public NpgsqlTsVector SearchVector { get; set; }
public List<Comment> Comments { get; set; }
public DateTime PublishedDate { get; set; }
}
// Usage
public class BlogService
{
private readonly BlogDbContext _context;
public async Task<List<BlogPost>> GetRecentPostsAsync(int count)
{
return await _context.BlogPosts
.Include(p => p.Comments)
.OrderByDescending(p => p.PublishedDate)
.Take(count)
.ToListAsync();
}
public async Task<List<BlogPost>> SearchPostsAsync(string searchTerm)
{
return await _context.BlogPosts
.Where(p => p.SearchVector.Matches(EF.Functions.ToTsQuery("english", searchTerm)))
.ToListAsync();
}
public async Task AddPostAsync(BlogPost post)
{
_context.BlogPosts.Add(post);
await _context.SaveChangesAsync();
}
}
EF 核心部分,原SQL
EF 核心也支持原始 SQL 查询, 当您需要更多的控制时@: @%
public async Task<List<BlogPost>> GetPostsByComplexCriteriaAsync()
{
var searchTerm = "postgresql";
return await _context.BlogPosts
.FromSqlInterpolated($@"
SELECT * FROM ""BlogPosts""
WHERE ""SearchVector"" @@ to_tsquery('english', {searchTerm})
AND array_length(""Tags"", 1) > 3
ORDER BY ts_rank(""SearchVector"", to_tsquery('english', {searchTerm})) DESC
")
.ToListAsync();
}
// Or with DbDataReader for maximum control
public async Task<List<PostStatistics>> GetPostStatisticsAsync()
{
using var command = _context.Database.GetDbConnection().CreateCommand();
command.CommandText = @"
SELECT
DATE_TRUNC('month', ""PublishedDate"") as Month,
COUNT(*) as PostCount,
AVG(ARRAY_LENGTH(""Tags"", 1)) as AvgTags
FROM ""BlogPosts""
GROUP BY DATE_TRUNC('month', ""PublishedDate"")
ORDER BY Month DESC";
await _context.Database.OpenConnectionAsync();
var results = new List<PostStatistics>();
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
results.Add(new PostStatistics
{
Month = reader.GetDateTime(0),
PostCount = reader.GetInt32(1),
AverageTags = reader.GetDouble(2)
});
}
return results;
}
何时使用 EF 核心
✅ 使用 EF 核心 当 @:
- 建立一个新应用程序,满足不断变化的计划要求
- 您需要强大的打字和编译@ - @ time 查询验证
- 移民与计划版本非常重要, 我的移民指南)
- 您的团队比 SQL 更喜欢工作对象
- 您正在使用复杂域模式与关系
- 发展速度比原始业绩更关键
- 您需要交叉“ -” 数据库可移植性@ MS K1 @ 尽管 PostgreSQL-}特定功能将您锁定在“ MPK3” 中 。
@❌ 避免EF核心 当@:
- 最大性能是关键 ( @high@-}通过APIs*,}批量处理=)
- 您有复杂的 SQL 查询
- 您的查询与对象图形相匹配
- 您需要精细的 SQL 语句控制@ - @ grained 控制
- 内存用量是一个关键的制约因素 @(#change track subject@)
- 您正在与公约地图上的遗留方案合作
EF SQL 核心SQL Pages *:}了解执行什么方法
有效使用EF核心的最重要方面之一是了解它产生什么SQL=.EF Core多年来大大改进了 SQL 一代人={,但对于核实发送到PostgreSQQL.}的询问至关重要
查看生成的 SQL
// Enable sensitive data logging and detailed errors (development only!)
optionsBuilder
.UseNpgsql(connectionString)
.EnableSensitiveDataLogging()
.EnableDetailedErrors()
.LogTo(Console.WriteLine, LogLevel.Information);
// Or use logging to see SQL
public class BlogService
{
private readonly BlogDbContext _context;
private readonly ILogger<BlogService> _logger;
public async Task<List<BlogPost>> GetPostsAsync()
{
var query = _context.BlogPosts
.Where(p => p.PublishedDate > DateTime.UtcNow.AddDays(-30))
.OrderByDescending(p => p.PublishedDate);
// View the SQL before execution
var sql = query.ToQueryString();
_logger.LogInformation("Executing query: {Sql}", sql);
return await query.ToListAsync();
}
}
示例@:简单查询
? ?
var recentPosts = await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.OrderByDescending(p => p.PublishedDate)
.Take(10)
.ToListAsync();
生成 SQL @ (EF 核心“8+”):
SELECT b."Id", b."Title", b."Content", b."CategoryId", b."PublishedDate"
FROM "BlogPosts" AS b
WHERE b."CategoryId" = @__categoryId_0
ORDER BY b."PublishedDate" DESC
LIMIT @__p_1
注意 EF 核心 @ 8+是如何生成清洁的\ MS K1 高效 SQL 且具有适当的参数化@ . EF 核心“10” 这一趋势继续发展下去,取得了更多的进步。
例: 加入包含 @( 在EF核心@5)之前
C# 代码□:
var posts = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.ToListAsync();
旧的SQL @(EF核心 #3.1}#-笛卡尔爆炸):#*
SELECT b."Id", b."Title", c."Id", c."Name", cm."Id", cm."Content"
FROM "BlogPosts" AS b
LEFT JOIN "Categories" AS c ON b."CategoryId" = c."Id"
LEFT JOIN "Comments" AS cm ON b."Id" = cm."BlogPostId"
ORDER BY b."Id", c."Id"
这样产生 笛卡尔产品 - @ {如果邮递有10} obsession * MS K2} 该行被重覆 {10} 乘以_!}
插件::+SplitQuery @(_EF核心@5+)
C代码, 带有分裂查询} :
var posts = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.AsSplitQuery() // ← This is the key!
.ToListAsync();
生成 SQL @ ( @ 多端查询@ MS K1
-- Query 1: Get posts and categories
SELECT b."Id", b."Title", b."Content", c."Id", c."Name"
FROM "BlogPosts" AS b
LEFT JOIN "Categories" AS c ON b."CategoryId" = c."Id"
-- Query 2: Get comments for those posts
SELECT cm."Id", cm."Content", cm."BlogPostId"
FROM "Comments" AS cm
INNER JOIN (
SELECT b."Id"
FROM "BlogPosts" AS b
) AS t ON cm."BlogPostId" = t."Id"
ORDER BY t."Id"
这消除了笛卡尔人的产品,而且往往 速度快得多 用于收藏@! @%
框注@:BARBAR 过滤器中包含 @_EF 核心 @MS K2}
C# 代码□:
var posts = await _context.BlogPosts
.Include(p => p.Comments.Where(c => c.IsApproved))
.ToListAsync();
生成 SQL:
SELECT b."Id", b."Title", b."Content", t."Id", t."Content", t."IsApproved"
FROM "BlogPosts" AS b
LEFT JOIN (
SELECT c."Id", c."Content", c."IsApproved", c."BlogPostId"
FROM "Comments" AS c
WHERE c."IsApproved" = TRUE
) AS t ON b."Id" = t."BlogPostId"
ORDER BY b."Id"
例如::JSON列 查询: (EF核心:
C# 代码□:
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public PostMetadata Metadata { get; set; } // Stored as JSONB
}
public class PostMetadata
{
public bool IsFeatured { get; set; }
public int ViewCount { get; set; }
public List<string> RelatedTags { get; set; }
}
// Query JSON properties
var featuredPosts = await _context.BlogPosts
.Where(p => p.Metadata.IsFeatured)
.ToListAsync();
生成 SQL:
SELECT b."Id", b."Title", b."Metadata"
FROM "BlogPosts" AS b
WHERE b."Metadata" ->> 'IsFeatured' = 'true'
EF Core {7+可以翻译JSON的地产访问到邮电站 JSON操作员@!}
执行更新@) @
旧路 # M(# 效率不高 @ ):@
var posts = await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.ToListAsync();
foreach (var post in posts)
{
post.IsArchived = true;
}
await _context.SaveChangesAsync(); // Generates N UPDATE statements!
新的路 #(#EF核心#7+):#
await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.IsArchived, true));
生成 SQL @ ( @ single 查询@ MS K1
UPDATE "BlogPosts" AS b
SET "IsArchived" = TRUE
WHERE b."CategoryId" = 5
这是 大规模 @-一个SQL 语句,而不是 NZ!
框注::Bulk 删除 @(EF核心@7+)
旧路 :
var oldPosts = await _context.BlogPosts
.Where(p => p.PublishedDate < DateTime.UtcNow.AddYears(-5))
.ToListAsync();
_context.BlogPosts.RemoveRange(oldPosts);
await _context.SaveChangesAsync(); // N DELETE statements
新的路 #:#
await _context.BlogPosts
.Where(p => p.PublishedDate < DateTime.UtcNow.AddYears(-5))
.ExecuteDeleteAsync();
生成 SQL:
DELETE FROM "BlogPosts" AS b
WHERE b."PublishedDate" < @__p_0
实例::-复杂聚合
C# 代码□:
var categoryStats = await _context.Categories
.Select(c => new CategoryStats
{
CategoryName = c.Name,
PostCount = c.BlogPosts.Count(),
LatestPostDate = c.BlogPosts.Max(p => p.PublishedDate),
AverageComments = c.BlogPosts.Average(p => p.Comments.Count)
})
.ToListAsync();
生成 SQL @ (_ EF 核心@ 8+/10):
SELECT c."Name" AS "CategoryName",
COUNT(*)::int AS "PostCount",
MAX(b."PublishedDate") AS "LatestPostDate",
COALESCE(AVG((
SELECT COUNT(*)::int
FROM "Comments" AS c0
WHERE b."Id" = c0."BlogPostId"
))::double precision, 0.0) AS "AverageComments"
FROM "Categories" AS c
LEFT JOIN "BlogPosts" AS b ON c."Id" = b."CategoryId"
GROUP BY c."Id", c."Name"
PostgreSQL 全@ - @ Text 搜索
更深的潜入完整@-_text search @,}见我的文章 使用 EF 核心的文本搜索.
C# 代码□:
var searchResults = await _context.BlogPosts
.Where(p => p.SearchVector.Matches(EF.Functions.ToTsQuery("english", "postgresql & performance")))
.OrderByDescending(p => p.SearchVector.Rank(EF.Functions.ToTsQuery("english", "postgresql & performance")))
.Take(20)
.ToListAsync();
生成 SQL:
SELECT b."Id", b."Title", b."Content", b."SearchVector"
FROM "BlogPosts" AS b
WHERE b."SearchVector" @@ to_tsquery('english', @__searchTerm_0)
ORDER BY ts_rank(b."SearchVector", to_tsquery('english', @__searchTerm_0)) DESC
LIMIT 20
关键EF核心警告和瀑布
1. 变化跟踪记忆泄漏
问题:
// ❌ DANGER: This can cause memory leaks!
public class PostCache
{
private readonly BlogDbContext _context;
private List<BlogPost> _cachedPosts;
public PostCache(BlogDbContext context)
{
_context = context;
}
public async Task LoadCacheAsync()
{
// These entities are now tracked by the context
_cachedPosts = await _context.BlogPosts.ToListAsync();
// The DbContext holds references to these entities FOREVER
// They can never be garbage collected while the context lives!
}
}
为什么这有什么问题?
- 跟踪实体在《公约》生效期间的记忆
DbContext - 更改跟踪器维护引用@, 防止垃圾收集
- Long -活环境 @(e.gMS K3单吨@)#=内存泄漏
- 在 ASP.NET Core, 上下文默认范围为 @ (Good}!)
- 但如果你隐藏了追踪实体 你麻烦大了
解决方案#:
public async Task LoadCacheAsync()
{
// ✅ Use AsNoTracking() for read-only queries
_cachedPosts = await _context.BlogPosts
.AsNoTracking()
.ToListAsync();
// Or detach entities after loading
var posts = await _context.BlogPosts.ToListAsync();
foreach (var post in posts)
{
_context.Entry(post).State = EntityState.Detached;
}
_cachedPosts = posts;
}
2. 代理产生和懒惰装载危险
-=YTET -伊甸园字幕组=- 翻译:
Lazy 装货代理 * @ + @ caching # =@ label 被保障的记忆沉默
如果您缓存 DbContext 实例或含有已启用代理文件的缓存实体@, @ 您 意志 代理机制仍然引用 DbContext, 防止垃圾收集@. 这是EF核心应用程序中最常见和危险的错误之一 @.}
拇指规则: 总是包括收藏
.Include()“. ” 只有完全理解权衡并永远不隐藏代理实体“.”才使用代理人。
问题 @1:N+1查询噩梦
// ❌ Enable lazy loading
optionsBuilder
.UseNpgsql(connectionString)
.UseLazyLoadingProxies(); // Convenient but dangerous!
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public virtual Category Category { get; set; } // Virtual = proxy
public virtual List<Comment> Comments { get; set; }
}
// Somewhere in your code
var posts = await _context.BlogPosts.ToListAsync();
foreach (var post in posts)
{
Console.WriteLine(post.Category.Name); // N+1 query here!
Console.WriteLine(post.Comments.Count); // Another N+1 query!
}
发生什么事了?
- 首次查询加载所有员额
- 用于 每个员额@, 访问
Category触发数据库查询 - 用于 每个员额@, 访问
Comments触发另一个查询 - 如果您有 @100 @ poss @ ,_ 您刚刚执行处决 201查询!
问题 @2: 代理@+ 缓冲 @ = 内存漏
// ❌ CATASTROPHIC: Lazy loading proxies + caching
public class BlogPostCache
{
private static List<BlogPost> _cachedPosts;
private readonly BlogDbContext _context;
public BlogPostCache()
{
var optionsBuilder = new DbContextOptionsBuilder<BlogDbContext>();
optionsBuilder
.UseNpgsql(connectionString)
.UseLazyLoadingProxies(); // ⚠️ DANGER!
_context = new BlogDbContext(optionsBuilder.Options);
}
public async Task<List<BlogPost>> GetCachedPostsAsync()
{
if (_cachedPosts == null)
{
// ❌ These proxy entities hold references to _context
_cachedPosts = await _context.BlogPosts.ToListAsync();
}
return _cachedPosts;
}
}
为什么这是灾难性的?
- 代理实体保持提及其
DbContext - 缩略
DbContext继续提及所有跟踪实体 - 您的缓存现在防止了整个对象图形被垃圾回收
- 每次访问导航属性 , 已缓存的上下文@ option: check
- 内存随着您加载更多数据而变得无约束
- 您最终会用完内存或排气连接池
答案=: 明确
// ✅ NEVER use lazy loading proxies - always be explicit
optionsBuilder
.UseNpgsql(connectionString);
// NO .UseLazyLoadingProxies()!
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public Category Category { get; set; } // NOT virtual
public List<Comment> Comments { get; set; } // NOT virtual
}
// ✅ Explicit eager loading - you control what's loaded
var posts = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.ToListAsync();
// ✅ Or use split queries for better performance
var posts = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.AsSplitQuery()
.ToListAsync();
// ✅ Or use projection to DTOs (best for caching)
var posts = await _context.BlogPosts
.Select(p => new PostDto
{
Title = p.Title,
CategoryName = p.Category.Name,
CommentCount = p.Comments.Count
})
.ToListAsync();
// ✅ If you MUST cache, use AsNoTracking and no proxies
public class SafeBlogPostCache
{
private static List<BlogPost> _cachedPosts;
private readonly IDbContextFactory<BlogDbContext> _contextFactory;
public async Task<List<BlogPost>> GetCachedPostsAsync()
{
if (_cachedPosts == null)
{
using var context = await _contextFactory.CreateDbContextAsync();
_cachedPosts = await context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.AsNoTracking() // Critical for caching!
.ToListAsync();
}
return _cachedPosts;
}
}
当近邻可以接受时, “(”明白“折价交易”吗?“):”
只有在“:” 时才能接受 Lazy 装载代名词 。
- MSKO你有 短的-生命 每个 HTTP 请求 < )
- MSKO你 从未 缓存实体
- ✅ 你're 同意 NMS K2查询性能
- ✅你're原型,以后将优化
- 您的团队完全理解 影响
但即使如此 , 明确 Include() 几乎总是更好的选择 因为:
- 它使数据加载 明确和明显
- {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF 更方便优化 “(”你可以看到“'”正在加载的是什么?
- 其《 它》 防止意外的N+1查询
- 它正确处理缓存和长-}生活环境
- {\fn方正黑体简体\fs18 建议采用的方法 EF 核心小组
3. DbContext 寿命问题
有关DbContext生产寿命管理的详细信息请参见我的文章 EF 移徙的正确途径.
问题:
// ❌ NEVER do this - singleton DbContext
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<BlogDbContext>(); // WRONG!
}
// ❌ Also wrong - storing context in static field
public static class DataAccess
{
private static BlogDbContext _context = new BlogDbContext();
public static async Task<BlogPost> GetPostAsync(int id)
{
return await _context.BlogPosts.FindAsync(id);
}
}
为何会搞错?
DbContext是 非线索@ - @ 安全- 同时提出请求将造成数据腐败
- 变换跟踪器无限增长
- 连接池耗竭
- 从缓存中存储数据
解决方案#:
// ✅ Use scoped lifetime (default in ASP.NET Core)
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BlogDbContext>(options =>
options.UseNpgsql(connectionString));
}
// ✅ Or use DbContext factory for background services
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<BlogDbContext>(options =>
options.UseNpgsql(connectionString));
}
public class BlogBackgroundService
{
private readonly IDbContextFactory<BlogDbContext> _contextFactory;
public async Task ProcessPostsAsync()
{
// Create a new context for this operation
using var context = await _contextFactory.CreateDbContextAsync();
var posts = await context.BlogPosts.ToListAsync();
// Process posts...
}
}
4. uninted 包括导航属性
问题:
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public List<Comment> Comments { get; set; }
}
// You query one post...
var post = await _context.BlogPosts.FirstAsync();
// Add a new comment
var newComment = new Comment { Content = "Great post!" };
post.Comments.Add(newComment);
await _context.SaveChangesAsync();
// ❌ EF Core saves the comment, BUT...
// If Comments wasn't loaded, you just lost all existing comments!
// The collection is empty, so EF thinks there are no other comments
解决方案#:
// ✅ Always load navigation properties before modifying
var post = await _context.BlogPosts
.Include(p => p.Comments)
.FirstAsync(p => p.Id == postId);
post.Comments.Add(newComment);
await _context.SaveChangesAsync();
// Or add directly to the DbSet
_context.Comments.Add(new Comment
{
BlogPostId = postId,
Content = "Great post!"
});
await _context.SaveChangesAsync();
5. Async vs同步混音
问题:
// ❌ Mixing sync and async - deadlock risk!
public async Task<BlogPost> GetPostAsync(int id)
{
var post = _context.BlogPosts
.Where(p => p.Id == id)
.FirstOrDefault(); // Sync method in async context!
return post;
}
// ❌ Even worse - blocking async code
public BlogPost GetPost(int id)
{
return _context.BlogPosts
.FirstOrDefaultAsync(p => p.Id == id)
.Result; // DEADLOCK RISK!
}
解决方案#:
// ✅ Use async all the way
public async Task<BlogPost> GetPostAsync(int id)
{
return await _context.BlogPosts
.FirstOrDefaultAsync(p => p.Id == id);
}
// ✅ Or use sync all the way (not recommended for ASP.NET Core)
public BlogPost GetPost(int id)
{
return _context.BlogPosts
.FirstOrDefault(p => p.Id == id);
}
EF 核心 10: 什么是'}新变化和突破性变化
与 EF 核心“10” 升级@.}* 完整列表 < , 参见> EF 核心“10”中的断开更改.
所需运行时间
**EF Core {10} 需要.NET <10(EF 核心{MSk0 要求 MSKQ2} ***.+NET @8,}.NET@9,}或這是最重要的變化 net10.0 在升级前@. @%
查询@-}相关断裂变化
1. 参数收藏翻译@( Default changed)
EF Core 核心 @ 10}# 改变方式 Contains() 在% - memory concollation 中, 已翻译为 SQL.} 前称, EF Core OpenJson() (QSQL 服务器@)}或相似的 @.}现在默认为 参数数组 提供更好的查询计划 caching.
影响: 您可能看到不同的 SQL 生成了像 @:这样的查询 。
var ids = new List<int> { 1, 2, 3, 4, 5 };
var posts = await _context.BlogPosts
.Where(p => ids.Contains(p.Id))
.ToListAsync();
EF 核心 @8/9 @ @% (_ OpenJson@):#
SELECT b."Id", b."Title"
FROM "BlogPosts" AS b
WHERE b."Id" IN (SELECT value FROM OPENJSON(@__ids_0))
EF 核心 10 (Parater 阵列MS K2
SELECT b."Id", b."Title"
FROM "BlogPosts" AS b
WHERE b."Id" = ANY(@__ids_0) -- PostgreSQL
-- Or: WHERE b."Id" IN (@__ids_0_0, @__ids_0_1, @__ids_0_2, ...) -- SQL Server
如果工作表现出现倒退@,BARBAR 回到旧的习惯上#:}
// SQL Server
optionsBuilder.UseSqlServer(connectionString,
o => o.UseParameterizedCollectionMode(ParameterTranslationMode.Constant));
// PostgreSQL - generally parameter arrays work well, but you can opt out if needed
2. 执行更新Assync 签名更改
缩略 ExecuteUpdateAsync 签名已更改, 以支持非@ - @ 表达式 lambdas @ MS K1 @ this is more followly but 用于编程构建表达式树的断开代码:
旧式 @(#EF核心@7-9):#
// This still works
await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.IsArchived, true));
新建于 EF 核心 @ 10 @ @ I- @ non-_ 表达式 # ambdas@ MS K3#
// Now you can include custom logic!
await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.ExecuteUpdateAsync(setters =>
{
setters.SetProperty(p => p.IsArchived, true);
setters.SetProperty(p => p.UpdatedAt, DateTime.UtcNow);
// Can now include conditional logic, loops, etc.
});
3.}复式列命名
EF Core {10 改变嵌套复杂类型栏的命名方式,以防止数据腐败:
EF 核心“9:”
NestedComplex_Property
EF 核心“10:”
OuterComplex_NestedComplex_Property
移徙对移民的影响: 如果您有复杂类型的现有表格 @, 您可能需要重命名列或配置清晰的栏名@:
modelBuilder.Entity<Order>()
.ComplexProperty(o => o.ShippingAddress)
.Property(a => a.Street)
.HasColumnName("ShippingAddress_Street"); // Explicit name
SQL 服务器 */Azure SQl 特定更改
JSON 数据类型默认
对于 Azure SQL 数据库或 SQl 服务器 {2025}\ (}相容级别 *170+),EF Core {10} 默认为新本地端的默认值 JSON 数据类型代替 NVARCHAR(MAX).
选择不适用 (如果你需要逆向兼容@):
optionsBuilder.UseAzureSql(connectionString,
o => o.UseCompatibilityLevel(160)); // Use old NVARCHAR behavior
升级升级核对清单
当从 EF 核心 @8/9升级为EF Core:#10:
- 更新目标框架
net10.0 - ✅ 更新全部
Microsoft.EntityFrameworkCore.*组合键到 & @10.#x - “✅ 更新”
Npgsql.EntityFrameworkCore.PostgreSQL到\ 10.x - ✅审查查询
Contains()含有用于执行变化的收藏 - ✅ 测试任何程序构建的代码
ExecuteUpdateAsync表达式 - ✅ 如果使用嵌套的复杂类型, 请检查复式列名称 。
- ✅ 校對 SQL 伺服器 JSON 若使用 Azure SQ L/SQL server {2025}
什么' 新建 @(}亮光 @MS K2
- 改进LINQ翻译更好地生成复杂查询的 SQL
- 执行更新 Async 中的非-表达式羔羊在批量更新中更具灵活性
- 更好地处理参数: 改进查询计划缓存
- 加强JSON支助SQL 服务器“2025”上的土著 JSON类型
- 执行情况改进快速实现和变化跟踪
性能特点
- 查询性能与Dapper相比,简单查询的间接费用
- 内存使用*: 高,因为变化跟踪和代理生成
- 第一个查询“: 慢”“(Qquery 编译和缓存”“ )”
- 随后的询问\ :\ 由于已编译的查询缓存而更快@ info
- 插入@ / @ 更新自动更改跟踪增加了间接费用。
- 散装作业@:_ 使用默认方法的性能不佳@(\考虑 EFCore. 在 EF 核心中执行更新@7+) @%
EF核心核心最佳做法
一般准则一般准则
- 使用无跟踪@ () @% 1 仅查询以减少内存管理费
- 避免 N+1 查询 - 使用
Include()或适当分开查询 - 使用汇总查询 重复查询模式
- 考虑做为简单查询@ () @ 复合物包括避免使用笛卡尔产品
- 使用批量 用于多个插入@ / @ 更新
- DTOs 项目 早期减少数据传输和内存使用
- 使用杠杆执行更新@ / @ execuutDelette 用于散装作业的(EF核心7+)
- 总是对生成的 SQL 进行日志和审查 发展中
与 PostgreSQL 合作时
- 使用全@ - @ text search 功能@( @%我的向导取代“)”
LIKE查询 - 利用 JSONB 列 用于灵活 schemaM-}无数据
- 使用数组类型 供实体内部收集
- 配置连接共享 适当调整工作量
- 您的索引
tsvector列 带有 GIN 指数 - 使用范围类型 时间范围
即将出现在2的片段
在下篇文章“,”中,我们'将探索:
- 顶端微小的-*ORM甜点
- Raw ADO.NETNSK1Npgsql最大性能和控制
- 目标绘图图书馆: 映像师對自动 Mapper
- 混合办法将EF核心和Dapper组合起来 (CQRS模式
- 业绩基准-=YTET -伊甸园字幕组=- 翻译:
- 决定矩阵: 选择适合您情景的正确工具
参考文献和进一步阅读
此博客上的关联文章@: @
在“2,”部分中,我们'将潜入 DapperMS K2 原Npgsql},并探讨如何结合多种方法实现最佳性能和可维护性?