在“.NET”和“ORMS与绘图战略比较”中的数据访问@(_Part @1}{-}实体框架核心=) (中文 (Chinese Simplified))

在“.NET”和“ORMS与绘图战略比较”中的数据访问@(_Part @1}{-}实体框架核心=)

Wednesday, 03 December 2025

//

15 minute read

当建设“.”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核心执行感兴趣的话 , 看看我的其他文章

目录目录

数据获取方法的光谱

“.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!
}

发生什么事了?

  1. 首次查询加载所有员额
  2. 用于 每个员额@, 访问 Category 触发数据库查询
  3. 用于 每个员额@, 访问 Comments 触发另一个查询
  4. 如果您有 @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 装载代名词 。

  1. MSKO你有 短的-生命 每个 HTTP 请求 < )
  2. MSKO你 从未 缓存实体
  3. ✅ 你're 同意 NMS K2查询性能
  4. ✅你're原型,以后将优化
  5. 您的团队完全理解 影响

但即使如此 , 明确 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:

  1. 更新目标框架 net10.0
  2. ✅ 更新全部 Microsoft.EntityFrameworkCore.* 组合键到 & @10.#x
  3. “✅ 更新” Npgsql.EntityFrameworkCore.PostgreSQL 到\ 10.x
  4. ✅审查查询 Contains() 含有用于执行变化的收藏
  5. ✅ 测试任何程序构建的代码 ExecuteUpdateAsync 表达式
  6. ✅ 如果使用嵌套的复杂类型, 请检查复式列名称 。
  7. ✅ 校對 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. 使用无跟踪@ () @% 1 仅查询以减少内存管理费
  2. 避免 N+1 查询 - 使用 Include() 或适当分开查询
  3. 使用汇总查询 重复查询模式
  4. 考虑做为简单查询@ () @ 复合物包括避免使用笛卡尔产品
  5. 使用批量 用于多个插入@ / @ 更新
  6. DTOs 项目 早期减少数据传输和内存使用
  7. 使用杠杆执行更新@ / @ execuutDelette 用于散装作业的(EF核心7+)
  8. 总是对生成的 SQL 进行日志和审查 发展中

与 PostgreSQL 合作时

  1. 使用全@ - @ text search 功能@( @%我的向导取代“)” LIKE 查询
  2. 利用 JSONB 列 用于灵活 schemaM-}无数据
  3. 使用数组类型 供实体内部收集
  4. 配置连接共享 适当调整工作量
  5. 您的索引 tsvector 列 带有 GIN 指数
  6. 使用范围类型 时间范围

即将出现在2的片段

在下篇文章“,”中,我们'将探索:

  • 顶端微小的-*ORM甜点
  • Raw ADO.NETNSK1Npgsql最大性能和控制
  • 目标绘图图书馆: 映像师對自动 Mapper
  • 混合办法将EF核心和Dapper组合起来 (CQRS模式
  • 业绩基准-=YTET -伊甸园字幕组=- 翻译:
  • 决定矩阵: 选择适合您情景的正确工具

参考文献和进一步阅读

此博客上的关联文章@: @


在“2,”部分中,我们'将潜入 DapperMS K2 原Npgsql},并探讨如何结合多种方法实现最佳性能和可维护性?

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.