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

<!-- category -- .NET, EF Core, PostgreSQL, Performance, Database -->
<datetime class="hidden">2025-12-03T14:00</datetime>

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

## Related Articles

Om ni är intresserade av praktiska EF Core implementeringar

- [Att lägga till enhetens ram för bloggposter (Teil 1)](/blog/addingentityframeworkforblogpostspt1) - Att sätta upp EF Core från början
- [EF- Migrationer på rätt väg](/blog/efmigrationstherightway) - Hur man hanterar migrationer ordentligt i produktion
- [Full Textsökning (Pt 1)](/blog/textsearchingpt1) - Implementering of PostgreSQL full -text search with EF Core
- [Modern CQRS och Event Sourcing](/blog/moderncqrsandeventsourcing) - avancerade arkitektoniska mönster med EF Core

## 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.

```mermaid
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

```mermaid
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 Core: Den fulla-Featured ORM

[Enhetens ramskärna](https://learn.microsoft.com/en-us/ef/core/) är Microsofts flagship ORM, ,, som ger en komplett abstraktion över din databas. [Npgsql.EntityFrameworkCore.PostgreSQL](https://www.npgsql.org/efcore/) 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](/blog/addingentityframeworkforblogpostspt1).

### 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](/blog/efmigrationstherightway))
- **LINQ-leverantör**: Typ
- **Lazy/Ansträngt laddande**: Flexible loading strategies for related entities
- **avancerade PostgreSQL-fenskaper**: Full-textsökning  ([se min artikel](/blog/textsearchingpt1)), JSON- kolumner , skiffer
- **Interceptörer och händelser**: Brejbaritetspunkter för cross-

### exempel: Grund CRUD med EF Core

```csharp
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

```csharp
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](/blog/efmigrationstherightway))
- 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

```csharp
// 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**

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

**Genererad SQL ([EF Core 8+](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew)):**

```sql
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](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/whatsnew) fortsätter denna trend med ännu fler framsteg

### exempel: Join with Include (Before EF Core 5)

**C# Kod**

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

**Gamla SQL (EF Core 3.1 - Kartesisk explosion**

```sql
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**

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

**Genererad SQL (Multiple Queries):**

```sql
-- 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**

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

**Genererat SQL:**

```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**

```csharp
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:**

```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):**

```csharp
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+):**

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

**Genererad SQL (En enda fråga!):**

```sql
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:**

```csharp
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:**

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

**Genererat SQL:**

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

### exempel: Kompleks aggregation

**C# Kod**

```csharp
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):**

```sql
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](/blog/textsearchingpt1).

**C# Kod**

```csharp
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:**

```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:**

```csharp
// ❌ 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:**

```csharp
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**

```csharp
// ❌ 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**

```csharp
// ❌ 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**

```csharp
// ✅ 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](/blog/efmigrationstherightway).

**Problemet:**

```csharp
// ❌ 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:**

```csharp
// ✅ 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:**

```csharp
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:**

```csharp
// ✅ 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:**

```csharp
// ❌ 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:**

```csharp
// ✅ 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](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/whatsnew) 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](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/breaking-changes).

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

```csharp
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**

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

**EF Core 10 (Parameter Arrays):**

```sql
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

```csharp
// 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):**

```csharp
// 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:**

```csharp
// 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

```csharp
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):

```csharp
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](https://github.com/borisdj/EFCore.BulkExtensions) 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](/blog/textsearchingpt1)) 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

- [Enhetens ramverkets kärndokumentation](https://learn.microsoft.com/en-us/ef/core/)
- [Npgsql-enhetens ram som är central leverantör](https://www.npgsql.org/efcore/)
- [EF: s grundresultat](https://learn.microsoft.com/en-us/ef/core/performance/)
- [Vad är nytt med EF Core 10](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/whatsnew)
- [Utbrott av förändringar i EF: s kärna 10](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/breaking-changes)
- [Vad är nytt med EF Core 8](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew)
- [PostgreSQL Dokumentation](https://www.postgresql.org/docs/)

**Related Articles on This Blog:**

- [Att lägga till enhetens ram för bloggposter](/blog/addingentityframeworkforblogpostspt1)
- [EF- Migrationer på rätt väg](/blog/efmigrationstherightway)
- [Full Text-sökning med EF Core](/blog/textsearchingpt1)
- [Modern CQRS och Event Sourcing](/blog/moderncqrsandeventsourcing)

---


*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.*