(الجزء 1 - إطار عمل الكيانين الأساسي) (العربية (Arabic))

(الجزء 1 - إطار عمل الكيانين الأساسي)

Wednesday, 03 December 2025

//

20 minute read

عند بناء تطبيقات شبكة الإنترنت، أحد أهم القرارات المعمارية التي ستتخذها هو كيفية التعامل مع الوصول إلى البيانات ورسم خرائط الأشياء. النظام الإيكولوجي لشبكة الإنترنت يقدم مجموعة متنوعة من النُهج، بدءاً من تنفيذ 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 الأساسي في مشروعك، انظر مقالتي عن إطار الهيئة المُضافة للوظائف.

ك الرئيسية

  • & تغيير&:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  • ****: إدارة المدونة الأولى للمخطط ومراقبة النسخ (انظر: المهجرات السبيل الصحيح)
  • مقدِّم SLINQمعلومات أساسية: استفسارات عن الأنواع المأمونة باستخدام تركيبات لغوية من طراز C(#)
  • كزز/ كندر التحميل:: استراتيجيات التحميل المرن للكيانات ذات الصلة
  • الميزات: البحث عن النص الكامل(أ) انظر المادة (أ))، وJUSON أعمدة، ومصفوفات، وأنواع النطاقات
  • المعترضات والتظاهراتالنقاط المرجعية للشواغل الشاملة لعدة قطاعات

مثال: مادة أساسية مع 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 الأساسية مع الخام SQL

يدعم 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 متى:

  • بناء تطبيق جديد مع متطلبات مخطط متطور
  • تحتاج إلى دعم قوي لكتابة و تجميع الوقت إعلام إعلام إستفسار إستفسار
  • والهجرات والنسخ الوصفية مهمة (انظر: دليل الهجرة)
  • فريقك يفضل العمل مع كائنات على SQL
  • أنت تستخدم نماذج معقدة في مجال معقد مع علاقات
  • سرعة التنمية أكثر أهمية من الأداء الخام
  • تحتاج إلى تقاطع بيانات قاعدة البيانات القابلة للنقل (بالرغم من أن الميزات المحددة في PostgreSQL تحبسك في)

● تجنب تَوَجِّد التمويل:

  • الأداء الأقصى هو أداء حاسم الأهمية (المؤشرات القياسية لأسعار الإنتاج العالية الجودة، والتجهيز بالدفعات)
  • لديك مُعقد، مُطَرّف باليد إستفسارات SQLS SQL
  • لا تُرسِل بشكل جيد إلى كائن
  • تحتاج إلى تحكم دقيق في كل بيان SQL
  • يشكل استخدام الذاكرة قيدا حرجا (نفقات عامة لتعقب التغيير)
  • أنتِ تعملين مع إرثكِ الذي لا يرسم خرائطاً للاتفاقيات

EF الأساسي SQL جيل: فهم ما يتم تنفيذه

واحد من أهم جوانب استخدام 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 ولا يزال هذا الاتجاه قائماً مع مزيد من التحسينات.

مثال: الانضمام إلى عملية الإدماج (قبل المرحلة الأساسية من التمويل المعزز 5)

رمز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 مرات!

مثال: طلبات التماسات (الجماعة الأوروبية: 5+)

مشفرة مع س س س س س س س س س س س

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"

وهذا يزيل المنتج الكارتستاني وكثيراً ما أكثر من ذلك بكثير للجمعيات!

مثال: إدراج مرشَّح (EFKRE 5+)

رمز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"

مثال على ذلك: فريق الاتصال التابع للجنة الأمم المتحدة للقانون التجاري الدولي (المصدر 7+)

رمز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!

مثال على ذلك: تحديث بالسّك (EF الأساسي 7+ التنفيذ المستكمل)

الطريق القديم (لا يتسم بالكفاءة):

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!

مثال: السُّك يُحذف (EF COR 7+)

الطريقة القديمة:

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"

المُنزِلSSSQL كامل

لغوص أعمق في بحث النص الكامل، انظر مقالتي عن تنفيذ بحث كامل النص مع 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

الإنذارات الأساسية والزلالات

1 - التغيير في التتبع

المشكلة:

