# Dataåtkomst i .NET: Jämföra ORM och kartläggningsstrategier (Del 1 - Enhetsram)

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

När du bygger .NET-applikationer, en av de viktigaste arkitektoniska beslut du kommer att göra är hur man hanterar dataåtkomst och objektkartläggning. .NET ekosystem erbjuder en rik variation av metoder, från fullfjädrade ORMs till barmetall SQL utförande. Varje metod kommer med sina egna kompromisser när det gäller prestanda, utvecklare produktivitet, typ säkerhet, och underhållbarhet.

I denna omfattande två delar guide, kommer vi att utforska de mest populära data åtkomst mönster i .NET. Medan vi använder PostgreSQL med Npgsql i våra exempel (eftersom det är vad som driver denna blogg), begrepp, mönster och kompromisser gäller lika för SQL Server, MySQL, SQLite, och andra relationsdatabaser. Principerna förblir samma - bara SQL dialekt och vissa specifika funktioner skiljer sig.

**Del 1 (denna artikel)** fokuserar på Entity Framework Core, SQL-generering och vanliga fallgropar.
**Häfte 2** kommer att omfatta Dapper, rå ADO.NET, bibliotek för objektkartläggning och hybridmetoder.

## Relaterade artiklar

Om du är intresserad av praktiska EF Core implementeringar, kolla in mina andra artiklar:

- [Lägga till Entity Framework för blogginlägg (Del 1)](/blog/addingentityframeworkforblogpostspt1) - Sätta upp EF Core från grunden
- [EF Migrationer på rätt sätt](/blog/efmigrationstherightway) - Hur man hanterar migreringar på rätt sätt i produktionen
- [Fullständig textsökning (Pt 1)](/blog/textsearchingpt1) - Implementera PostgreSQL fulltextsökning med EF Core
- [Modern CQRS och Händelseförslitning](/blog/moderncqrsandeventsourcing) - Avancerade arkitektoniska mönster med EF Core

## Innehållsförteckning

## Spektrumet för dataåtkomstmetoder

.NET data åtkomst landskap kan visualiseras som ett spektrum:

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

När du flyttar från vänster till höger, du får prestanda och kontroll men förlorar bekvämlighet och automatiska funktioner. Låt oss undersöka varje strategi i detalj.

### Jämförelse av dataåtkomstflöde

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

### Prestanda mot utvecklarproduktivitet 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
```

## Entity Framework Core: Den fullfjädrade ORM

[Entitetsramar](https://learn.microsoft.com/en-us/ef/core/) är Microsofts flaggskepp ORM, ger en komplett abstraktion över din databas. Det stöder PostgreSQL genom [Npgsql.EntityFrameworkCore.PostgreSQL](https://www.npgsql.org/efcore/) leverantör.

För praktisk vägledning om inrättandet av EF Core i ditt projekt, se min artikel om [Lägga till entitetsramverk för blogginlägg](/blog/addingentityframeworkforblogpostspt1).

### Nyckelfunktioner

- **Ändra spårning**: Automatiskt spårar enheten förändringar och genererar lämplig SQL
- **Flyttningar**: Hantering av kod-första schema och versionskontroll (se [EF Migrationer på rätt sätt](/blog/efmigrationstherightway))
- **LINQ-leverantör**: Typsäkra frågor med C# språkkonstruktioner
- **Lazy/Eager Laddar**: Flexibla lastningsstrategier för närstående enheter
- **Avancerade PostgreSQL-funktioner**: Sök i fulltext ([se min artikel](/blog/textsearchingpt1)), JSON kolumner, matriser, intervalltyper
- **Interceptorer och händelser**: Omfattande punkter för övergripande frågor

### Exempel: Grundläggande 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 stöder också rå SQL-frågor 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 du ska använda EF Core

**Använd EF Core när:**

- Bygga en ny applikation med föränderliga schemakrav
- Du behöver starkt skriva och kompilera-tid frågevalidering
- Migration och schemaversioner är viktiga (se [min migrationsguide](/blog/efmigrationstherightway))
- Ditt team föredrar att arbeta med objekt över SQL
- Du använder komplexa domänmodeller med relationer
- Utvecklingshastighet är mer kritisk än rå prestanda
- Du behöver korsdatabasportabilitet (även om PostgreSQL-specifika funktioner låser in dig)

**❌ Undvik EF Core När:**

- Maximal prestanda är kritisk (högt genomflöde API:er, batch bearbetning)
- Du har komplexa, handjusterade SQL-frågor
- Dina frågor mappar inte bra till objektgrafer
- Du behöver finkornig kontroll över varje SQL uttalande
- Minne användning är en kritisk begränsning (förändring spårning overhead)
- Du jobbar med äldre scheman som inte kartlägger till konventioner.

## EF Core SQL Generation: Förstå vad som blir avrättat

En av de viktigaste aspekterna av att använda EF Core effektivt är att förstå vad SQL det genererar. EF Core har avsevärt förbättrat SQL generation genom åren, men det är viktigt att verifiera de frågor som skickas till PostgreSQL.

### Visa genererat 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-kärna 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-kärna 10](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/whatsnew) fortsätter denna trend med ännu fler förbättringar.

### Exempel: Gå med i Inkludera (Innan EF Core 5)

**Ursprunglig hänvisning till den nationella lagstiftningen:**

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

**Gamla SQL (EF Core 3.1 - Cartesian 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 **Kartesisk produkt** - om ett inlägg har 10 kommentarer, upprepas den raden 10 gånger!

### Exempel: Dela frågor (EF Core 5+)

**C# Kod med delad fråga:**

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

**Genererad SQL (flera frågor):**

```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 Cartesian produkten och är ofta **mycket snabbare** för samlingar!

