Datautrymme i .NET: Vergleich av ORM och kartläggningsstrategier | ( | Part |1 | МSK4 | Entity Framework Core (Svenska (Swedish))

Datautrymme i .NET: Vergleich av ORM och kartläggningsstrategier | ( | Part |1 | МSK4 | Entity Framework Core

Wednesday, 03 December 2025

//

20 minute read

När man bygger NET-applikationer, är en av de viktigaste arkitektoniska besluten du kommer att göra, ', hur man hanterar dataangång och objektkartläggning... NET: s ekosystem erbjuder en mängd olika tillvägagångssätt, från fulla --komponenter till bar ORM.

I den här komplexa två delarguiden, , och ', utforskar vi de mest populära data-utgångsmönsterna i NET. Medan vi använder PostgreSQL med Npgsql i våra exempel.

Del 1 (den här artikelnM SK2 fokuserar på Entity Framework Core , ,, generation av SQL, , och vanliga fall. Del 2 kommer att täcka Dapper, råda ADO. NETM SK2 objektkarteringsbibliotek , och hybrida tillvägagångssätt

Om ni är intresserade av praktiska EF Core implementeringar

Tabellen med innehåll

Spektrumet av datatillgångssätt

.NET-datatillgångs landskapet kan visualiseras som ett spektrum

Full Abstraction                                      Full Control
     ↓                                                      ↓
[EF Core] → [EF Core Raw SQL] → [Dapper] → [Npgsql ADO.NET]

När du rör dig från vänster till höger

Vergleich av datatillgångsströmmar

Här är en visuell jämförelse av hur varje metod hanterar en typisk fråga.

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

Performance vs Developer Productivity Trade-off

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

Enhetens ramskärna är Microsofts flagship ORM, ,, som ger en komplett abstraktion över din databas. Npgsql.EntityFrameworkCore.PostgreSQL leverantör.

För praktiska instruktioner om att sätta upp EF Core i ditt projekt, se min artikel på Att lägga till enhetens ram för bloggposter.

Nyckliga egenskaper

  • ändra spårning: Automatiskt följer enhetens förändringar och genererar lämpliga SQL
  • Migrationer: Kod-first schemamanagement och versionskontroll | ( | se EF- Migrationer på rätt väg)
  • LINQ-leverantör: Typ
  • Lazy/Ansträngt laddande: Flexible loading strategies for related entities
  • avancerade PostgreSQL-fenskaper: Full-textsökning (se min artikel), JSON- kolumner , skiffer
  • Interceptörer och händelser: Brejbaritetspunkter för cross-

exempel: Grund CRUD med EF Core

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 Core med rå SQL

EF Core supporterar också rå SQL-fragen när du behöver mer kontroll

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;
}

När man ska använda EF Core

✅ Använd EF Core när

  • Att bygga en ny applikation med utvecklande schemaanforderunger
  • Du behöver en stark typning och kompilera
  • Migrationer och schemaversionering är viktiga (se min migrationsführer)
  • ditt team föredrar att arbeta med objekt än med SQL
  • Du använder komplexa domänmodeller med relationer
  • Utvecklingshastighet är viktigare än råpresterande
  • Du behöver cross-databasens överförbarhet | ( | fast PostgreSQL |- | specifika draginställningar låser dig i

❌ Avoid EF Core när

  • Maximmunska performance är kritiskt (high-throughput APIs
  • Du har komplexa, hand
  • Dina frågor kartlägger inte riktigt objektgraferna
  • Du behöver fin-kontroll över varje SQL-sats
  • Att använda minnet är en viktig begränsning. (förändring av spårning överhuvudtaget
  • Du arbetar med gamla schema som inte kartlägger ' till konventioner

EF Core SQL Generation: förstå vad som utförs

En av de viktigaste aspekterna av att använda EF Core effektivt är att förstå vad SQL den genererar.

Att se Genererad 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();
    }
}

exempel : Enkel fråga

C# LINQ

var recentPosts = await _context.BlogPosts
    .Where(p => p.CategoryId == 5)
    .OrderByDescending(p => p.PublishedDate)
    .Take(10)
    .ToListAsync();

Genererad SQL (EF Core 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

Lägg märke till hur EF Core 8+ genererar ren , effektiv SQL med korrekt parameterisering EF Core 10 fortsätter denna trend med ännu fler framsteg

exempel: Join with Include (Before EF Core 5)

C# Kod

var posts = await _context.BlogPosts
    .Include(p => p.Category)
    .Include(p => p.Comments)
    .ToListAsync();

Gamla SQL (EF Core 3.1 - Kartesisk explosion

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"

Detta skapar en Cartesiansk produkt - om ett meddelande har 10 kommentarer M SK2 så upprepas rown 10 gånger

exempel: Split Queries (EF Core 5+)

C# Kod med Split Query

var posts = await _context.BlogPosts
    .Include(p => p.Category)
    .Include(p => p.Comments)
    .AsSplitQuery()  // ← This is the key!
    .ToListAsync();

Genererad SQL (Multiple Queries):

-- 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"

Detta eliminerar den karttensiska produkten och är ofta mycket snabbare för samlingar!

exempel: Filterat Inbegripa (EF Core 5+)

C# Kod

var posts = await _context.BlogPosts
    .Include(p => p.Comments.Where(c => c.IsApproved))
    .ToListAsync();

Genererat 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"

exempel: JSON Column Queries (EF Core 7+)

C# Kod

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();

Genererat SQL:

SELECT b."Id", b."Title", b."Metadata"
FROM "BlogPosts" AS b
WHERE b."Metadata" ->> 'IsFeatured' = 'true'

EF Core 7+ kan översätta JSONs egenskap till PostgreSQL JSON-operatörer

exempel: Bulkupdate (EF Core 7+ ExecuteUpdateM SK3

Old Way (Ineffektivt):

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!

Nya sätt (EF Core 7+):

await _context.BlogPosts
    .Where(p => p.CategoryId == 5)
    .ExecuteUpdateAsync(setters => setters
        .SetProperty(p => p.IsArchived, true));

Genererad SQL (En enda fråga!):

UPDATE "BlogPosts" AS b
SET "IsArchived" = TRUE
WHERE b."CategoryId" = 5

Det här är en massiva förbättring - ett SQL-uttalande istället för N!

exempel: Storskaffa (EF Core 7+)

Old Way:

var oldPosts = await _context.BlogPosts
    .Where(p => p.PublishedDate < DateTime.UtcNow.AddYears(-5))
    .ToListAsync();

_context.BlogPosts.RemoveRange(oldPosts);
await _context.SaveChangesAsync();  // N DELETE statements

New Way:

await _context.BlogPosts
    .Where(p => p.PublishedDate < DateTime.UtcNow.AddYears(-5))
    .ExecuteDeleteAsync();

Genererat SQL:

DELETE FROM "BlogPosts" AS b
WHERE b."PublishedDate" < @__p_0

exempel: Kompleks aggregation

C# Kod

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();

Genererad SQL (EF Core 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 Full-Textsökning

För att dyka djupare in i det hela, se min artikel på implementerar full-textsökning med EF Core.

C# Kod

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();

Genererat 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

⚠️ Kritiska EF-grindsvaror och fall

1. ändra spårning av minnesmängder

Problemet:

// ❌ 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!
    }
}

Varför är det problem?

  • De spårade varelserna förblir i minnet under livstid DbContext
  • Den ändrade spåraren upprätthåller referenser, för att förhindra skräpsamlagning
  • lång tidiga-livade sammanhang | ( | e | . | g | МSK3 | enton | ) | = | minnesläcka
  • I ASP är kontexten standardmässigt skalad (good!)
  • Men om du kastar spårade enheter, så är du i brång

Lösningen:

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. Proxy generering och lat laddande risker

🚨 CRITICAL WARNING : Använd inte EF CORE PROXIES IF YOU CACHE ENTITIES

Låga laddande försedlar + kassering = GUARANTERED MEMORY LEAK

Om du kastar DbContext-instantioner eller kastar varelser som lades med hjälp av aktiverade proxyer WILL lek minnet. Proxymekanismen behåller referenser till DbKontexten , för att förhindra skräpsamlagning . Det är ett av de vanligaste och farligaste misstag i EF Core-applikationerna

tumregler: Ännu alltid inkludera samlingar explicit med .Include(). Använd endast proxyer om du förstår fullkomligt vad som är på spel och aldrig någonsin

Problemet 1: N+1 Rädslorna i fråga

// ❌ 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!
}

Vad händer?

  1. Den första frågan laddar alla meddelanden
  2. För varenda post, tillgång Category löser en database-fråga
  3. För varenda post, tillgång Comments triggar en annan fråga
  4. Om du har poster 100 201 frågor!

Problemet 2: Proxy + Caching = Memory Leak

// ❌ 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;
    }
}

Varför är detta katastrofellt?

  • Proxyenheter håller en referens till deras DbContext
  • Den DbContext underhåller en referens till alla spårade enheter
  • Din kas hindrar nu hela objektgrafen från att bli skräp
  • Varje gång du går in på en navigation property, så kan det utlösa sökningar med hjälp av gammalt, kaserad kontext
  • minnet växer obegränsat när du laddar mer data
  • Du' kommer slutligen att bli slutgiltig med minne eller avloppskopplingspooler

Lösningen : Ska vara tydlig

// ✅ 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;
    }
}