// ❌ 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
  • يُبقي التعقب التغيير على المراجع، ويمنع جمع القمامة
  • السياقات الطويلة العمر الطويلة (مثلاً، النغمات) = تسرب الذاكرة
  • في قاعدة ASP.net الأساسية، يتم تحديد نطاق السياق بالتقصير (السلع!)
  • ولكن إذا كنت تخبئ الكيانات المُراقبة، فأنت في ورطة

الحل:

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 - توليد وحمل المخاطر

لا تستخدم نماذج أساسية إضافية إذا كنت تؤلف كيانات

كظظي التحميل وكيلات المُعاملات المُعَظِف + كش = الميخ المكـان المكـان الميـن الميـك

إذا كنت مخبئاً لـ 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!
}

ما يحدث:

  1. المجلد الأول
  2. (أ) كل وظيفة واحدة لكل وظيفة، الوصول إلى Category
  3. (أ) كل وظيفة واحدة لكل وظيفة، الوصول إلى Comments
  4. إذا كان لديك 100 وظيفة، كنت مجرد تنفيذ ** 20 20 20 20 20**!

المشكلة 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;
    }
}

لماذا هذا كارثي:

  • تحتفظ الكيانات التي تُعنى بالتسويات بإشارة إلى ما يلي: DbContext
  • الـ DbContext يُحتفظ بالإشارة إلى جميع الكيانات المُتَرَكَّبَة
  • مُخبأك الآن يمنع كامل كائن الرسم البياني من جمع القمامة
  • كلّ مرّة تدخل فيها على خاصّية مُلاحة، قد يسبّب ذلك إستفسارات باستخدام سياقات مخبئة
  • لا يُوحز الذاكرة عند تحميل المزيد من البيانات
  • في نهاية المطاف ستنفد منك الذاكرة أو أحواض الربط

الحل : كن صريحا

// ✅ 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;
    }
}

عندما يمكن قبول التوكيلات (افهم المفاضلات):

يمكن قبول وكيلات تحميل لازي يمكن أن تكون مقبولة عندما:

  1. هل لديك طراز SS (على سبيل المثال، وفقاً لطلب HTTTP)
  2. انت لا يمكن أبداً خصائص المخبأ
  3. ^ أنت موافق مع N+ 1 طلب أداء
  4. "أنتِ مُخطّطة وسوف تُحسنين لاحقًا"
  5. فريقك يفهم تماماً الآثار

ولكن حتى في ذلك الوقت، صراحة Include() هو الخيار الأفضل دائماً تقريباً لأن:

  • إنه يجعل تحميل البيانات (العلنية والعلنية والعلنية)
  • إنه إلى: (يمكنك أن ترى ما يجري تحميل)
  • هو: يحول دون حدوث استفسارات عارضة N+1
  • وهي تعمل بشكل صحيح مع سياقات التكسير والسياقات الطويلة الأجل
  • انها نهج موصى به من قبل الفريق الأساسي

3 - المسائل المتعلقة بالسياق مدى الحياة

للحصول على مزيد من التفاصيل عن إدارة 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...
    }
}

4 - الفئات غير المقصودة في مجال الملاحة

المشكلة:

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

Async vs مزام مزام من

المشكلة:

// ❌ 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);
}

EF الأساسي 10: ما هو الجديد والمتغيرات الكسرية

مع مع مع مع الموارد الأساسية 10 هناك العديد من التغييرات الهامة التي يجب أن تكون على علم بها عند رفع المستوى. وللاطلاع على القائمة الكاملة، انظر: التغييرات الناشئة في الصندوق التكميلي 10.

الوقت الذي لا يُنفَّر فيه

الموارد الأساسية الأساسية 10 تتطلب الصافي 10هذا هو أهم تغيير - ضمان أهداف مشروعك. net10.0 قبل رفع المستوى.

تغييرات محدثات الاستعلامات من السؤال عن السؤال

Default Danged) (المتغيرة سابقاً)

هاء واو - التغييرات 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

2 - تنفيذ التنفيذ الاستكمال

الـ 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.
    });

3 - نوع النوع

تغييرات 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

SQL خادم / اززززحSQL خصائص تغييرات

لقاعدة بيانات Azure SQL أو SQL خادم 2025 (مستوى التوافق 170+)، EF الأساسي 10 افتراضي إلى الموطن الجديد JSON نوع بيانات البيانات NVARCHAR(MAX).

(أ) اختيار الاختيار (إذا كنت بحاجة إلى التوافق):

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