### Exempel: Filtrerat Inkludera (EF Core 5+)

**Ursprunglig hänvisning till den nationella lagstiftningen:**

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

**Genererad 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 Kolumnfrågor (EF Core 7+)

**Ursprunglig hänvisning till den nationella lagstiftningen:**

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

**Genererad SQL:**

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

EF Core 7+ kan översätta JSON egendom tillgång till PostgreSQL JSON operatörer!

### Exempel: Bulkuppdatering (EF Core 7+ ExecuteUpdate)

**Gamla sättet (ineffektiv):**

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

**Nytt sätt (EF-grund 7+):**

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

**Genererad SQL (Single Query!):**

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

Detta är en **massiv** förbättring - ett SQL uttalande istället för N!

### Exempel: Bulk Ta bort (EF Core 7+)

**Gamla sättet:**

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

**Nytt sätt:**

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

**Genererad SQL:**

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

### Exempel: Komplex aggregation

**Ursprunglig hänvisning till den nationella lagstiftningen:**

```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- fulltextsökning

För ett djupare dyk i fulltextsökning, se min artikel om [genomföra fulltextsökning med EF Core](/blog/textsearchingpt1).

**Ursprunglig hänvisning till den nationella lagstiftningen:**

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

**Genererad 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 kärnvarningar och fallgropar"

### 1. Ändra spårning minne läck

**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 det är ett problem:**

- Spårade enheter förblir i minnet under livstiden för `DbContext`
- Byte tracker upprätthåller referenser, förhindrar sophämtning
- Långlivade sammanhang (t.ex. singletons) = minnesläcka
- I ASP.NET Core, är sammanhang som standard (bra!)
- Men om du cache spårade enheter, du är i trubbel

**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 Generation och Lazy Laddar faror

> **KRITISK VARNING: ANVÄND INTE EF Core PROXIES OM DU KRAFTAR ENHETER**
> 
> Lata laddningsproxies + caching = **GARANTERADE MINNESLAG**
> 
> Om du cache DbContext instanser eller cache enheter laddade med proxys aktiverat, du **VILKA** Läckra minne. Proxymekanismen upprätthåller referenser till DbContext, förhindrar sophämtning. Detta är en av de vanligaste och farligaste misstagen i EF Core applikationer.
> 
> **Tumregel**: Alltid inkludera samlingar explicit med `.Include()`. Använd bara fullmakter om du till fullo förstår kompromisserna och aldrig, någonsin cache proxy enheter.

**Problem 1: N+1-frågans mardröm**

```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. Första frågan laddar alla inlägg
2. För **varje tjänst**, tillgång `Category` utlöser en databasfråga
3. För **varje tjänst**, tillgång `Comments` utlöser en annan fråga
4. Om du har 100 inlägg, du bara avrättade **201 frågor**!

**Problem 2: Proxy + Caching = Minnesläckage**

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

**Därför är detta katastrofalt:**