När försedelser kan vara accepterabla (Förstå vad man kan välja

Låga laddar kan bara accepteras när:

  1. ✅ Du har kort-liv avgränsade kontexter (e.g ., per HTTP-fråga
  2. ✅ Du aldrig kas-enheter
  3. ✅ Du 'var okej med N+1 sökresultat
  4. ✅ Du ' är prototyp och optimerar senare
  5. ✅ ditt team förstår fullkomligt konsekvenserna

Men även då Include() är nästan alltid den bättre valet eftersom

  • Det gör datan laddad explicitt och uppenbart
  • It's lättare att optimera ( du kan se vad
  • Det förhindrar oavsiktliga N+1 frågor
  • Det fungerar korrekt med kassering och långa-liva kontexter
  • Det är tillämpningssätt av EF Core-teamet

3. DbKontext livstidsproblem

För mer information om att hantera DbContext livstid i produktion, se min artikel på EF- Migrationer på rätt väg.

Problemet:

// ❌ 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);
    }
}

Varför är det fel?

  • DbContext är inte thread-säkra
  • Samma requester kommer att orsaka datakorruption
  • Veranderaren växer oändligt
  • Anslutningspool utplåning
  • Stanna data från kas

Lösningen:

// ✅ 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. Oavsiktligt Inbegriper Navigation Properties