قائمة مرجعية

(أ) عندما تُرفَّع من المستوى الأساسي 8/9 إلى المستوى الأساسي 10:

  1. الإطار المستهدف المحدَّث net10.0
  2. معلومات مستوفاة عن جميع Microsoft.EntityFrameworkCore.* إلى 10x
  3. معلومات محدثة Npgsql.EntityFrameworkCore.PostgreSQL إلى ٠١
  4. • استعراض الاستفسارات التي Contains() مُجمعات من أجل التغييرات في الأداء
  5. اختبار أي مدونة تبنى برنامجياً ExecuteUpdateAsync تعابير
  6. (ي) كِش كِش كِش كِش مُعقد مُعَقّد كِش كِش أنواع الأعمود إذا كان استخدام أنواع معقدة مُعَثة
  7. استعراض استخدام عمود عمود SQL الخادم SQL Json إذا استخدم Azure SQL/SQL Server 2025

ما الجديد (مصابيح)

  • تحسين الترجمة التحريرية LINSQ:: جيل أفضل من طراز SQL للاستفسارات المعقدة
  • لاماندا في تنفيذ تنفيذ التحديث Assyncزيادة المرونة في الاستكمالات بالجملة
  • مناولة أفضل: تحسين إعداد خطة الاستفسار
  • الدعم المُعزَّز الذي يُعزَز: السكان الأصليون JJSON نوع على SQL خادم 2525
  • التحسينات (أ):: ما يلي:

(أ)

  • & طلب المستجد من الأداء20-5 في المائة من النفقات العامة مقارنة بـ Daper للاستفسارات البسيطة
  • ****: يُعزى أعلى إلى تتبع التغيير وتوليد الوكيل
  • الأمر المُناقشبطء (تجميـع المكـرات وتكويـهـة)
  • المستجداتأقرب إلى مُجمّد مجلد
  • المُدخلات/الاستكمالات:: زيادة في التكاليف العامة:
  • مجموع: أداء رديئة مع أساليب افتراضية (نظر) المواد الكيميائية. أو التنفيذ أو الاستكمال/التنفيذ/الإلغاء التنفيذي في EF الأساسي 7+)

أفضل الممارسات في مجال

مبادئ توجيهية عامة

  1. **** للاستفسارات المقروءة فقط من أجل تقليل حجم الذاكرة
  2. مُغفل N+ 1 استفسارات - الاستخدام - الاستخدام Include() أو أو
  3. **** لـ تكرار لـ استفسار
  4. النظر في كِئز QQQ بما في ذلك لتجنّب المنتجات الكارتيزية
  5. دعم إدراجات/تحديثات متعددة
  6. من المشروع إلى الملحقات خفض نقل البيانات واستخدام الذاكرة
  7. النفـج المنفـف/المُنفِـد المُنفِـد المُنفِـد المُنفِـد المُنفِـد (المصدر الأساسي 7++) للعمليات السائبة
  8. & استعراض و استعراض في التنمية

عند العمل مع PostgresSQL

  1. **** (العلامات)دليلي) بدلاً من LIKE ما يلـي:
  2. سِرْس JSONSB أعمدة البيانات المرنة التي لا يمكن إدراجها في بيانات
  3. **** للجمعيات داخل الكيانات
  4. **** ملائمة لعبء العمل الخاص بك
  5. **مؤشر مؤشرك tsvector ** بمؤشرات GIN
  6. أنواع الاستخدام للتاريخ/المدىات الزمنية

الخروج في الجزء الثاني من الجزء 2

في المقالة التالية، سنستكشف:

  • مُنْظَر♪ بقعة حلوّة صغيرة جداً ♪
  • ADO.net/Npgsqlالحد الأقصى من الأداء والمراقبة: الحد الأقصى
  • مكتبات الأمم المتحدةالخريطة vs Aumaper
  • النُهُج المُنْجْلُ(نمط CQRS)
  • نقاط إثبات الأداء: مقارنات العالم الحقيقي
  • مُرْمَجاختيار الأداة الصحيحة لسيناريوك

المراجع ومواصلة القراءة

مواد ذات صلة في هذا الكتاب:


في الجزء 2، سنغوص في Daper، Npgsql الخام، ونستكشف كيفية الجمع بين النُهج المتعددة للأداء الأمثل والصيانة.

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.