This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Wednesday, 03 December 2025
عند بناء تطبيقات شبكة الإنترنت، أحد أهم القرارات المعمارية التي ستتخذها هو كيفية التعامل مع الوصول إلى البيانات ورسم خرائط الأشياء. النظام الإيكولوجي لشبكة الإنترنت يقدم مجموعة متنوعة من النُهج، بدءاً من تنفيذ ORMs كامل الجودة إلى تنفيذ SQL المعدني العاري. كل نهج يأتي مع مقايضاته الخاصة من حيث الأداء، إنتاجية المطور، سلامة النوع، والقدرة على الصيانة.
في هذا الدليل الشامل المكون من قسمين، سنستكشف أكثر أنماط الوصول إلى البيانات شعبية في شبكة الإنترنت. بينما نستخدم PostgresQL مع Npgsql في أمثلةنا (بما أن هذه هي القوى هذه المدونة)، فإن المفاهيم والأنماط والمفاضلات تنطبق بالتساوي على SQL Server, MySQL, SQLite, وغيرها من قواعد بيانات العلاقات. تبقى المبادئ كما هي - فقط لهجة SQL وبعض السمات المحددة مختلفة.
الجزء الأول (هذه المادة) التركيز على إطار عمل الهيئة الأساسي، والتوليد في مجال تحديد نوعية المعلومات، والمزالق المشتركة. الجزء الثاني وستغطي هذه الشبكة Daper، وADO.NET الخام، ومكتبات رسم خرائط الأجسام، والنهج الهجينة.
إذا كنت مهتما في تنفيذات EF الأساسية العملية، تحقق من مقالاتي الأخرى:
ويمكن تصور المناظر الطبيعية للوصول إلى البيانات على شبكة الإنترنت على أنها طيف:
Full Abstraction Full Control
↓ ↓
[EF Core] → [EF Core Raw SQL] → [Dapper] → [Npgsql ADO.NET]
بينما تنتقل من اليسار إلى اليمين، تحصل على الأداء والسيطرة لكن تفقد الملاءمة والمميزات التلقائية. دعونا نفحص كل نهج بالتفصيل.
هنا مقارنة بصرية لكيفية تعامل كل نهج مع إستفسار نموذجي:
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
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
إطار عمل الأمم المتحدة هو رئيسي مايكروسوفت OORM، يوفر خلاصة كاملة لقاعدة بياناتك. يدعم PostgresQL من خلال Npgsql. (أ) مقدّماً من مقدّم الخدمات.
للاطلاع على إرشادات عملية بشأن إنشاء مرفق EF الأساسي في مشروعك، انظر مقالتي عن إطار الهيئة المُضافة للوظائف.
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 أيضاً استفسارات SQL الخام عندما تحتاج إلى مزيد من الرقابة:
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;
}
● استخدام أساس EF متى:
● تجنب تَوَجِّد التمويل:
واحد من أهم جوانب استخدام EF enor بفعالية هو فهم ما تولده SQL. EF coor تحسن بشكل كبير توليد SQL على مر السنين، ولكن من المهم التحقق من الاستفسارات المرسلة إلى PastgreSQL.
// 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();
}
}
C# plend / /
var recentPosts = await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.OrderByDescending(p => p.PublishedDate)
.Take(10)
.ToListAsync();
SQLهاء واو- العنصر 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
لاحظ كيف يولّد EF CORE 8+ SQL نظيفاً وكفؤاً مع البارامترات الصحيحة. الموارد الأساسية 10 ولا يزال هذا الاتجاه قائماً مع مزيد من التحسينات.
رمزC# رمز:
var posts = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.ToListAsync();
SQL القديم (EF نواة 3-1 - انفجار كارتيسيان):
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"
هذا يُنشئ المنتج الكارتيسي - إذا كان للعمدة 10 تعليقات، هذا الصف هو تكرار 10 مرات!
مشفرة مع س س س س س س س س س س س
var posts = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Comments)
.AsSplitQuery() // ← This is the key!
.ToListAsync();
مُجِين 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"
وهذا يزيل المنتج الكارتستاني وكثيراً ما أكثر من ذلك بكثير للجمعيات!
رمزC# رمز:
var posts = await _context.BlogPosts
.Include(p => p.Comments.Where(c => c.IsApproved))
.ToListAsync();
الدالة 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"
رمزC# رمز:
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();
الدالة SQL:
SELECT b."Id", b."Title", b."Metadata"
FROM "BlogPosts" AS b
WHERE b."Metadata" ->> 'IsFeatured' = 'true'
EF care 7+ يمكن ترجمة ممتلكات JOSON الوصول إلى المشغلين PostgreSQL JOSON!
الطريق القديم (لا يتسم بالكفاءة):
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!
طريقة جديدة (المصدر 7+):
await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.IsArchived, true));
الـ مُستخدَم SQL (Single QL)
UPDATE "BlogPosts" AS b
SET "IsArchived" = TRUE
WHERE b."CategoryId" = 5
هذا هو a )أجــر - بيان واحد من SQL بدلا من N!
الطريقة القديمة:
var oldPosts = await _context.BlogPosts
.Where(p => p.PublishedDate < DateTime.UtcNow.AddYears(-5))
.ToListAsync();
_context.BlogPosts.RemoveRange(oldPosts);
await _context.SaveChangesAsync(); // N DELETE statements
طريقة جديدة:
await _context.BlogPosts
.Where(p => p.PublishedDate < DateTime.UtcNow.AddYears(-5))
.ExecuteDeleteAsync();
الدالة SQL:
DELETE FROM "BlogPosts" AS b
WHERE b."PublishedDate" < @__p_0
رمزC# رمز:
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();
SQL (EF الأساسي 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"
لغوص أعمق في بحث النص الكامل، انظر مقالتي عن تنفيذ بحث كامل النص مع EF.
رمزC# رمز:
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();
الدالة 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
المشكلة:
// ❌ 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!
}
}
لماذا هي مشكلة:
DbContextالحل:
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;
}
لا تستخدم نماذج أساسية إضافية إذا كنت تؤلف كيانات
كظظي التحميل وكيلات المُعاملات المُعَظِف + كش = الميخ المكـان المكـان الميـن الميـك
إذا كنت مخبئاً لـ DbbCCon خصائص/ أو كائنات مخبئة مع مُمكّن سُجُل وتحتفظ آلية الوكيل بإشارات إلى سياق DbConcil، مما يحول دون جمع القمامة، وهو واحد من أكثر الأخطاء شيوعاً وخطورة في تطبيقات EF الأساسية.
(أ) قاعدة التحكم في الإبهامدائماً: إدراج المجموعات بشكل صريح مع:
.Include()فقط استخدام وكلاء إذا كنت تفهم تماما المقايضات... ... و أبدا، أي وقت مضى إخفاء كيانات الوكيل.
المشكلة: الـ N+ N+ 1 QQ QQQ QQQ
// ❌ 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!
}
ما يحدث:
CategoryCommentsالمشكلة 2: المُسْكِر + الكَشْح = الذاكرة
// ❌ 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;
}
}
لماذا هذا كارثي:
DbContextDbContext يُحتفظ بالإشارة إلى جميع الكيانات المُتَرَكَّبَةالحل : كن صريحا
// ✅ 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;
}
}
عندما يمكن قبول التوكيلات (افهم المفاضلات):
يمكن قبول وكيلات تحميل لازي يمكن أن تكون مقبولة عندما:
ولكن حتى في ذلك الوقت، صراحة Include() هو الخيار الأفضل دائماً تقريباً لأن:
للحصول على مزيد من التفاصيل عن إدارة DbContus عمر الإنتاج، انظر مقالتي عن المهجرات السبيل الصحيح.
المشكلة:
// ❌ 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);
}
}
لماذا هو خطأ:
DbContext هو ** لا يمكن أن يكون مأموناً**الحل:
// ✅ 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...
}
}
المشكلة:
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
الحل:
// ✅ 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();
المشكلة:
// ❌ 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!
}
الحل:
// ✅ 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);
}
مع مع مع مع الموارد الأساسية 10 هناك العديد من التغييرات الهامة التي يجب أن تكون على علم بها عند رفع المستوى. وللاطلاع على القائمة الكاملة، انظر: التغييرات الناشئة في الصندوق التكميلي 10.
الموارد الأساسية الأساسية 10 تتطلب الصافي 10هذا هو أهم تغيير - ضمان أهداف مشروعك. net10.0 قبل رفع المستوى.
هاء واو - التغييرات 10 في كيفية Contains() تم ترجمة المجموعات في الذاكرة إلى SQL. سابقاً، EF Cour مُستخدَم OpenJson() (SQL خادم) أو ما يشبهها. الآن أنها ترجع إلى الدالة التي توفر خطة أفضل للاستفسار.
أولاً - مقدمةيمكنك أن ترى مختلف SQL توليد للاستفسارات مثل:
var ids = new List<int> { 1, 2, 3, 4, 5 };
var posts = await _context.BlogPosts
.Where(p => ids.Contains(p.Id))
.ToListAsync();
EF الأساسي 8/9 (الحلقة:
SELECT b."Id", b."Title"
FROM "BlogPosts" AS b
WHERE b."Id" IN (SELECT value FROM OPENJSON(@__ids_0))
الموارد الأساسية 10 (أشعة البارامترات):
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
إذا كنت تجري تراجعات أداء الأداءالعودة إلى السلوك القديم:
// SQL Server
optionsBuilder.UseSqlServer(connectionString,
o => o.UseParameterizedCollectionMode(ParameterTranslationMode.Constant));
// PostgreSQL - generally parameter arrays work well, but you can opt out if needed
الـ ExecuteUpdateAsync هذا أكثر مرونة ولكن الشفرة التي بنيت إعراب الأشجار برنامجياً:
الطريق القديم (المصدر 7-9:
// This still works
await _context.BlogPosts
.Where(p => p.CategoryId == 5)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.IsArchived, true));
جديد بوصة EF أساسي 10 لامداس غير معرّف:
// 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.
});
تغييرات EF الأساسي 10 في كيفية تسمية أعمدة من النوع المعقد المعقّد لمنع الفساد في البيانات:
EF الأساسي 9:
NestedComplex_Property
الفئة الأساسية 10:
OuterComplex_NestedComplex_Property
التأثير المترتب على الهجرةإذا كان لديك جداول قائمة مع أنواع معقدة، قد تحتاج إلى إعادة تسمية أعمدة أو تشكيل أسماء الأعمدة الواضحة:
modelBuilder.Entity<Order>()
.ComplexProperty(o => o.ShippingAddress)
.Property(a => a.Street)
.HasColumnName("ShippingAddress_Street"); // Explicit name
لقاعدة بيانات Azure SQL أو SQL خادم 2025 (مستوى التوافق 170+)، EF الأساسي 10 افتراضي إلى الموطن الجديد JSON نوع بيانات البيانات NVARCHAR(MAX).
(أ) اختيار الاختيار (إذا كنت بحاجة إلى التوافق):
optionsBuilder.UseAzureSql(connectionString,
o => o.UseCompatibilityLevel(160)); // Use old NVARCHAR behavior
(أ) عندما تُرفَّع من المستوى الأساسي 8/9 إلى المستوى الأساسي 10:
net10.0Microsoft.EntityFrameworkCore.* إلى 10xNpgsql.EntityFrameworkCore.PostgreSQL إلى ٠١Contains() مُجمعات من أجل التغييرات في الأداءExecuteUpdateAsync تعابيرInclude() أو أوLIKE ما يلـي:tsvector ** بمؤشرات GINفي المقالة التالية، سنستكشف:
مواد ذات صلة في هذا الكتاب:
في الجزء 2، سنغوص في Daper، Npgsql الخام، ونستكشف كيفية الجمع بين النُهج المتعددة للأداء الأمثل والصيانة.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.