Datautrymme i .NET: Vergleich av ORM och kartläggningsstrategier | ( | Part |1 | МSK4 | Entity Framework Core
När man bygger NET-applikationer, är en av de viktigaste arkitektoniska besluten du kommer att göra, ', hur man hanterar dataangång och objektkartläggning... NET: s ekosystem erbjuder en mängd olika tillvägagångssätt, från fulla --komponenter till bar ORM.
I den här komplexa två delarguiden, , och ', utforskar vi de mest populära data-utgångsmönsterna i NET. Medan vi använder PostgreSQL med Npgsql i våra exempel.
Del 1 (den här artikelnM SK2 fokuserar på Entity Framework Core , ,, generation av SQL, , och vanliga fall. Del 2 kommer att täcka Dapper, råda ADO. NETM SK2 objektkarteringsbibliotek , och hybrida tillvägagångssätt
Related Articles
Om ni är intresserade av praktiska EF Core implementeringar
- Att lägga till enhetens ram för bloggposter (Teil 1) - Att sätta upp EF Core från början
- EF- Migrationer på rätt väg - Hur man hanterar migrationer ordentligt i produktion
- Full Textsökning (Pt 1) - Implementering of PostgreSQL full -text search with EF Core
- Modern CQRS och Event Sourcing - avancerade arkitektoniska mönster med EF Core
Tabellen med innehåll
Spektrumet av datatillgångssätt
.NET-datatillgångs landskapet kan visualiseras som ett spektrum
Full Abstraction Full Control
↓ ↓
[EF Core] → [EF Core Raw SQL] → [Dapper] → [Npgsql ADO.NET]
När du rör dig från vänster till höger
Vergleich av datatillgångsströmmar
Här är en visuell jämförelse av hur varje metod hanterar en typisk fråga.
graph TB
subgraph "EF Core Flow"
A1[LINQ Query] -->|Compile| B1[Expression Tree]
B1 -->|Translate| C1[SQL Query]
C1 -->|Execute| D1[PostgreSQL]
D1 -->|Results| E1[DbDataReader]
E1 -->|Materialize| F1[Tracked Entities]
F1 -->|Return| G1[Application]
end
subgraph "Dapper Flow"
A2[SQL String] -->|Parameterize| B2[DbCommand]
B2 -->|Execute| C2[PostgreSQL]
C2 -->|Results| D2[DbDataReader]
D2 -->|Map| E2[POCOs]
E2 -->|Return| F2[Application]
end
subgraph "Raw Npgsql Flow"
A3[SQL + Parameters] -->|Build Command| B3[NpgsqlCommand]
B3 -->|Execute| C3[PostgreSQL]
C3 -->|Results| D3[NpgsqlDataReader]
D3 -->|Manual Mapping| E3[Objects]
E3 -->|Return| F3[Application]
end
style A1 stroke:#2563eb,stroke-width:2px
style B1 stroke:#2563eb,stroke-width:2px
style C1 stroke:#2563eb,stroke-width:2px
style D1 stroke:#2563eb,stroke-width:2px
style E1 stroke:#2563eb,stroke-width:2px
style F1 stroke:#2563eb,stroke-width:2px
style G1 stroke:#2563eb,stroke-width:2px
style A2 stroke:#059669,stroke-width:2px
style B2 stroke:#059669,stroke-width:2px
style C2 stroke:#059669,stroke-width:2px
style D2 stroke:#059669,stroke-width:2px
style E2 stroke:#059669,stroke-width:2px
style F2 stroke:#059669,stroke-width:2px
style A3 stroke:#dc2626,stroke-width:2px
style B3 stroke:#dc2626,stroke-width:2px
style C3 stroke:#dc2626,stroke-width:2px
style D3 stroke:#dc2626,stroke-width:2px
style E3 stroke:#dc2626,stroke-width:2px
style F3 stroke:#dc2626,stroke-width:2px
Performance vs Developer Productivity Trade-off
graph LR
A[High Productivity<br/>Low Performance] --> B[EF Core<br/>Full Tracking]
B --> C[EF Core<br/>No Tracking]
C --> D[EF Core<br/>Raw SQL]
D --> E[Dapper]
E --> F[Raw Npgsql]
F --> G[Low Productivity<br/>High Performance]
style A stroke:#2563eb,stroke-width:2px
style B stroke:#2563eb,stroke-width:2px
style C stroke:#3b82f6,stroke-width:2px
style D stroke:#059669,stroke-width:2px
style E stroke:#059669,stroke-width:2px
style F stroke:#dc2626,stroke-width:2px
style G stroke:#dc2626,stroke-width:2px
Enhetens ramskärna Core: Den fulla-Featured ORM
Enhetens ramskärna är Microsofts flagship ORM, ,, som ger en komplett abstraktion över din databas. Npgsql.EntityFrameworkCore.PostgreSQL leverantör.
För praktiska instruktioner om att sätta upp EF Core i ditt projekt, se min artikel på Att lägga till enhetens ram för bloggposter.
Nyckliga egenskaper
- ändra spårning: Automatiskt följer enhetens förändringar och genererar lämpliga SQL
- Migrationer: Kod-first schemamanagement och versionskontroll | ( | se EF- Migrationer på rätt väg)
- LINQ-leverantör: Typ
- Lazy/Ansträngt laddande: Flexible loading strategies for related entities
- avancerade PostgreSQL-fenskaper: Full-textsökning (se min artikel), JSON- kolumner , skiffer
- Interceptörer och händelser: Brejbaritetspunkter för cross-
exempel: Grund CRUD med EF Core
public class BlogDbContext : DbContext
{
public DbSet<BlogPost> BlogPosts { get; set; }
public DbSet<Comment> Comments { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql("Host=localhost;Database=blog;Username=postgres;Password=secret");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// PostgreSQL-specific: Full-text search
modelBuilder.Entity<BlogPost>()
.HasGeneratedTsVectorColumn(
p => p.SearchVector,
"english",
p => new { p.Title, p.Content })
.HasIndex(p => p.SearchVector)
.HasMethod("GIN");
// PostgreSQL array type
modelBuilder.Entity<BlogPost>()
.Property(p => p.Tags)
.HasPostgresArrayConversion(
tag => tag.ToLowerInvariant(),
tag => tag);
}
}
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public string[] Tags { get; set; }
public NpgsqlTsVector SearchVector { get; set; }
public List<Comment> Comments { get; set; }
public DateTime PublishedDate { get; set; }
}
// Usage
public class BlogService
{
private readonly BlogDbContext _context;
public async Task<List<BlogPost>> GetRecentPostsAsync(int count)
{
return await _context.BlogPosts
.Include(p => p.Comments)
.OrderByDescending(p => p.PublishedDate)
.Take(count)
.ToListAsync();
}
public async Task<List<BlogPost>> SearchPostsAsync(string searchTerm)
{
return await _context.BlogPosts
.Where(p => p.SearchVector.Matches(EF.Functions.ToTsQuery("english", searchTerm)))
.ToListAsync();
}
public async Task AddPostAsync(BlogPost post)
{
_context.BlogPosts.Add(post);
await _context.SaveChangesAsync();
}
}
EF Core med rå SQL
EF Core supporterar också rå SQL-fragen när du behöver mer kontroll
public async Task<List<BlogPost>> GetPostsByComplexCriteriaAsync()
{
var searchTerm = "postgresql";
return await _context.BlogPosts
.FromSqlInterpolated($@"
SELECT * FROM ""BlogPosts""
WHERE ""SearchVector"" @@ to_tsquery('english', {searchTerm})
AND array_length(""Tags"", 1) > 3
ORDER BY ts_rank(""SearchVector"", to_tsquery('english', {searchTerm})) DESC
")
.ToListAsync();
}
// Or with DbDataReader for maximum control
public async Task<List<PostStatistics>> GetPostStatisticsAsync()
{
using var command = _context.Database.GetDbConnection().CreateCommand();
command.CommandText = @"
SELECT
DATE_TRUNC('month', ""PublishedDate"") as Month,
COUNT(*) as PostCount,
AVG(ARRAY_LENGTH(""Tags"", 1)) as AvgTags
FROM ""BlogPosts""
GROUP BY DATE_TRUNC('month', ""PublishedDate"")
ORDER BY Month DESC";
await _context.Database.OpenConnectionAsync();
var results = new List<PostStatistics>();
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
results.Add(new PostStatistics
{
Month = reader.GetDateTime(0),
PostCount = reader.GetInt32(1),
AverageTags = reader.GetDouble(2)
});
}
return results;
}
När man ska använda EF Core
✅ Använd EF Core när
- Att bygga en ny applikation med utvecklande schemaanforderunger
- Du behöver en stark typning och kompilera
- Migrationer och schemaversionering är viktiga (se min migrationsführer)
- ditt team föredrar att arbeta med objekt än med SQL
- Du använder komplexa domänmodeller med relationer
- Utvecklingshastighet är viktigare än råpresterande
- Du behöver cross-databasens överförbarhet | ( | fast PostgreSQL |- | specifika draginställningar låser dig i
❌ Avoid EF Core när
- Maximmunska performance är kritiskt (high-throughput APIs
- Du har komplexa, hand
- Dina frågor kartlägger inte riktigt objektgraferna
- Du behöver fin-kontroll över varje SQL-sats
- Att använda minnet är en viktig begränsning. (förändring av spårning överhuvudtaget
- Du arbetar med gamla schema som inte kartlägger ' till konventioner
EF Core SQL Generation: förstå vad som utförs
En av de viktigaste aspekterna av att använda EF Core effektivt är att förstå vad SQL den genererar.
Att se Genererad SQL
// Enable sensitive data logging and detailed errors (development only!)
optionsBuilder
.UseNpgsql(connectionString)
.EnableSensitiveDataLogging()
.EnableDetailedErrors()
.LogTo(Console.WriteLine, LogLevel.Information);
// Or use logging to see SQL
public class BlogService
{
private readonly BlogDbContext _context;
private readonly ILogger<BlogService> _logger;
public async Task<List<BlogPost>> GetPostsAsync()
{
var query = _context.BlogPosts
.Where(p => p.PublishedDate > DateTime.UtcNow.AddDays(-30))
.OrderByDescending(p => p.PublishedDate);
// View the SQL before execution
var sql = query.ToQueryString();
_logger.LogInformation("Executing query: {Sql}", sql);
return await query.ToListAsync();
}
}
exempel : Enkel fråga
C# LINQ
var recentPosts = await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.OrderByDescending(p => p.PublishedDate)
.Take(10)
.ToListAsync();
Genererad SQL (EF Core 8+):
SELECT b."Id", b."Title", b."Content", b."CategoryId", b."PublishedDate"
FROM "BlogPosts" AS b
WHERE b."CategoryId" = @__categoryId_0
ORDER BY b."PublishedDate" DESC
LIMIT @__p_1
Lägg märke till hur EF Core 8+ genererar ren , effektiv SQL med korrekt parameterisering EF Core 10 fortsätter denna trend med ännu fler framsteg
exempel: Join with Include (Before EF Core 5)
C# Kod
var posts = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.ToListAsync();
Gamla SQL (EF Core 3.1 - Kartesisk explosion
SELECT b."Id", b."Title", c."Id", c."Name", cm."Id", cm."Content"
FROM "BlogPosts" AS b
LEFT JOIN "Categories" AS c ON b."CategoryId" = c."Id"
LEFT JOIN "Comments" AS cm ON b."Id" = cm."BlogPostId"
ORDER BY b."Id", c."Id"
Detta skapar en Cartesiansk produkt - om ett meddelande har 10 kommentarer M SK2 så upprepas rown 10 gånger
exempel: Split Queries (EF Core 5+)
C# Kod med Split Query
var posts = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.AsSplitQuery() // ← This is the key!
.ToListAsync();
Genererad SQL (Multiple Queries):
-- Query 1: Get posts and categories
SELECT b."Id", b."Title", b."Content", c."Id", c."Name"
FROM "BlogPosts" AS b
LEFT JOIN "Categories" AS c ON b."CategoryId" = c."Id"
-- Query 2: Get comments for those posts
SELECT cm."Id", cm."Content", cm."BlogPostId"
FROM "Comments" AS cm
INNER JOIN (
SELECT b."Id"
FROM "BlogPosts" AS b
) AS t ON cm."BlogPostId" = t."Id"
ORDER BY t."Id"
Detta eliminerar den karttensiska produkten och är ofta mycket snabbare för samlingar!
exempel: Filterat Inbegripa (EF Core 5+)
C# Kod
var posts = await _context.BlogPosts
.Include(p => p.Comments.Where(c => c.IsApproved))
.ToListAsync();
Genererat SQL:
SELECT b."Id", b."Title", b."Content", t."Id", t."Content", t."IsApproved"
FROM "BlogPosts" AS b
LEFT JOIN (
SELECT c."Id", c."Content", c."IsApproved", c."BlogPostId"
FROM "Comments" AS c
WHERE c."IsApproved" = TRUE
) AS t ON b."Id" = t."BlogPostId"
ORDER BY b."Id"
exempel: JSON Column Queries (EF Core 7+)
C# Kod
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public PostMetadata Metadata { get; set; } // Stored as JSONB
}
public class PostMetadata
{
public bool IsFeatured { get; set; }
public int ViewCount { get; set; }
public List<string> RelatedTags { get; set; }
}
// Query JSON properties
var featuredPosts = await _context.BlogPosts
.Where(p => p.Metadata.IsFeatured)
.ToListAsync();
Genererat SQL:
SELECT b."Id", b."Title", b."Metadata"
FROM "BlogPosts" AS b
WHERE b."Metadata" ->> 'IsFeatured' = 'true'
EF Core 7+ kan översätta JSONs egenskap till PostgreSQL JSON-operatörer
exempel: Bulkupdate (EF Core 7+ ExecuteUpdateM SK3
Old Way (Ineffektivt):
var posts = await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.ToListAsync();
foreach (var post in posts)
{
post.IsArchived = true;
}
await _context.SaveChangesAsync(); // Generates N UPDATE statements!
Nya sätt (EF Core 7+):
await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.IsArchived, true));
Genererad SQL (En enda fråga!):
UPDATE "BlogPosts" AS b
SET "IsArchived" = TRUE
WHERE b."CategoryId" = 5
Det här är en massiva förbättring - ett SQL-uttalande istället för N!
exempel: Storskaffa (EF Core 7+)
Old Way:
var oldPosts = await _context.BlogPosts
.Where(p => p.PublishedDate < DateTime.UtcNow.AddYears(-5))
.ToListAsync();
_context.BlogPosts.RemoveRange(oldPosts);
await _context.SaveChangesAsync(); // N DELETE statements
New Way:
await _context.BlogPosts
.Where(p => p.PublishedDate < DateTime.UtcNow.AddYears(-5))
.ExecuteDeleteAsync();
Genererat SQL:
DELETE FROM "BlogPosts" AS b
WHERE b."PublishedDate" < @__p_0
exempel: Kompleks aggregation
C# Kod
var categoryStats = await _context.Categories
.Select(c => new CategoryStats
{
CategoryName = c.Name,
PostCount = c.BlogPosts.Count(),
LatestPostDate = c.BlogPosts.Max(p => p.PublishedDate),
AverageComments = c.BlogPosts.Average(p => p.Comments.Count)
})
.ToListAsync();
Genererad SQL (EF Core 8+/10):
SELECT c."Name" AS "CategoryName",
COUNT(*)::int AS "PostCount",
MAX(b."PublishedDate") AS "LatestPostDate",
COALESCE(AVG((
SELECT COUNT(*)::int
FROM "Comments" AS c0
WHERE b."Id" = c0."BlogPostId"
))::double precision, 0.0) AS "AverageComments"
FROM "Categories" AS c
LEFT JOIN "BlogPosts" AS b ON c."Id" = b."CategoryId"
GROUP BY c."Id", c."Name"
PostgreSQL Full-Textsökning
För att dyka djupare in i det hela, se min artikel på implementerar full-textsökning med EF Core.
C# Kod
var searchResults = await _context.BlogPosts
.Where(p => p.SearchVector.Matches(EF.Functions.ToTsQuery("english", "postgresql & performance")))
.OrderByDescending(p => p.SearchVector.Rank(EF.Functions.ToTsQuery("english", "postgresql & performance")))
.Take(20)
.ToListAsync();
Genererat SQL:
SELECT b."Id", b."Title", b."Content", b."SearchVector"
FROM "BlogPosts" AS b
WHERE b."SearchVector" @@ to_tsquery('english', @__searchTerm_0)
ORDER BY ts_rank(b."SearchVector", to_tsquery('english', @__searchTerm_0)) DESC
LIMIT 20
⚠️ Kritiska EF-grindsvaror och fall
1. ändra spårning av minnesmängder
Problemet:
// ❌ DANGER: This can cause memory leaks!
public class PostCache
{
private readonly BlogDbContext _context;
private List<BlogPost> _cachedPosts;
public PostCache(BlogDbContext context)
{
_context = context;
}
public async Task LoadCacheAsync()
{
// These entities are now tracked by the context
_cachedPosts = await _context.BlogPosts.ToListAsync();
// The DbContext holds references to these entities FOREVER
// They can never be garbage collected while the context lives!
}
}
Varför är det problem?
- De spårade varelserna förblir i minnet under livstid
DbContext - Den ändrade spåraren upprätthåller referenser, för att förhindra skräpsamlagning
- lång tidiga-livade sammanhang | ( | e | . | g | МSK3 | enton | ) | = | minnesläcka
- I ASP är kontexten standardmässigt skalad (good!)
- Men om du kastar spårade enheter, så är du i brång
Lösningen:
public async Task LoadCacheAsync()
{
// ✅ Use AsNoTracking() for read-only queries
_cachedPosts = await _context.BlogPosts
.AsNoTracking()
.ToListAsync();
// Or detach entities after loading
var posts = await _context.BlogPosts.ToListAsync();
foreach (var post in posts)
{
_context.Entry(post).State = EntityState.Detached;
}
_cachedPosts = posts;
}
2. Proxy generering och lat laddande risker
🚨 CRITICAL WARNING : Använd inte EF CORE PROXIES IF YOU CACHE ENTITIES
Låga laddande försedlar + kassering = GUARANTERED MEMORY LEAK
Om du kastar DbContext-instantioner eller kastar varelser som lades med hjälp av aktiverade proxyer WILL lek minnet. Proxymekanismen behåller referenser till DbKontexten , för att förhindra skräpsamlagning . Det är ett av de vanligaste och farligaste misstag i EF Core-applikationerna
tumregler: Ännu alltid inkludera samlingar explicit med
.Include(). Använd endast proxyer om du förstår fullkomligt vad som är på spel och aldrig någonsin
Problemet 1: N+1 Rädslorna i fråga
// ❌ Enable lazy loading
optionsBuilder
.UseNpgsql(connectionString)
.UseLazyLoadingProxies(); // Convenient but dangerous!
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public virtual Category Category { get; set; } // Virtual = proxy
public virtual List<Comment> Comments { get; set; }
}
// Somewhere in your code
var posts = await _context.BlogPosts.ToListAsync();
foreach (var post in posts)
{
Console.WriteLine(post.Category.Name); // N+1 query here!
Console.WriteLine(post.Comments.Count); // Another N+1 query!
}
Vad händer?
- Den första frågan laddar alla meddelanden
- För varenda post, tillgång
Categorylöser en database-fråga - För varenda post, tillgång
Commentstriggar en annan fråga - Om du har poster 100 201 frågor!
Problemet 2: Proxy + Caching = Memory Leak
// ❌ CATASTROPHIC: Lazy loading proxies + caching
public class BlogPostCache
{
private static List<BlogPost> _cachedPosts;
private readonly BlogDbContext _context;
public BlogPostCache()
{
var optionsBuilder = new DbContextOptionsBuilder<BlogDbContext>();
optionsBuilder
.UseNpgsql(connectionString)
.UseLazyLoadingProxies(); // ⚠️ DANGER!
_context = new BlogDbContext(optionsBuilder.Options);
}
public async Task<List<BlogPost>> GetCachedPostsAsync()
{
if (_cachedPosts == null)
{
// ❌ These proxy entities hold references to _context
_cachedPosts = await _context.BlogPosts.ToListAsync();
}
return _cachedPosts;
}
}
Varför är detta katastrofellt?
- Proxyenheter håller en referens till deras
DbContext - Den
DbContextunderhåller en referens till alla spårade enheter - Din kas hindrar nu hela objektgrafen från att bli skräp
- Varje gång du går in på en navigation property, så kan det utlösa sökningar med hjälp av gammalt, kaserad kontext
- minnet växer obegränsat när du laddar mer data
- Du' kommer slutligen att bli slutgiltig med minne eller avloppskopplingspooler
Lösningen : Ska vara tydlig
// ✅ NEVER use lazy loading proxies - always be explicit
optionsBuilder
.UseNpgsql(connectionString);
// NO .UseLazyLoadingProxies()!
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public Category Category { get; set; } // NOT virtual
public List<Comment> Comments { get; set; } // NOT virtual
}
// ✅ Explicit eager loading - you control what's loaded
var posts = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.ToListAsync();
// ✅ Or use split queries for better performance
var posts = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.AsSplitQuery()
.ToListAsync();
// ✅ Or use projection to DTOs (best for caching)
var posts = await _context.BlogPosts
.Select(p => new PostDto
{
Title = p.Title,
CategoryName = p.Category.Name,
CommentCount = p.Comments.Count
})
.ToListAsync();
// ✅ If you MUST cache, use AsNoTracking and no proxies
public class SafeBlogPostCache
{
private static List<BlogPost> _cachedPosts;
private readonly IDbContextFactory<BlogDbContext> _contextFactory;
public async Task<List<BlogPost>> GetCachedPostsAsync()
{
if (_cachedPosts == null)
{
using var context = await _contextFactory.CreateDbContextAsync();
_cachedPosts = await context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.AsNoTracking() // Critical for caching!
.ToListAsync();
}
return _cachedPosts;
}
}
När försedelser kan vara accepterabla (Förstå vad man kan välja
Låga laddar kan bara accepteras när:
- ✅ Du har kort-liv avgränsade kontexter (e.g ., per HTTP-fråga
- ✅ Du aldrig kas-enheter
- ✅ Du 'var okej med N+1 sökresultat
- ✅ Du ' är prototyp och optimerar senare
- ✅ ditt team förstår fullkomligt konsekvenserna
Men även då Include() är nästan alltid den bättre valet eftersom
- Det gör datan laddad explicitt och uppenbart
- It's lättare att optimera ( du kan se vad
- Det förhindrar oavsiktliga N+1 frågor
- Det fungerar korrekt med kassering och långa-liva kontexter
- Det är tillämpningssätt av EF Core-teamet
3. DbKontext livstidsproblem
För mer information om att hantera DbContext livstid i produktion, se min artikel på EF- Migrationer på rätt väg.
Problemet:
// ❌ NEVER do this - singleton DbContext
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<BlogDbContext>(); // WRONG!
}
// ❌ Also wrong - storing context in static field
public static class DataAccess
{
private static BlogDbContext _context = new BlogDbContext();
public static async Task<BlogPost> GetPostAsync(int id)
{
return await _context.BlogPosts.FindAsync(id);
}
}
Varför är det fel?
DbContextär inte thread-säkra- Samma requester kommer att orsaka datakorruption
- Veranderaren växer oändligt
- Anslutningspool utplåning
- Stanna data från kas
Lösningen:
// ✅ Use scoped lifetime (default in ASP.NET Core)
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BlogDbContext>(options =>
options.UseNpgsql(connectionString));
}
// ✅ Or use DbContext factory for background services
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<BlogDbContext>(options =>
options.UseNpgsql(connectionString));
}
public class BlogBackgroundService
{
private readonly IDbContextFactory<BlogDbContext> _contextFactory;
public async Task ProcessPostsAsync()
{
// Create a new context for this operation
using var context = await _contextFactory.CreateDbContextAsync();
var posts = await context.BlogPosts.ToListAsync();
// Process posts...
}
}
4. Oavsiktligt Inbegriper Navigation Properties
Problemet:
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public List<Comment> Comments { get; set; }
}
// You query one post...
var post = await _context.BlogPosts.FirstAsync();
// Add a new comment
var newComment = new Comment { Content = "Great post!" };
post.Comments.Add(newComment);
await _context.SaveChangesAsync();
// ❌ EF Core saves the comment, BUT...
// If Comments wasn't loaded, you just lost all existing comments!
// The collection is empty, so EF thinks there are no other comments
Lösningen:
// ✅ Always load navigation properties before modifying
var post = await _context.BlogPosts
.Include(p => p.Comments)
.FirstAsync(p => p.Id == postId);
post.Comments.Add(newComment);
await _context.SaveChangesAsync();
// Or add directly to the DbSet
_context.Comments.Add(new Comment
{
BlogPostId = postId,
Content = "Great post!"
});
await _context.SaveChangesAsync();
5. Async mot Sync Mixing
Problemet:
// ❌ Mixing sync and async - deadlock risk!
public async Task<BlogPost> GetPostAsync(int id)
{
var post = _context.BlogPosts
.Where(p => p.Id == id)
.FirstOrDefault(); // Sync method in async context!
return post;
}
// ❌ Even worse - blocking async code
public BlogPost GetPost(int id)
{
return _context.BlogPosts
.FirstOrDefaultAsync(p => p.Id == id)
.Result; // DEADLOCK RISK!
}
Lösningen:
// ✅ Use async all the way
public async Task<BlogPost> GetPostAsync(int id)
{
return await _context.BlogPosts
.FirstOrDefaultAsync(p => p.Id == id);
}
// ✅ Or use sync all the way (not recommended for ASP.NET Core)
public BlogPost GetPost(int id)
{
return _context.BlogPosts
.FirstOrDefault(p => p.Id == id);
}
EF: s kärna 10: Vad är det som är nytt och brytande
Med EF Core 10 som släppts tillsammans med .NET | 10, | finns det flera viktiga ändringar som man måste vara medveten om när man uppgraderar Utbrott av förändringar i EF Core 10.
Runtime krav
EF Core 10 kräver .NET 10. Den kommer inte att fungera på | . | NET | 8, | МSK3 | Net |9, | eller | net10.0 före uppgraderingen.
Fråga-Störande förändringar
1. Parameteriserad Kollektionsöversättning (Default ändrad)
EF Core 10 ändrar hur Contains() med in-memory collections är översatt till SQL. Förresten , EF Core användes OpenJson() (SQL Server ) eller liknande. Nu är det standardmässigt så att parameterraster som ger bättre queryplan caching.
Intryck: Du kan se olika SQL genererade för queryer som
var ids = new List<int> { 1, 2, 3, 4, 5 };
var posts = await _context.BlogPosts
.Where(p => ids.Contains(p.Id))
.ToListAsync();
EF Core 8/9 (OpenJsonM SK2
SELECT b."Id", b."Title"
FROM "BlogPosts" AS b
WHERE b."Id" IN (SELECT value FROM OPENJSON(@__ids_0))
EF Core 10 (Parameter Arrays):
SELECT b."Id", b."Title"
FROM "BlogPosts" AS b
WHERE b."Id" = ANY(@__ids_0) -- PostgreSQL
-- Or: WHERE b."Id" IN (@__ids_0_0, @__ids_0_1, @__ids_0_2, ...) -- SQL Server
Om du upplever utvecklingsregressioner, återvänder till det gamla beteendet
// SQL Server
optionsBuilder.UseSqlServer(connectionString,
o => o.UseParameterizedCollectionMode(ParameterTranslationMode.Constant));
// PostgreSQL - generally parameter arrays work well, but you can opt out if needed
2. ExecuteUpdateAsync Signature Change
Den ExecuteUpdateAsync handtekening har ändrats till att stödja icke-expression lambdas . Detta är mer flexibelt men bryter koden som bygger uttrycksträd programmerat:
Gamla sätt (EF Core 7-9):
// This still works
await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.IsArchived, true));
Ny i EF: s kärna 10 - ickeM SK2expression lambdas:
// Now you can include custom logic!
await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.ExecuteUpdateAsync(setters =>
{
setters.SetProperty(p => p.IsArchived, true);
setters.SetProperty(p => p.UpdatedAt, DateTime.UtcNow);
// Can now include conditional logic, loops, etc.
});
3. Komplextyp Spalva Namning
EF Core 10 ändrar hur nesterade komplexa columner är döpta för att förhindra datakorruption
EF Core 9:
NestedComplex_Property
EF Core 10:
OuterComplex_NestedComplex_Property
Migrationseffekter: Om du har existerande tabeller med komplexa typer
modelBuilder.Entity<Order>()
.ComplexProperty(o => o.ShippingAddress)
.Property(a => a.Street)
.HasColumnName("ShippingAddress_Street"); // Explicit name
SQL Server / Azure SQL Spesifika förändringar
JSON Datatyp Standard
För Azure SQL Database eller SQL Server 2025 | ( | Kompatibilitetsnivån ♫ 170+), | EF Core ♫ JSON datatyp istället för NVARCHAR(MAX).
Att välja (om du behöver bakåtkompatibilitet):
optionsBuilder.UseAzureSql(connectionString,
o => o.UseCompatibilityLevel(160)); // Use old NVARCHAR behavior
Uppgradering av kontrolllistan
När man uppgraderar från EF Core 8/9 till EF Core
- ✅ uppdatera målstrukturen till
net10.0 - ✅ uppdatera alla
Microsoft.EntityFrameworkCore.*paket till 10.x - ✅ Aktualisering
Npgsql.EntityFrameworkCore.PostgreSQLtill 10.x - ✅ Omvärdera sökningar genom
Contains()med samlingar för prestansförändringar - ✅ Testa vilken kod som programmerat bygger
ExecuteUpdateAsyncuttryck - ✅ Kolla komplexa typers kolumnerna om man använder nesterade komplexa gener
- ✅ Försök användandet av JSON kolumner på SQL Server om du använder Azure SQL/SQL Server 2025
Vad är det som är nytt?
- förbättrad LINQ-översättning: Bättre SQL-generering för komplexa frågor
- icke-expression lambdas i ExecuteUpdateAsync: Mer flexibilitet när det gäller storskaffade versioner
- bättre handling av parametrar: förbättrad query-planskapande
- Förstärkt support för JSON: Infödda JSON-typ på SQL Server 2025
- Performance förbättringar: snabbare materialisering och ändrad spårning
Förmågor och egenskaper
- Försöksresultat: 20-50% överhead jämfört med Dapper för enkla queryer
- minnesanvändning: Högare på grund av ändrad spårning och proxy generering
- Den första fråga: långsamt (sökningskompilation och kakning
- Efterföljande sökningar: snabbare på grund av kompilerad query-キャッシュ
- Inserar/Updates: Automatisk förändringsspårning lägger till överhead
- Operationer i stor skala: dåliga resultat med standardmetoder ( överväg EFCore.BulkExtensioner eller ExecuteUpdate/ExecuteDelete i EF Core 7+)
De bästa metoderna för EF Core
Andra generallinjer
- Använd AsNoTracking() för read- endast frågor för att minska minnesöverskott
- undvika N+1 frågor - användande
Include()eller fördela sökningar ordentligt - Använd sammanlagda frågor för upprepade sökmönster
- Tänk på AsSplitQuery() för komplex inkluderar att undvika kartesiska produkter
- Använd slagning för flera wstawningar/aktualiseringar
- Projekt till DTOs tidigt för att minska dataöverföring och minnesanvändning
- Leverage ExecuteUpdate/ExecuteDelete (EF Core 7+) för storskaftoperationer
- Ännulogga och överväg genererade SQL under utveckling
När man arbetar med PostgreSQL
- Använd fulla-textsökning egenskaper (min guide) istället för
LIKEfrågor - Leveragera JSONB- kolumner för flexibelt schema-lös data
- Använd radsorter för insamlingar inom enheten
- Konfigurera kopplingspoolning ordentligt för din arbetslast
- Indeksera din
tsvectorkolumner med GIN-index - Använda omvangstyper för datums-/-timmar
Kom upp i del 2
I nästa artikel, ,, utforskar vi
- Damper: Mikron
- Rout ADO.NET/Npgsql: Maximalna effekt och kontroll
- Objektkarteringsbibliotek: Mapster mot AutoMapper
- Hybrida förhållningssätt: Kombination av EF Core och Dapper
- Leistungsindikatorer: Verkliga comparisoner
- Bestämmande matrix: Välja det rätta verktyget för ditt scenario
Referenser och ytterligare svar
- Enhetens ramverkets kärndokumentation
- Npgsql-enhetens ram som är central leverantör
- EF: s grundresultat
- Vad är nytt med EF Core 10
- Utbrott av förändringar i EF: s kärna 10
- Vad är nytt med EF Core 8
- PostgreSQL Dokumentation
Related Articles on This Blog:
- Att lägga till enhetens ram för bloggposter
- EF- Migrationer på rätt väg
- Full Text-sökning med EF Core
- Modern CQRS och Event Sourcing
I delen 2, dyker vi in i Dapper , raw Npgsql , och utforskar hur man kombinerar flera tillvägagångssätt för optimal prestation och underhåll.