# Datenzugriff in der Analyse von ORMs und Mapping-Strategien

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

Beim Bau von Anwendungen für .-NET – eine der wichtigsten architektonischen Entscheidungen, die Sie treffen, ist der Umgang mit Datenzugriff und Objektmapping..- Das Ecosystem von .-NET bietet eine Vielzahl an Ansätzen, von vollwertigen ORMs bis hin zu bloßer Ausführung von --Metall SQL Jeder Ansatz kommt mit seinem eigenen Trade – --offs in Bezug auf Leistungsfähigkeit – ,-Entwicklerproduktivität – ,-Typ Sicherheit –,- und Aufrechterhaltungsfähigkeit –.-

In diesem umfassenden Zwei---Teile-Leitfaden erkunden wir uns mit den populärsten Datenzugriffsmustern in ..NET... Während wir PostgreSQL mit Npgsql in unseren Beispielen verwenden, gelten die Konzepte , und trade.MSC10offs gleichermaßen für SQL Server, ,. MySQ,. SQLite,. und andere relationale Datenbanken... Die Prinzipien bleiben derselbe ?-? nur der SQL-Dialekt und einige spezifische Merkmale unterscheiden sich von ..

**Teil dieses Artikels** konzentriert sich auf Entity Framework Core, ,-SQL-Generation und gemeinsame Fallstricke.
**Teil** wird Dapper, ,, rohe ADO-.,NET, ,, Objektmappingbibliotheken und hybride Ansätze abdecken

## Verwandte Artikel

Wenn Sie sich für praktische EF-Core-Implementierungen interessieren, schauen Sie sich meine anderen Artikel an

- [Hinzufügen von Entity Framework für Blog-Posts](/blog/addingentityframeworkforblogpostspt1) Einrichten von EF-Kern von Grund auf
- [EF-Migrationen auf dem richtigen Weg](/blog/efmigrationstherightway) Wie man Migrationen in der Produktion richtig behandelt
- [Volltextsuche](/blog/textsearchingpt1) Umsetzung der PostgreSQL-Volltextsuche mit EF Core
- [Moderne CQRS und Event Sourcing](/blog/moderncqrsandeventsourcing) Erweiterte architektonische Muster mit EF Core

## Inhaltsverzeichnis

## Das Spektrum der Datenzugriffsansätze

Die Datenzugriffslandschaft von -.®NET kann als Spektrum visualisiert werden.

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

Wenn du dich von links nach rechts bewegst, erhältst du Leistung und Kontrolle, aber verlierst Bequemlichkeit und automatische Funktionen.

### Vergleich des Datenzugriffs

Hier ist ein visueller Vergleich, wie jeder Ansatz mit einer typischen Abfrage umgeht.

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

### Leistung vs. Entwickler Produktivität trade

```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-Kern, der vollwertige ORM

[Kern des Rahmens der Einrichtung](https://learn.microsoft.com/en-us/ef/core/) ist Microsoft-Flaggschiff ORM.,, das eine vollständige Abstraktion über Ihre Datenbank bietet. Es unterstützt PostgreSQL durch die [Npgsql-.-EntitätsschemaKörnung.-PostgreSQL](https://www.npgsql.org/efcore/) Anbieter

Praktische Anleitung zum Einrichten von EF Core in Ihrem Projekt finden Sie unter [Hinzufügen von Entity Framework für Blog-Posts](/blog/addingentityframeworkforblogpostspt1).

### Hauptmerkmale

- **Nachverfolgung ändern**.: . Verfolgt automatisch Entity-Änderungen und generiert entsprechende SQL
- **Wanderungsbewegungen**Code- und Versionskontrolle des ersten Schemas [EF-Migrationen auf dem richtigen Weg](/blog/efmigrationstherightway))
- **LINQ-Anbieter**Sichere Abfragen mit Hilfe von Sprachkonstrukten
- **Lazy-/-Eager Laden**Flexible Beladungsstrategien für verwandte Unternehmen
- **Erweiterte PostgreSQL-Funktionen**Weitere Informationen: http:europa.eu.int[siehe meinen Artikel](/blog/textsearchingpt1)),, JSON-Säulen,, Arrays,, Bereichstypen
- **Abhörer und Veranstaltungen**Erweiterbarkeitspunkte für Querschnitte

### Beispiel: Basis CRUD mit EF-Kern

```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 mit Raw SQL