Problemet:

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

Lösningen:

// ✅ 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 mot Sync Mixing

Problemet:

// ❌ 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!
}

Lösningen:

// ✅ 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: s kärna 10: Vad är det som är nytt och brytande

Med EF Core 10 som släppts tillsammans med .NET | 10, | finns det flera viktiga ändringar som man måste vara medveten om när man uppgraderar Utbrott av förändringar i EF Core 10.

Runtime krav

EF Core 10 kräver .NET 10. Den kommer inte att fungera på | . | NET | 8, | МSK3 | Net |9, | eller | net10.0 före uppgraderingen.

Fråga-Störande förändringar

1. Parameteriserad Kollektionsöversättning (Default ändrad)

EF Core 10 ändrar hur Contains() med in-memory collections är översatt till SQL. Förresten , EF Core användes OpenJson() (SQL Server ) eller liknande. Nu är det standardmässigt så att parameterraster som ger bättre queryplan caching.

Intryck: Du kan se olika SQL genererade för queryer som

var ids = new List<int> { 1, 2, 3, 4, 5 };
var posts = await _context.BlogPosts
    .Where(p => ids.Contains(p.Id))
    .ToListAsync();

EF Core 8/9 (OpenJsonM SK2

SELECT b."Id", b."Title"
FROM "BlogPosts" AS b
WHERE b."Id" IN (SELECT value FROM OPENJSON(@__ids_0))

EF Core 10 (Parameter Arrays):

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

Om du upplever utvecklingsregressioner, återvänder till det gamla beteendet

// SQL Server
optionsBuilder.UseSqlServer(connectionString,
    o => o.UseParameterizedCollectionMode(ParameterTranslationMode.Constant));

// PostgreSQL - generally parameter arrays work well, but you can opt out if needed

2. ExecuteUpdateAsync Signature Change

Den ExecuteUpdateAsync handtekening har ändrats till att stödja icke-expression lambdas . Detta är mer flexibelt men bryter koden som bygger uttrycksträd programmerat:

Gamla sätt (EF Core 7-9):

// This still works
await _context.BlogPosts
    .Where(p => p.CategoryId == 5)
    .ExecuteUpdateAsync(setters => setters
        .SetProperty(p => p.IsArchived, true));

Ny i EF: s kärna 10 - ickeM SK2expression lambdas:

// 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. Komplextyp Spalva Namning

EF Core 10 ändrar hur nesterade komplexa columner är döpta för att förhindra datakorruption

EF Core 9:

NestedComplex_Property

EF Core 10:

OuterComplex_NestedComplex_Property

Migrationseffekter: Om du har existerande tabeller med komplexa typer

modelBuilder.Entity<Order>()
    .ComplexProperty(o => o.ShippingAddress)
    .Property(a => a.Street)
    .HasColumnName("ShippingAddress_Street"); // Explicit name

SQL Server / Azure SQL Spesifika förändringar

JSON Datatyp Standard

För Azure SQL Database eller SQL Server 2025 | ( | Kompatibilitetsnivån ♫ 170+), | EF Core ♫ JSON datatyp istället för NVARCHAR(MAX).

Att välja (om du behöver bakåtkompatibilitet):

optionsBuilder.UseAzureSql(connectionString,
    o => o.UseCompatibilityLevel(160)); // Use old NVARCHAR behavior

Uppgradering av kontrolllistan

När man uppgraderar från EF Core 8/9 till EF Core

  1. ✅ uppdatera målstrukturen till net10.0
  2. ✅ uppdatera alla Microsoft.EntityFrameworkCore.* paket till 10.x
  3. ✅ Aktualisering Npgsql.EntityFrameworkCore.PostgreSQL till 10.x
  4. ✅ Omvärdera sökningar genom Contains() med samlingar för prestansförändringar
  5. ✅ Testa vilken kod som programmerat bygger ExecuteUpdateAsync uttryck
  6. ✅ Kolla komplexa typers kolumnerna om man använder nesterade komplexa gener
  7. ✅ Försök användandet av JSON kolumner på SQL Server om du använder Azure SQL/SQL Server 2025

Vad är det som är nytt?

  • förbättrad LINQ-översättning: Bättre SQL-generering för komplexa frågor
  • icke-expression lambdas i ExecuteUpdateAsync: Mer flexibilitet när det gäller storskaffade versioner
  • bättre handling av parametrar: förbättrad query-planskapande
  • Förstärkt support för JSON: Infödda JSON-typ på SQL Server 2025
  • Performance förbättringar: snabbare materialisering och ändrad spårning

Förmågor och egenskaper

  • Försöksresultat: 20-50% överhead jämfört med Dapper för enkla queryer
  • minnesanvändning: Högare på grund av ändrad spårning och proxy generering
  • Den första fråga: långsamt (sökningskompilation och kakning
  • Efterföljande sökningar: snabbare på grund av kompilerad query-キャッシュ
  • Inserar/Updates: Automatisk förändringsspårning lägger till överhead
  • Operationer i stor skala: dåliga resultat med standardmetoder ( överväg EFCore.BulkExtensioner eller ExecuteUpdate/ExecuteDelete i EF Core 7+)

De bästa metoderna för EF Core

Andra generallinjer

  1. Använd AsNoTracking() för read- endast frågor för att minska minnesöverskott
  2. undvika N+1 frågor - användande Include() eller fördela sökningar ordentligt
  3. Använd sammanlagda frågor för upprepade sökmönster
  4. Tänk på AsSplitQuery() för komplex inkluderar att undvika kartesiska produkter
  5. Använd slagning för flera wstawningar/aktualiseringar
  6. Projekt till DTOs tidigt för att minska dataöverföring och minnesanvändning
  7. Leverage ExecuteUpdate/ExecuteDelete (EF Core 7+) för storskaftoperationer
  8. Ännulogga och överväg genererade SQL under utveckling

När man arbetar med PostgreSQL

  1. Använd fulla-textsökning egenskaper (min guide) istället för LIKE frågor
  2. Leveragera JSONB- kolumner för flexibelt schema-lös data
  3. Använd radsorter för insamlingar inom enheten
  4. Konfigurera kopplingspoolning ordentligt för din arbetslast
  5. Indeksera din tsvector kolumner med GIN-index
  6. Använda omvangstyper för datums-/-timmar

Kom upp i del 2

I nästa artikel, ,, utforskar vi

  • Damper: Mikron
  • Rout ADO.NET/Npgsql: Maximalna effekt och kontroll
  • Objektkarteringsbibliotek: Mapster mot AutoMapper
  • Hybrida förhållningssätt: Kombination av EF Core och Dapper
  • Leistungsindikatorer: Verkliga comparisoner
  • Bestämmande matrix: Välja det rätta verktyget för ditt scenario

Referenser och ytterligare svar

Related Articles on This Blog:


I delen 2, dyker vi in i Dapper , raw Npgsql , och utforskar hur man kombinerar flera tillvägagångssätt för optimal prestation och underhåll.

Finding related posts...
logo

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