- Proxy-enheter behåller en hänvisning till sina `DbContext`
- och `DbContext` behåller en hänvisning till alla spårade enheter
- Din cache nu förhindrar hela objektet grafen från att vara skräp samlas in
- Varje gång du får tillgång till en navigationsfastighet, kan det utlösa frågor med hjälp av **gamla, cachade sammanhang**
- Minnet växer fritt när du laddar mer data
- Du kommer så småningom få slut på minne eller avgasanslutning pooler

**Lösningen: Var explicit**

```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 Proxies kan vara godtagbar (förstå kompromisserna):**

Lazy laddningsproxies kan vara acceptabelt ENDAST när:

1. Du har rätt. **Kortlivad** avgränsade sammanhang (t.ex., per HTTP-begäran)
2. på dig själv **aldrig** cacheenheter
3. Du är okej med N+1 frågeprestanda
4. Du är prototyper och kommer att optimera senare
5. Ditt team förstår helt och fullt konsekvenserna

Men även då, explicit `Include()` är nästan alltid det bättre valet eftersom:

- Det gör att data laddas **explicit och uppenbart**
- Det är **lättare att optimera** (du kan se vad som laddas)
- Det är det. **förhindrar oavsiktliga N+1-frågor**
- Det fungerar korrekt med caching och långvariga sammanhang
- Det är **rekommenderat tillvägagångssätt** av EF Core-gruppen

### 3. DbContext Livstidsfrågor

För mer information om hantering av DbContext livstid i produktionen, se min artikel om [EF Migrationer på rätt sätt](/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 det är fel:**

- `DbContext` är **ej trådsäker**
- Samtidiga förfrågningar kommer att orsaka datakorruption
- Ändra spårare växer hur länge som helst
- Uttömd anslutningspool
- Stapla data från cache

**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. Oavsedda Inkluderar i navigeringsegenskaper

**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 vs synkronisering

**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 Core 10: Vad är nytt och bryta förändringar

med [EF-kärna 10](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/whatsnew) släpptes tillsammans med .NET 10, det finns flera viktiga förändringar att vara medveten om när uppgradering. För den fullständiga listan, se [Bryta förändringar i EF Core 10](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/breaking-changes).

### Krav på körtid

**EF Core 10 kräver .NET 10**. Det kommer inte att köras på .NET 8, .NET 9, eller .NET Framework. Detta är den mest betydande förändringen - säkerställa dina projektmål `net10.0` Före uppgradering.

### Körelaterade förändringar i brytande ställning

#### 1. Parameterized Collection Översättning (Default Ändrad)

EF Core 10 ändrar hur `Contains()` med in-minne samlingar är översatt till SQL. Tidigare, EF Core används `OpenJson()` (SQL Server) eller liknande. Nu är den förvald till **Parametermatriser** vilket ger bättre frågeplan caching.

**Konsekvenser**: Du kan se olika SQL genereras för frågor 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-kärna 8/9 (OpenJson):**

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

**EF-kärna 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 prestanda regressioner**, återgå 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. KörUpdateAsync Signaturändring

och `ExecuteUpdateAsync` signatur har ändrats för att stödja icke-uttryck lambdas. Detta är mer flexibelt men **bryter kod som byggde uttryck träd programmatiskt**:

**Gamla vägen (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 Core 10 - Non-expression 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. Komplex typ Kolumnnamngivning

EF Core 10 ändrar hur inbäddade komplexa kolumner namnges för att förhindra datakorruption:

**EF-kärna 9:**

```
NestedComplex_Property
```

**EF-kärna 10:**

```
OuterComplex_NestedComplex_Property
```

**Inverkan på migrationen**: Om du har befintliga tabeller med komplexa typer, kan du behöva byta namn på kolumner eller ställa in explicita kolumnnamn:

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

### SQL- server / Azure SQL- specifika ändringar

#### Förval av JSON- datatyp

För Azure SQL Databas eller SQL Server 2025 (kompatibilitetsnivå 170+), EF Core 10 standardvärden till den nya inhemska `JSON` datatyp i stället för `NVARCHAR(MAX)`.

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

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

### Uppgradering av checklista

Vid uppgradering från EF Core 8/9 till EF Core 10:

1. Uppdatering av målramen för `net10.0`
2. på Uppdatera alla `Microsoft.EntityFrameworkCore.*` paket till 10.x
3. på uppdatering `Npgsql.EntityFrameworkCore.PostgreSQL` till 10.x
4. på Granska frågor med hjälp av `Contains()` med samlingar för prestandaförändringar
5. Pröva alla koder som programmatiskt bygger `ExecuteUpdateAsync` uttryck
6. till Kontrollera komplexa typ kolumnnamn om du använder inbäddade komplexa typer
7. Om Azure SQL/SQL Server 2025 används ska du granska SQL Server JSON-kolumnen

### Vad är nytt (Highlights)

- **Förbättrad LINQ-översättning**: Bättre SQL generation för komplexa frågor
- **Non-expression lambdas i ExecuteUpdateAsync**: Mer flexibilitet i bulk uppdateringar
- **Bättre parameterhantering**: Förbättrad frågeplan caching
- **Förbättrat stöd för JSON**: Native JSON type på SQL Server 2025
- **Förbättringar av prestandan**: Snabbare materialisering och förändringsspårning

## Prestandaegenskaper

- **Fråga resultat**: 20-50% overhead jämfört med Dapper för enkla frågor
- **Minnesanvändning**: Högre på grund av förändring spårning och proxygenerering
- **Första frågestunden**: Långsam (query sammanställning och caching)
- **Efterföljande frågor**: Snabbare på grund av kompilerad frågecache
- **Infogar/uppdateringar**: Automatisk ändringsspårning lägger till overhead
- **Bulktransaktioner**: Dålig prestanda med standardmetoder (överväg [EFCore.BulkExtensioner](https://github.com/borisdj/EFCore.BulkExtensions) eller executeUpdate/ExecuteDelete i EF Core 7+)

## Bästa praxis för EF Core

### Allmänna riktlinjer

1. **Använd asNoTracking ()** för skrivskyddade frågor för att minska minnesomkostnaderna
2. **Undvik N+1-frågor** - användning `Include()` eller dela frågor på lämpligt sätt
3. **Använd kompilerade frågor** för upprepade frågemönster
4. **Överväg AssplitQuery ()** för komplex omfattar att undvika kartesiska produkter
5. **Använd batching** för flera inlägg/uppdateringar
6. **Projekt till DTO** tidigt för att minska dataöverföring och minnesanvändning
7. **VävnadsutförandeUppdatering/ExecuteDelete** (EF Core 7+) för bulktransaktioner
8. **Logga alltid och granska genererad SQL** i utveckling

### När du arbetar med PostgreSQL

1. **Använd fulltextsökning** Kännetecken ([min guide](/blog/textsearchingpt1)) i stället för `LIKE` frågor
2. **Bruttosoliditetsgradens exponeringsvärde av tillgångar som utgör exponeringar enligt schablonmetoden** för flexibla schemalösa data
3. **Använd arraytyper** för samlingar inom enheter
4. **Anpassa anslutningspoolningName** korrekt för din arbetsbörda
5. **Indexera din `tsvector` kolumner** med GIN-index
6. **Använd intervalltyper** för datum/tidsintervall

## Kommer upp i del 2

I nästa artikel ska vi undersöka:

- **Gräslök**: Mikro-ORM söt plats
- **Rå ADO.NET/Npgsql**: Maximal prestanda och kontroll
- **Objektkartläggning av bibliotek**: Mapster vs AutoMapper
- **Hybridmetoder**: Kombinera EF Core och Dapper (CQRS-mönster)
- **Riktmärken för prestanda**: Real-world jämförelser
- **Beslutsmatris**: Välja rätt verktyg för ditt scenario

## Referenser och ytterligare läsning

- [Grundläggande dokumentation om en Enhets rättsliga ram](https://learn.microsoft.com/en-us/ef/core/)
- [Huvudleverantör av Npgsql entity rame](https://www.npgsql.org/efcore/)
- [EF Core Prestanda](https://learn.microsoft.com/en-us/ef/core/performance/)
- [Vad är nytt i EF Core 10](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/whatsnew)
- [Bryta förändringar i EF Core 10](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/breaking-changes)
- [Vad är nytt i 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/)

**Relaterade artiklar på denna blogg:**

- [Lägga till entitetsramverk för blogginlägg](/blog/addingentityframeworkforblogpostspt1)
- [EF Migrationer på rätt sätt](/blog/efmigrationstherightway)
- [Fullständig textsökning med EF Core](/blog/textsearchingpt1)
- [Modern CQRS och Händelseförslitning](/blog/moderncqrsandeventsourcing)

---


*I del 2 dyker vi in i Dapper, rå Npgsql, och utforskar hur man kombinerar flera tillvägagångssätt för optimal prestanda och underhållbarhet.*