EF Core unterstützt auch rohe SQL-Abfragen, wenn Sie mehr Kontrolle benötigen

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

### Wann EF Core verwendet werden soll

**Verwendung des EF-Kerns**

- Aufbau einer neuen Anwendung mit sich entwickelnden Schemaanforderungen
- Sie benötigen eine starke Eingabe und Compilierung der Abfrage-Validierung
- Migrationen und Schemaversionierung sind wichtig [mein Migrationshandbuch](/blog/efmigrationstherightway))
- Ihr Team arbeitet lieber mit Objekten über SQL
- Sie verwenden komplexe Domänenmodelle mit Beziehungen
- Entwicklungsgeschwindigkeit ist kritischer als Rohleistung
- Du benötigst die Übertragbarkeit von cross---Datenbanken, obwohl PostgreSQL---spezifische Features dich in -) sperren.

**Vermeiden Sie EF-Kern, wenn**

- Maximale Leistungsfähigkeit ist kritisch (-High--Throughput APIs,-Chargenverarbeitung)-
- Sie haben komplexe SQL-Abfragen
- Ihre Abfragen dont-'.t Karte gut, um Graphen zu Objekt
- Sie benötigen feine Kontrolle über jede SQL-Anweisung
- Die Speichernutzung ist eine kritische Einschränkung.
- Sie arbeiten mit Legacy-Schemas, die nicht zu Konventionen führen

## EF-Core-SQL-Generierung – :-Verstehen, was ausgeführt wird

Einer der wichtigsten Aspekte des Einsatzes von EF Core effektiv ist, zu verstehen, was SQL es erzeugt .. . EF Core hat deutlich verbessert SQL-Generation im Laufe der Jahre ?, .

### SQL-Erzeugung anzeigen

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

### Beispiel: Einfache Abfrage

**LINQ-:**

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

**Erzeugte SQL-Daten[EF-Kern](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
```

Beachten Sie, wie EF-Core erzeugt clean-,. Effiziente SQL mit korrekter Parametrierung. [EF-Kern](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/whatsnew) setzt diesen Trend mit noch mehr Verbesserungen fort

### Beispiel: Verbindung mit dem EF-Kern

**Code des Herstellers:**

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

**Altes SQL-Kernmaterial (MSK) - kartesische 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"
```

Dies schafft eine **Kartesisches Erzeugnis** Wenn ein Beitrag 10 Kommentare ,, dass Zeile wiederholt wird 10 times

### Beispiel: geteilte Abfragen

**Code mit geteilter Abfrage**

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

**Erzeugte SQL-Daten Mehrsprachige Abfragen):**

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

Dies beseitigt das kartesische Produkt und ist oft **viel schneller** für Sammlungen

### Beispiel: :-gefiltert Einschließlich (-EF-Kern

**Code des Herstellers:**

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

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

### Beispiel: JSON-Säulenfragen

**Code des Herstellers:**

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

**Generiert SQL-:**

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

EF Core kann JSON-Eigenschaftszugriff auf PostgreSQL-JSON-Betreiber übersetzen

### Beispiel: Massenaktualisierung von (.EF-Kernen

**Alte Art und Weise**

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

**Neue Art und Weise**

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

**Erzeugte SQL-Datei (Single Query)**

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

Das ist ein **massiv** Verbesserung Eine SQL-Anweisung anstelle von NB!

### Beispiel: :-Gülle Löschen (-EF-Kern

**Alte Art und Weise**

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

**Neue Art und Weise**

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

**Generiert SQL-:**

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

### Beispiel: Komplexe Aggregation

**Code des Herstellers:**

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

**Generierter SQL-Kern (MSK)**

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

Für einen tieferen Tauchgang in die vollständige --Textsuche siehe meinen Artikel auf [Umsetzung der Volltextsuche mit EF Core](/blog/textsearchingpt1).

