Datenzugriff in der Analyse von ORMs und Mapping-Strategien
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 Einrichten von EF-Kern von Grund auf
- EF-Migrationen auf dem richtigen Weg Wie man Migrationen in der Produktion richtig behandelt
- Volltextsuche Umsetzung der PostgreSQL-Volltextsuche mit EF Core
- Moderne CQRS und Event Sourcing 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.
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
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 ist Microsoft-Flaggschiff ORM.,, das eine vollständige Abstraktion über Ihre Datenbank bietet. Es unterstützt PostgreSQL durch die Npgsql-.-EntitätsschemaKörnung.-PostgreSQL Anbieter
Praktische Anleitung zum Einrichten von EF Core in Ihrem Projekt finden Sie unter Hinzufügen von Entity Framework für Blog-Posts.
Hauptmerkmale
- Nachverfolgung ändern.: . Verfolgt automatisch Entity-Änderungen und generiert entsprechende SQL
- WanderungsbewegungenCode- und Versionskontrolle des ersten Schemas EF-Migrationen auf dem richtigen Weg)
- LINQ-AnbieterSichere Abfragen mit Hilfe von Sprachkonstrukten
- Lazy-/-Eager LadenFlexible Beladungsstrategien für verwandte Unternehmen
- Erweiterte PostgreSQL-FunktionenWeitere Informationen: http:europa.eu.intsiehe meinen Artikel),, JSON-Säulen,, Arrays,, Bereichstypen
- Abhörer und VeranstaltungenErweiterbarkeitspunkte für Querschnitte
Beispiel: Basis CRUD mit EF-Kern
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
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)
- 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
// 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-:
var recentPosts = await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.OrderByDescending(p => p.PublishedDate)
.Take(10)
.ToListAsync();
Erzeugte SQL-DatenEF-Kern):
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 setzt diesen Trend mit noch mehr Verbesserungen fort
Beispiel: Verbindung mit dem EF-Kern
Code des Herstellers:
var posts = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.ToListAsync();
Altes SQL-Kernmaterial (MSK) - kartesische Explosion
SELECT b."Id", b."Title", c."Id", c."Name", cm."Id", cm."Content"
FROM "BlogPosts" AS b
LEFT JOIN "Categories" AS c ON b."CategoryId" = c."Id"
LEFT JOIN "Comments" AS cm ON b."Id" = cm."BlogPostId"
ORDER BY b."Id", c."Id"
Dies schafft eine Kartesisches Erzeugnis Wenn ein Beitrag 10 Kommentare ,, dass Zeile wiederholt wird 10 times
Beispiel: geteilte Abfragen
Code mit geteilter Abfrage
var posts = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.AsSplitQuery() // ← This is the key!
.ToListAsync();
Erzeugte SQL-Daten Mehrsprachige Abfragen):
-- 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:
var posts = await _context.BlogPosts
.Include(p => p.Comments.Where(c => c.IsApproved))
.ToListAsync();
Generiert 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:
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-:
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
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
await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.IsArchived, true));
Erzeugte SQL-Datei (Single Query)
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
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
await _context.BlogPosts
.Where(p => p.PublishedDate < DateTime.UtcNow.AddYears(-5))
.ExecuteDeleteAsync();
Generiert SQL-:
DELETE FROM "BlogPosts" AS b
WHERE b."PublishedDate" < @__p_0
Beispiel: Komplexe Aggregation
Code des Herstellers:
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)
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.
Code des Herstellers:
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-:
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
// ❌ 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
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.
FaustregelImmer 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
// ❌ 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?
- Erste Abfrage lädt alle Beiträge
- Für jede StelleZugriff
Categorylöst eine Datenbankabfrage aus - Für jede StelleZugriff
Commentslöst eine weitere Abfrage aus - Wenn du gerade ausgeführt hast Abfragen!
Problem: Proxy-Proxy-Caching-Speicherleak
// ❌ 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
DbContextpflegt 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
// ✅ 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
- Sie haben kurz, kurz und lang Scoped-Kontexten (MSK) für HTTP-Anfragen
- Sie niemals Cache-Entities
- Sie sind in Ordnung mit der Abfrageleistung von N.+1.
- Sie werden das Prototyping durchführen und später optimieren.
- 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.
Das Problem
// ❌ 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
DbContextwird nicht gewindestark- Gleichzeitige Anfragen verursachen Datenkorruption
- Tracker auf unbestimmte Zeit wechseln
- Ausschöpfung des Verbindungspools
- Stale Daten aus dem Cache
Die Lösung
// ✅ 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
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
// ✅ 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
// ❌ 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
// ✅ 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 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.
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
AuswirkungenSie können verschiedene SQL-generierte für Abfragen sehen, z.B.
var ids = new List<int> { 1, 2, 3, 4, 5 };
var posts = await _context.BlogPosts
.Where(p => ids.Contains(p.Id))
.ToListAsync();
EF-Kerne
SELECT b."Id", b."Title"
FROM "BlogPosts" AS b
WHERE b."Id" IN (SELECT value FROM OPENJSON(@__ids_0))
EF-Kernmittel (MSK)
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 erlebenKehren Sie zum alten Verhalten zurück
// 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
// This still works
await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.IsArchived, true));
Neu im EF-Kern
// 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
MigrationsauswirkungenWenn Sie vorhandene Tabellen mit komplexen Typen haben, müssen Sie ggf. Spalten umbenennen oder explizite Spaltennamen konfigurieren.
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
optionsBuilder.UseAzureSql(connectionString,
o => o.UseCompatibilityLevel(160)); // Use old NVARCHAR behavior
Checkliste aktualisieren
Beim Upgrade von EF Core auf EF Core
- Aktualisierung des Zielrahmens auf
net10.0 - Alle aktualisieren
Microsoft.EntityFrameworkCore.*Pakete zu -10.-x - Aktualisierung
Npgsql.EntityFrameworkCore.PostgreSQLbis zu 10.-x - Überprüfung von Abfragen mit
Contains()mit Sammlungen für Performance-Änderungen - Testen Sie jeden Code, der programmatisch baut
ExecuteUpdateAsyncAusdrücke - Prüfen Sie komplexe Typ Spaltennamen, wenn verschachtelte Komplextypen verwendet werden
- Überprüfe die Verwendung von SQL Server JSON-Spalten, wenn Azure SQL®/®SQL Server verwendet wird.
Was ist '?Neuen Höhepunkte
- Verbesserte LINQ-ÜbersetzungBessere SQL-Generierung für komplexe Abfragen
- Nicht-Ausdruck Lambdas in AusführenUpdateAsyncMehr Flexibilität bei Massenaktualisierungen
- Bessere ParameterbehandlungVerbessertes Abfrageplan-Caching
- Verstärkte JSON-UnterstützungNative JSON-Typ auf SQL Server
- LeistungsverbesserungenSchnellere Materialisierung und Änderungsverfolgung
Leistungsmerkmale
- AbfrageleistungIm Vergleich zu Dapper für einfache Abfragen
- SpeichernutzungHöher durch Änderungsverfolgung und Proxy-Generierung
- Erste AbfrageLangsame Zusammenstellung und Caching
- Nachfolgende FragenSchneller durch kompilierten Abfrage-Cache
- Einfügungen-/-UpdatesAutomatische Änderungsverfolgung fügt Overhead hinzu
- MassenbetriebSchlechte Leistung mit Standard-Methoden EFCore-.-BulkExtensionen oder ExecuteAktualisierung/-AusführenLöschen Sie im EF-Kern aus.
Best Practices für EF Core
Allgemeine Leitlinien
- AsNoTracking verwenden für nur Abfragen von Lese--, um den Speicherüberkopf zu reduzieren
- Abfragen vermeiden Verwendung
Include()oder aufgeteilte Abfragen angemessen - Kompilierte Abfragen verwenden für wiederholte Abfragemuster
- Betrachten Sie alsSplitQuery-() für Komplex beinhaltet, um kartesische Produkte zu vermeiden
- Chargen verwenden für mehrere Inserts-/-Updates
- Projekt zu DTOs frühen Datenübertragung und Speicherverbrauch zu reduzieren
- Leverage ExecuteAktualisierung/-AusführenLöschen Für Massengut-Betriebe
- Immer loggen und überprüfen generiert SQL in der Entwicklung
Bei der Arbeit mit PostgreSQL
- Volle Textsuche verwenden MerkmaleMein FührerNicht mehr als
LIKEAbfragen - Leverage-JSONB-Spalten für flexible Schema-Daten
- Array-Typen verwenden für Sammlungen innerhalb von Einheiten
- Konfigurieren von Verbindungspooling richtig für Ihre Arbeitsbelastung
- Indexieren Sie Ihre
tsvectorSpalten mit GIN-Indizes - Verwendungsbereichstypen für Datums-/-Zeitbereiche
In Teil kommend
Im nächsten Artikel erkunden wir uns mit :
- DapperDie Mikro-- ORM Sweet Spot
- Rohes ADO-.-NETT/-NpgsqlMaximale Leistung und Kontrolle
- Objektmapping-BibliothekenKartenster gegen Automapper
- Hybride AnsätzeKombination von EF-Kern und Dapper-Mustern aus (CQRS
- LeistungsvergleicheVergleich der realen Werte der Welt
- EntscheidungsmatrixWählen Sie das richtige Werkzeug für Ihr Szenario
Literaturhinweise und weitere Lektüre
- Grundlegende Dokumentation der Einrichtung
- Npgsql Entity Framework Core Provider
- EF Kernleistung
- Was ist neu im EF-Kern?
- Bruch der Veränderungen im EF-Kern
- Was ist neu im EF-Kern?
- PostgreSQL-Dokumentation
Verwandte Artikel auf diesem Blog
- Hinzufügen von Entity Framework für Blog-Posts
- EF-Migrationen auf dem richtigen Weg
- Volltextsuche mit EF Core
- Moderne CQRS und Event Sourcing
Im Teil tauchen wir in Dapper , , , roh Npgsql ,, , und erforschen Sie, wie man mehrere Ansätze für optimale Leistung und Wartungsfähigkeit kombinieren