**Code des Herstellers:**

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

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

## Kritische EF-Kernwarnungen und Pitfalls

### Änderungen der Tracking-Speicherlecks

**Das Problem**

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

**Warum es ein Problem ist**

- Verfolgte Entitäten bleiben für die Lebensdauer der `DbContext`
- Der Change-Tracker hält Referenzen bereit.
- Lange, langanhaltende Kontexte von (e.eg.,e singletons)e
- Im Kontext von ASPA.-NET wird standardmäßig scoped.(...good.!)...
- Aber wenn du getrackte Entities zwischengespeichert hast

**Die Lösung**

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

### Proxy-Erzeugung und lazy Belastung Gefahren

> **KRITISCHE WARNUNG**
> 
> Lazy Belastungs-Proxys Caching **GARANTIERTE MEMORY LEAK**
> 
> Wenn Sie DbContext-Instanzen oder Cache-Entitäten mit aktivierten Proxies zwischenspeichern **WIRD** Der Proxy-Mechanismus hält Verweise auf den DbContext.,.Verhindert die Müllsammelung...Dies ist einer der häufigsten und gefährlichsten Fehler in EF Core-Anwendungen.
> 
> **Faustregel**Immer Sammlungen explizit mit einbeziehen `.Include()`Verwenden Sie Proxies nur dann, wenn Sie die Tradeoffs vollständig verstehen und niemals ,... jemals Proxy-Entitäten zwischenspeichern.

**Problem-Abfrage Nightmare**

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

**Was passiert?**

1. Erste Abfrage lädt alle Beiträge
2. Für **jede Stelle**Zugriff `Category` löst eine Datenbankabfrage aus
3. Für **jede Stelle**Zugriff `Comments` löst eine weitere Abfrage aus
4. Wenn du gerade ausgeführt hast **Abfragen**!

**Problem: Proxy-Proxy-Caching-Speicherleak**

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

**Warum ist dies katastrophal?**

- Proxy-Entitäten pflegen einen Verweis auf ihre `DbContext`
- Das `DbContext` pflegt einen Verweis auf alle verfolgten Einheiten
- Ihr Cache verhindert nun, dass der gesamte Objektgraph gesammelt wird
- Jedes Mal, wenn Sie auf eine Navigationseigenschaft zugreifen, kann es Abfragen mit dem **alter Kontext mit Zwischenspeichern**
- Speicher wächst ungebunden, wenn Sie mehr Daten laden
- You-'-ll läuft schließlich aus dem Speicher oder Abgasanschluss Pools

**Die Lösung ist explizit**

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

**Wenn Proxies akzeptabel sein könnten, verstehen Sie die Tradeoffs.**

Lazy Lade-Proxys könnten nur dann akzeptabel sein, wenn

1. Sie haben **kurz, kurz und lang** Scoped-Kontexten (MSK) für HTTP-Anfragen
2. Sie **niemals** Cache-Entities
3. Sie sind in Ordnung mit der Abfrageleistung von N.+1.
4. Sie werden das Prototyping durchführen und später optimieren.
5. Ihr Team versteht die Implikationen

Aber selbst dann `Include()` ist fast immer die bessere Wahl, weil

- Es macht Daten laden **explizit und offensichtlich**
- Es handelt sich um **einfacher zu optimieren** Sie können sehen, was geladen wird.
- Es ist **verhindert unbeabsichtigte Abfragen**
- Es funktioniert korrekt mit Caching und langanhaltenden Kontexten
- Es ist das **empfohlener Ansatz** vom EF Core-Team

### DbContext Lebenslange Probleme

Weitere Details zur Verwaltung von DbContext-Lebensdauer in der Produktion finden Sie in meinem Artikel [EF-Migrationen auf dem richtigen Weg](/blog/efmigrationstherightway).

**Das Problem**

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

**Warum es ' falsch ist**

- `DbContext` wird **nicht gewindestark**
- Gleichzeitige Anfragen verursachen Datenkorruption
- Tracker auf unbestimmte Zeit wechseln
- Ausschöpfung des Verbindungspools
- Stale Daten aus dem Cache

**Die Lösung**

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

### Nicht beabsichtigt Beinhaltet in Navigationseigenschaften

**Das Problem**

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

**Die Lösung**

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

### Async vs Sync Mischen

**Das Problem**

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

**Die Lösung**

```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 - neue und durchbrechende Veränderungen

Mit [EF-Kern](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/whatsnew) veröffentlicht neben -.-NET -10, , gibt es mehrere wichtige Änderungen zu beachten, wenn Upgrades-. Für die vollständige Liste finden Sie unter [Bruch der Veränderungen im EF-Core](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/breaking-changes).

### Anforderungen an die Laufzeit

**Der EF-Kern erfordert ,. ,NET , 10 ,**Es wird nicht auf .-NET ausgeführt.Dies ist die wichtigste Änderung, um sicherzustellen, dass Ihre Projektziele `net10.0` vor dem Aufrüsten

### Abfragen Related Breaking-Änderungen

#### Parameterisierte Sammlungsübersetzung

EF Core ändert, wie `Contains()` mit In---Memory-Sammlungen wird in SQL , . , zuvor , verwendet , , EF Core `OpenJson()` SQL Server, ) oder ähnliches . **Parameter Arrays** die einen besseren Abfrageplan für Caching zur Verfügung stellen

**Auswirkungen**Sie können verschiedene SQL-generierte für Abfragen sehen, z.B.

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

**EF-Kerne**

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

**EF-Kernmittel (MSK)**

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

**Wenn Sie Leistungsregressionen erleben**Kehren Sie zum alten Verhalten zurück

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

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

#### AusführenUpdateAsync Signature Change

Das `ExecuteUpdateAsync` Signatur hat sich geändert, um nicht zu unterstützen --Expression Lambdas-.. Dies ist flexibler aber **bricht Code, der Ausdruck Bäume programmatisch gebaut**:

**Alte Art und Weise**

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

**Neu im EF-Kern**

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

#### Umfassende Typ-Säulenbezeichnung

EF Core ändert, wie verschachtelte komplexe Typ Spalten benannt werden, um Datenkorruption zu verhindern.

**EF-Kern**

```
NestedComplex_Property
```

**EF-Kern**

```
OuterComplex_NestedComplex_Property
```

**Migrationsauswirkungen**Wenn Sie vorhandene Tabellen mit komplexen Typen haben, müssen Sie ggf. Spalten umbenennen oder explizite Spaltennamen konfigurieren.

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

### SQL Server Spezielle Änderungen von Azure SQL

#### JSON-Datentyp Standard

Für Azure SQL Database oder SQL Server Standardwerte für das neue native `JSON` Datentyp statt `NVARCHAR(MAX)`.

**Abmelden** Wenn Sie Rückwärtskompatibilität benötigen

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

### Checkliste aktualisieren

Beim Upgrade von EF Core auf EF Core

1. Aktualisierung des Zielrahmens auf `net10.0`
2. Alle aktualisieren `Microsoft.EntityFrameworkCore.*` Pakete zu -10.-x
3. Aktualisierung `Npgsql.EntityFrameworkCore.PostgreSQL` bis zu 10.-x
4. Überprüfung von Abfragen mit `Contains()` mit Sammlungen für Performance-Änderungen
5. Testen Sie jeden Code, der programmatisch baut `ExecuteUpdateAsync` Ausdrücke
6. Prüfen Sie komplexe Typ Spaltennamen, wenn verschachtelte Komplextypen verwendet werden
7. Überprüfe die Verwendung von SQL Server JSON-Spalten, wenn Azure SQL®/®SQL Server verwendet wird.

### Was ist '?Neuen Höhepunkte

- **Verbesserte LINQ-Übersetzung**Bessere SQL-Generierung für komplexe Abfragen
- **Nicht-Ausdruck Lambdas in AusführenUpdateAsync**Mehr Flexibilität bei Massenaktualisierungen
- **Bessere Parameterbehandlung**Verbessertes Abfrageplan-Caching
- **Verstärkte JSON-Unterstützung**Native JSON-Typ auf SQL Server
- **Leistungsverbesserungen**Schnellere Materialisierung und Änderungsverfolgung

## Leistungsmerkmale

- **Abfrageleistung**Im Vergleich zu Dapper für einfache Abfragen
- **Speichernutzung**Höher durch Änderungsverfolgung und Proxy-Generierung
- **Erste Abfrage**Langsame Zusammenstellung und Caching
- **Nachfolgende Fragen**Schneller durch kompilierten Abfrage-Cache
- **Einfügungen-/-Updates**Automatische Änderungsverfolgung fügt Overhead hinzu
- **Massenbetrieb**Schlechte Leistung mit Standard-Methoden [EFCore-.-BulkExtensionen](https://github.com/borisdj/EFCore.BulkExtensions) oder ExecuteAktualisierung/-AusführenLöschen Sie im EF-Kern aus.

## Best Practices für EF Core

### Allgemeine Leitlinien

1. **AsNoTracking verwenden** für nur Abfragen von Lese--, um den Speicherüberkopf zu reduzieren
2. **Abfragen vermeiden** Verwendung `Include()` oder aufgeteilte Abfragen angemessen
3. **Kompilierte Abfragen verwenden** für wiederholte Abfragemuster
4. **Betrachten Sie alsSplitQuery-()** für Komplex beinhaltet, um kartesische Produkte zu vermeiden
5. **Chargen verwenden** für mehrere Inserts-/-Updates
6. **Projekt zu DTOs** frühen Datenübertragung und Speicherverbrauch zu reduzieren
7. **Leverage ExecuteAktualisierung/-AusführenLöschen** Für Massengut-Betriebe
8. **Immer loggen und überprüfen generiert SQL** in der Entwicklung

### Bei der Arbeit mit PostgreSQL

1. **Volle Textsuche verwenden** Merkmale[Mein Führer](/blog/textsearchingpt1)Nicht mehr als `LIKE` Abfragen
2. **Leverage-JSONB-Spalten** für flexible Schema-Daten
3. **Array-Typen verwenden** für Sammlungen innerhalb von Einheiten
4. **Konfigurieren von Verbindungspooling** richtig für Ihre Arbeitsbelastung
5. **Indexieren Sie Ihre `tsvector` Spalten** mit GIN-Indizes
6. **Verwendungsbereichstypen** für Datums-/-Zeitbereiche

## In Teil kommend

Im nächsten Artikel erkunden wir uns mit :

- **Dapper**Die Mikro-- ORM Sweet Spot
- **Rohes ADO-.-NETT/-Npgsql**Maximale Leistung und Kontrolle
- **Objektmapping-Bibliotheken**Kartenster gegen Automapper
- **Hybride Ansätze**Kombination von EF-Kern und Dapper-Mustern aus (CQRS
- **Leistungsvergleiche**Vergleich der realen Werte der Welt
- **Entscheidungsmatrix**Wählen Sie das richtige Werkzeug für Ihr Szenario

## Literaturhinweise und weitere Lektüre

- [Grundlegende Dokumentation der Einrichtung](https://learn.microsoft.com/en-us/ef/core/)
- [Npgsql Entity Framework Core Provider](https://www.npgsql.org/efcore/)
- [EF Kernleistung](https://learn.microsoft.com/en-us/ef/core/performance/)
- [Was ist neu im EF-Kern?](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/whatsnew)
- [Bruch der Veränderungen im EF-Kern](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/breaking-changes)
- [Was ist neu im EF-Kern?](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew)
- [PostgreSQL-Dokumentation](https://www.postgresql.org/docs/)

**Verwandte Artikel auf diesem Blog**

- [Hinzufügen von Entity Framework für Blog-Posts](/blog/addingentityframeworkforblogpostspt1)
- [EF-Migrationen auf dem richtigen Weg](/blog/efmigrationstherightway)
- [Volltextsuche mit EF Core](/blog/textsearchingpt1)
- [Moderne CQRS und Event Sourcing](/blog/moderncqrsandeventsourcing)

---


*Im Teil tauchen wir in Dapper , , , roh Npgsql ,, , und erforschen Sie, wie man mehrere Ansätze für optimale Leistung und Wartungsfähigkeit kombinieren*