الوصول إلى البيانات في الشبكة العالمية: المقارنة بين المواد المستنفدة للأوزون واستراتيجيات رسم الخرائط (الجزء 2 - القمة، وراو SQQL، والنُهُج الهجينة) (العربية (Arabic))

الوصول إلى البيانات في الشبكة العالمية: المقارنة بين المواد المستنفدة للأوزون واستراتيجيات رسم الخرائط (الجزء 2 - القمة، وراو SQQL، والنُهُج الهجينة)

Wednesday, 03 December 2025

//

20 minute read

مرحباً بكم في الجزء الثاني من دليلنا الشامل للوصول إلى البيانات الجزء الأولاستكشفنا إطار الكيانين الأساسي بتعمق، بما في ذلك توليد SQL، والمزالق المشتركة، وذلك الإنذار الحرج عن العوامل غير المباشرة والتكتل.

في هذه المقالة، سنستكشف بدائل الوزن الخفيف وكيفية الجمع بين النُهج المتعددة للأداء الأمثل:

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

المحتويات (تابع)

مَجْزَر: المُجْهَرْرِر

مُنْظَر وهو يوفر طبقة رقيقة فوق ADO.NET، التي تتعامل مع العمل الممل لرسم خرائط نتائج الاستعلام إلى الأجسام بينما تعطيك السيطرة الكاملة على SQL.

لماذا لا يوجد

لقد وُلد (دابر) من حاجة (ستاك أوفرفورف) إلى الوصول إلى بيانات عالية الأداء. وجد الفريق أن أجهزة أورومز كاملة مثل إطار الكيان (قبل الكور) تضيف الكثير من النفقات العامة لسيناريوهاتها عالية الحركة. Daper يعطيك 95٪ من الملاءمة مع 5-15٪ فقط فوق ADO.NET الخام.

ك الرئيسية

  • السنة الأولى من العمرتكتب كل SQL بنفسك - التحكم الكامل
  • (أ) مـن الـمـيـع (أ مـن الـيـي): الحد الأدنى من النفقات العامة فوق ADO.net (5-15 في المائة)
  • الأمم المتحدة*: استفسارات معقدة للخرائط إلى الأنواع المتعددة ذات الصلة
  • منفذ:: الميكانيكية: تمنع المعاملات الآلية حقن SQL
  • ****:: المراقبة الكاملة للمعاملات
  • التسهيللا تشكيلات، لا تغيير في التتبع، لا سحر
  • async/ A انتظرالدعم الكامل للحداثة

اأمثلة

using Npgsql;
using Dapper;

public class DapperBlogRepository
{
    private readonly string _connectionString;

    public DapperBlogRepository(string connectionString)
    {
        _connectionString = connectionString;

        // Configure Dapper to work with PostgreSQL naming conventions
        DefaultTypeMap.MatchNamesWithUnderscores = true;
    }

    // Simple query
    public async Task<IEnumerable<BlogPost>> GetRecentPostsAsync(int count)
    {
        using var connection = new NpgsqlConnection(_connectionString);

        const string sql = @"
            SELECT id, title, content, tags, published_date, category_id
            FROM blog_posts
            ORDER BY published_date DESC
            LIMIT @Count";

        return await connection.QueryAsync<BlogPost>(sql, new { Count = count });
    }

    // Query with WHERE clause
    public async Task<BlogPost> GetPostByIdAsync(int id)
    {
        using var connection = new NpgsqlConnection(_connectionString);

        const string sql = @"
            SELECT id, title, content, published_date
            FROM blog_posts
            WHERE id = @Id";

        return await connection.QueryFirstOrDefaultAsync<BlogPost>(sql, new { Id = id });
    }

    // Insert with returning ID
    public async Task<int> CreatePostAsync(BlogPost post)
    {
        using var connection = new NpgsqlConnection(_connectionString);

        const string sql = @"
            INSERT INTO blog_posts (title, content, published_date, category_id)
            VALUES (@Title, @Content, @PublishedDate, @CategoryId)
            RETURNING id";

        return await connection.ExecuteScalarAsync<int>(sql, post);
    }

    // Update
    public async Task UpdatePostAsync(BlogPost post)
    {
        using var connection = new NpgsqlConnection(_connectionString);

        const string sql = @"
            UPDATE blog_posts
            SET title = @Title,
                content = @Content,
                published_date = @PublishedDate
            WHERE id = @Id";

        await connection.ExecuteAsync(sql, post);
    }

    // Delete
    public async Task DeletePostAsync(int id)
    {
        using var connection = new NpgsqlConnection(_connectionString);

        const string sql = "DELETE FROM blog_posts WHERE id = @Id";

        await connection.ExecuteAsync(sql, new { Id = id });
    }
}

التعدد التعددـات التـعدديـة مـن الاختيـارات: مـنـزززات المناولة

وإحدى أقوى السمات التي يتميز بها الرسم البياني هي المناولة الكفؤة لوصلات الأجسام المتعددة ذات الصلة ورسم خرائط لها:

public async Task<IEnumerable<BlogPost>> GetPostsWithCategoryAsync()
{
    using var connection = new NpgsqlConnection(_connectionString);

    const string sql = @"
        SELECT
            p.id, p.title, p.content, p.published_date,
            c.id, c.name, c.description
        FROM blog_posts p
        INNER JOIN categories c ON p.category_id = c.id
        ORDER BY p.published_date DESC";

    return await connection.QueryAsync<BlogPost, Category, BlogPost>(
        sql,
        (post, category) =>
        {
            post.Category = category;
            return post;
        },
        splitOn: "id"  // Split at the second "id" column
    );
}

// More complex: Posts with comments
public async Task<IEnumerable<BlogPost>> GetPostsWithCommentsAsync()
{
    using var connection = new NpgsqlConnection(_connectionString);

    const string sql = @"
        SELECT
            p.id, p.title, p.content,
            c.id, c.author, c.content, c.created_at
        FROM blog_posts p
        LEFT JOIN comments c ON p.id = c.blog_post_id
        ORDER BY p.published_date DESC, c.created_at";

    var postDict = new Dictionary<int, BlogPost>();

    await connection.QueryAsync<BlogPost, Comment, BlogPost>(
        sql,
        (post, comment) =>
        {
            if (!postDict.TryGetValue(post.Id, out var existingPost))
            {
                existingPost = post;
                existingPost.Comments = new List<Comment>();
                postDict.Add(post.Id, existingPost);
            }

            if (comment != null)
            {
                existingPost.Comments.Add(comment);
            }

            return existingPost;
        },
        splitOn: "id"
    );

    return postDict.Values;
}

متقدم Dador

البارامترات الديناميكية لمجمعات الأسئلة:

public async Task<IEnumerable<BlogPost>> SearchWithDynamicFiltersAsync(SearchCriteria criteria)
{
    using var connection = new NpgsqlConnection(_connectionString);

    var parameters = new DynamicParameters();
    var conditions = new List<string>();

    var sql = new StringBuilder("SELECT * FROM blog_posts");

    if (!string.IsNullOrEmpty(criteria.SearchTerm))
    {
        conditions.Add("search_vector @@ to_tsquery('english', @SearchTerm)");
        parameters.Add("SearchTerm", criteria.SearchTerm);
    }

    if (criteria.CategoryIds?.Any() == true)
    {
        conditions.Add("category_id = ANY(@CategoryIds)");
        parameters.Add("CategoryIds", criteria.CategoryIds);
    }

    if (criteria.FromDate.HasValue)
    {
        conditions.Add("published_date >= @FromDate");
        parameters.Add("FromDate", criteria.FromDate.Value);
    }

    if (criteria.Tags?.Any() == true)
    {
        conditions.Add("tags && @Tags");  // PostgreSQL array overlap
        parameters.Add("Tags", criteria.Tags);
    }

    if (conditions.Any())
    {
        sql.Append(" WHERE ");
        sql.Append(string.Join(" AND ", conditions));
    }

    sql.Append(" ORDER BY published_date DESC LIMIT @Limit");
    parameters.Add("Limit", criteria.Limit);

    return await connection.QueryAsync<BlogPost>(sql.ToString(), parameters);
}

مُستخدِم نوع مُستخدِم هذا النوع لِلمَنْز

// Handle PostgreSQL arrays
public class PostgresArrayTypeHandler : SqlMapper.TypeHandler<string[]>
{
    public override void SetValue(IDbDataParameter parameter, string[] value)
    {
        parameter.Value = value;
        ((NpgsqlParameter)parameter).NpgsqlDbType = NpgsqlDbType.Array | NpgsqlDbType.Text;
    }

    public override string[] Parse(object value)
    {
        return (string[])value;
    }
}

// Handle PostgreSQL JSONB
public class JsonTypeHandler<T> : SqlMapper.TypeHandler<T>
{
    public override void SetValue(IDbDataParameter parameter, T value)
    {
        parameter.Value = JsonSerializer.Serialize(value);
        ((NpgsqlParameter)parameter).NpgsqlDbType = NpgsqlDbType.Jsonb;
    }

    public override T Parse(object value)
    {
        return JsonSerializer.Deserialize<T>(value.ToString());
    }
}

// Register handlers (in startup)
SqlMapper.AddTypeHandler(new PostgresArrayTypeHandler());
SqlMapper.AddTypeHandler(new JsonTypeHandler<Dictionary<string, object>>());

عمليات سائبة مع PostgresSQL COPY:

public async Task BulkInsertPostsAsync(IEnumerable<BlogPost> posts)
{
    using var connection = new NpgsqlConnection(_connectionString);
    await connection.OpenAsync();

    using var writer = await connection.BeginBinaryImportAsync(
        "COPY blog_posts (title, content, tags, published_date) FROM STDIN (FORMAT BINARY)"
    );

    foreach (var post in posts)
    {
        await writer.StartRowAsync();
        await writer.WriteAsync(post.Title);
        await writer.WriteAsync(post.Content);
        await writer.WriteAsync(post.Tags, NpgsqlDbType.Array | NpgsqlDbType.Text);
        await writer.WriteAsync(post.PublishedDate);
    }

    await writer.CompleteAsync();
}

دعم المعاملات:

public async Task TransferPostToCategoryAsync(int postId, int newCategoryId)
{
    using var connection = new NpgsqlConnection(_connectionString);
    await connection.OpenAsync();

    using var transaction = await connection.BeginTransactionAsync();

    try
    {
        // Update the post
        await connection.ExecuteAsync(
            "UPDATE blog_posts SET category_id = @CategoryId WHERE id = @PostId",
            new { CategoryId = newCategoryId, PostId = postId },
            transaction
        );

        // Log the change
        await connection.ExecuteAsync(
            @"INSERT INTO category_history (post_id, category_id, changed_at)
              VALUES (@PostId, @CategoryId, @ChangedAt)",
            new { PostId = postId, CategoryId = newCategoryId, ChangedAt = DateTime.UtcNow },
            transaction
        );

        await transaction.CommitAsync();
    }
    catch
    {
        await transaction.RollbackAsync();
        throw;
    }
}

إلى وقت إلى استخدام مسند

& استخدم المُعلِم متى:

  • الأداء هو أمر حاسم لكنك لا تحتاج إلى السرعة القصوى المطلقة
  • أنت تَقْرأُ كَتْب SQL
  • لديك إستفسارات معقدة لا تُرسِل بشكل جيد إلى LINQ
  • تحتاج إلى سيطرة دقيقة على توليد SQL
  • أنت تعمل مع قاعدة بيانات قائمة
  • تريد الحد الأدنى من النفقات والمعتمدات
  • الاستخدام من دواعي القلق
  • تحتاج إلى استخدام خصائص خاصة لقاعدة بيانات البيانات بشكل كبير

مَا مَنْ يَتَجَنَّبُ مَوْمَ المَوْجِز عندما :

  • فريقك غير مرتاح مع SQL
  • تحتاج إلى تتبع تغيير تلقائي
  • تُريدُ تخطيطاً للهجرة كرمز
  • لديك مخطط سريع التطور
  • تحتاج إلى تبادل البيانات
  • أنت تفضيل لِينQ / SQL s

(أ)

  • & طلب المستجد من الأداء5-15 في المائة
  • ****الحد الأدنى - لا تغيير في تتبع مسار أو تفسيرات
  • مجموع: ممتاز مع PostgresSQL COPY
  • الأمر المُناقش: سريع - لا خطوة تجميعية
  • المطور المطور: يتطلب معرفة SQL ولكن يمكن التنبؤ به

ADO.Npgsql (Npgsql)

لـ مطلق أقصى أداء و تحكم، يمكنك استخدام Npggsql مباشرة بدون أي طبقة ORM.

دال - عندما يُعاد التصرّف على نحو يجعل

تكون الشبكة مناسبة في الحالات التالية:

  • تحتاج كل ميلي ثانية من الأداء
  • أنت تبني بيانات عالية الفعالية
  • أنت تعمل مع PostgresSQL خاصي خصائص غير مدعوم من قبل ORMs
  • تحتاج إلى التحكم الدقيق في مخصصات الذاكرة
  • أنت تقوم بعمليات ضخمة أو تتدفق مجموعات بيانات كبيرة

مثال: نقيع Npggsql

public class NpgsqlBlogRepository
{
    private readonly string _connectionString;

    public async Task<List<BlogPost>> GetRecentPostsAsync(int count)
    {
        var posts = new List<BlogPost>();

        using var connection = new NpgsqlConnection(_connectionString);
        await connection.OpenAsync();

        using var command = new NpgsqlCommand(
            "SELECT id, title, content, tags, published_date FROM blog_posts ORDER BY published_date DESC LIMIT @count",
            connection
        );

        command.Parameters.AddWithValue("count", count);

        using var reader = await command.ExecuteReaderAsync();

        while (await reader.ReadAsync())
        {
            posts.Add(new BlogPost
            {
                Id = reader.GetInt32(0),
                Title = reader.GetString(1),
                Content = reader.GetString(2),
                Tags = reader.GetFieldValue<string[]>(3),
                PublishedDate = reader.GetDateTime(4)
            });
        }

        return posts;
    }

    // Using prepared statements for repeated queries
    public async Task<BlogPost> GetPostByIdAsync(int id)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        await connection.OpenAsync();

        using var command = new NpgsqlCommand(
            "SELECT id, title, content FROM blog_posts WHERE id = $1",
            connection
        );

        command.Parameters.AddWithValue(id);
        await command.PrepareAsync(); // Prepared statement for performance

        using var reader = await command.ExecuteReaderAsync();

        if (await reader.ReadAsync())
        {
            return new BlogPost
            {
                Id = reader.GetInt32(0),
                Title = reader.GetString(1),
                Content = reader.GetString(2)
            };
        }

        return null;
    }

    // Working with PostgreSQL JSONB
    public async Task<Dictionary<string, object>> GetPostMetadataAsync(int id)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        await connection.OpenAsync();

        using var command = new NpgsqlCommand(
            "SELECT metadata FROM blog_posts WHERE id = $1",
            connection
        );

        command.Parameters.AddWithValue(id);

        var json = await command.ExecuteScalarAsync() as string;
        return JsonSerializer.Deserialize<Dictionary<string, object>>(json);
    }

    // Streaming large result sets
    public async IAsyncEnumerable<BlogPost> StreamAllPostsAsync()
    {
        using var connection = new NpgsqlConnection(_connectionString);
        await connection.OpenAsync();

        using var command = new NpgsqlCommand(
            "SELECT id, title, content FROM blog_posts ORDER BY id",
            connection
        );

        using var reader = await command.ExecuteReaderAsync();

        while (await reader.ReadAsync())
        {
            yield return new BlogPost
            {
                Id = reader.GetInt32(0),
                Title = reader.GetString(1),
                Content = reader.GetString(2)
            };
        }
    }
}

(أ)

  • & طلب المستجد من الأداء: خط الأساس:
  • ****الحد الأدنى: الحد الأدنى - السيطرة الكاملة على المخصصات
  • مجموع: ممتاز مع بروتوكول كيوتو
  • المطور المطوريطلب معظم العمل اليدوي

مكتبات الأمم المتحدة

عند العمل مع Daper أو ADO.NET الخام، غالباً ما تحتاج إلى رسم خريطة بين مختلف تمثيلات كائنات (DTOs، كيانات، نماذج عرض). العديد من المكتبات يمكن أن تتمكّن هذا.

ماجستير: رسم خرائط عالية الأداء

ماسستر هو خريطة كائنات سريعة تستند إلى الاتفاقيات وتستخدم توليد المصادر من أجل الأداء الأمثل.

// Install: Mapster and Mapster.Tool
using Mapster;

public class BlogPostDto
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Summary { get; set; }
    public List<string> CategoryNames { get; set; }
}

public class BlogPost
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public List<Category> Categories { get; set; }
}

// Configuration
public class MappingConfig : IRegister
{
    public void Register(TypeAdapterConfig config)
    {
        config.NewConfig<BlogPost, BlogPostDto>()
            .Map(dest => dest.Summary, src => src.Content.Substring(0, Math.Min(200, src.Content.Length)))
            .Map(dest => dest.CategoryNames, src => src.Categories.Select(c => c.Name).ToList());

        // Reverse map with ignore
        config.NewConfig<BlogPostDto, BlogPost>()
            .Ignore(dest => dest.Content);
    }
}

// Registration in Program.cs
TypeAdapterConfig.GlobalSettings.Scan(Assembly.GetExecutingAssembly());

// Usage with Dapper
public class BlogService
{
    private readonly string _connectionString;

    public async Task<List<BlogPostDto>> GetPostsAsync()
    {
        using var connection = new NpgsqlConnection(_connectionString);

        var posts = await connection.QueryAsync<BlogPost>(@"
            SELECT p.id, p.title, p.content
            FROM blog_posts p
        ");

        // Map to DTOs - very fast with Mapster
        return posts.Adapt<List<BlogPostDto>>();
    }

    // Projection mapping (compile-time)
    public async Task<List<BlogPostDto>> GetPostsDtosDirectlyAsync()
    {
        using var connection = new NpgsqlConnection(_connectionString);

        // Query directly to DTO shape
        return (await connection.QueryAsync<BlogPostDto>(@"
            SELECT
                id,
                title,
                SUBSTRING(content, 1, 200) as summary
            FROM blog_posts
        ")).ToList();
    }
}

(أ) ماج ماج ماج مانبب

هي مكتبة الخرائط الأكثر شعبية، على الرغم من أنها أبطأ من مابستر.

// Install: AutoMapper and AutoMapper.Extensions.Microsoft.DependencyInjection
using AutoMapper;

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<BlogPost, BlogPostDto>()
            .ForMember(d => d.Summary, opt => opt.MapFrom(s =>
                s.Content.Length > 200 ? s.Content.Substring(0, 200) : s.Content))
            .ForMember(d => d.CategoryNames, opt => opt.MapFrom(s =>
                s.Categories.Select(c => c.Name)));

        // Reverse map
        CreateMap<BlogPostDto, BlogPost>()
            .ForMember(d => d.Content, opt => opt.Ignore());
    }
}

// Registration in Program.cs
services.AddAutoMapper(typeof(MappingProfile));

// Usage
public class BlogService
{
    private readonly IMapper _mapper;
    private readonly string _connectionString;

    public BlogService(IMapper mapper, IConfiguration configuration)
    {
        _mapper = mapper;
        _connectionString = configuration.GetConnectionString("DefaultConnection");
    }

    public async Task<List<BlogPostDto>> GetPostsAsync()
    {
        using var connection = new NpgsqlConnection(_connectionString);

        var posts = await connection.QueryAsync<BlogPost>(@"
            SELECT id, title, content FROM blog_posts
        ");

        return _mapper.Map<List<BlogPostDto>>(posts.ToList());
    }
}

دليل رسم الخرائط: المراقبة الكاملة

وفي بعض الأحيان يكون أفضل نهج هو وضع خرائط يدوية واضحة:

public static class BlogPostMapper
{
    public static BlogPostDto ToDto(this BlogPost post)
    {
        return new BlogPostDto
        {
            Id = post.Id,
            Title = post.Title,
            Summary = post.Content.Length > 200
                ? post.Content.Substring(0, 200) + "..."
                : post.Content,
            CategoryNames = post.Categories?.Select(c => c.Name).ToList() ?? new List<string>()
        };
    }

    public static List<BlogPostDto> ToDtoList(this IEnumerable<BlogPost> posts)
    {
        return posts.Select(p => p.ToDto()).ToList();
    }

    // Inline mapping for simple cases
    public static BlogPostDto MapToDto(BlogPost post) => new()
    {
        Id = post.Id,
        Title = post.Title,
        Summary = post.Content[..Math.Min(200, post.Content.Length)]
    };
}

// Usage
var posts = await _repository.GetAllPostsAsync();
var dtos = posts.ToDtoList();

BenchmarkDotNet Results (mapping 1000 objects):

Method              | Mean      | Allocated
--------------------|-----------|----------
Manual Mapping      | 45.2 μs   | 78 KB
Mapster             | 52.1 μs   | 79 KB
AutoMapper          | 184.3 μs  | 156 KB

عمليات سحب المفاتيح:

  • رسم الخرائط هو أسرع ولكن يتطلب المزيد من الرموز
  • المسراب قريب من السرعة مع رمز أقل
  • AUUMaper هو مُلائم لكن له أعلى أعلى

النُهُج المُنتَجَلَة: أفضل ما في العالمين

في التطبيقات الحقيقية، غالباً ما تريد استخدام نُهُج مختلفة لسيناريوهات مختلفة داخل نفس التطبيق. هذا هو على النهج الموصى به لمعظم أنظمة الإنتاج.

نمط CQSRS: الفصل بين القراء والكتابات

إن نمط CQRS (الفصل بين الأسئلة المتعلقة بالمسؤولية عن الأسئلة) هو مناسب طبيعي لنُهُج الوصول إلى البيانات الهجينة. انظر مقالتي بشأن CQRS وحدث معاصر.

كيف يتعلق الأمر بالمناقشة التالية :

Marten هو a مستند قاعدة بيانات و الحدث مخزن بوصة PostgreSQL الذي يأخذ وصول البيانات إلى آخر مستوى هو يجمع:

  • عدد حالات الاستعانة بمصادر لكتابات (جداول الحدث)
  • الإسقاطات (الآراء المادية التي يُثلت للسؤال عنها)
  • (بآلاف دولارات الولايات المتحدة) لـمـا لـهـا
  • أنماط CQRS

بينما تركز هذه المادة على الوصول إلى البيانات العلاقاتية التقليدية (EF Courre, Daper)، تبين مارتن كيف يمكنك الاستفادة من الملامح المتقدمة لـ PostgreSQL (JSONB, Dapper) لتنفيذ البنايات المتطورة والمبادئ هي نفسها:

  • نماذج كتابة مستقلة (الأفضل بالنسبة للمعاملات والاتساق)
  • النماذج المفصَّلة (المحسَّنة على النحو الأمثل للاستفسارات والأداء)
graph TB
    Client[Client Application]

    subgraph "Write Side - Commands"
        WriteAPI[Write API / Commands]
        EFCore[EF Core Context]
        WriteDB[(PostgreSQL<br/>Write Operations)]
    end

    subgraph "Read Side - Queries"
        ReadAPI[Read API / Queries]
        Dapper[Dapper Repository]
        ReadDB[(PostgreSQL<br/>Read Operations)]
    end

    Client -->|Create/Update/Delete| WriteAPI
    WriteAPI --> EFCore
    EFCore -->|Change Tracking<br/>Validation<br/>Business Logic| WriteDB

    Client -->|Query/Search| ReadAPI
    ReadAPI --> Dapper
    Dapper -->|Optimized SQL<br/>DTOs<br/>No Tracking| ReadDB

    WriteDB -.->|Same Database| ReadDB

    style Client stroke:#6366f1,stroke-width:2px
    style WriteAPI stroke:#2563eb,stroke-width:2px
    style EFCore stroke:#2563eb,stroke-width:2px
    style WriteDB stroke:#2563eb,stroke-width:2px
    style ReadAPI stroke:#059669,stroke-width:2px
    style Dapper stroke:#059669,stroke-width:2px
    style ReadDB stroke:#059669,stroke-width:2px

هذا النمط من الإستفادات:

  • النفقات الأساسية لكتابات: تتبع التغيير، المصادقة، قواعد العمل
  • مُنْظَر فيما يلي: الحد الأقصى للاستفسار عن الأداء والمرونة
  • قاعدة بيانات واحدة، أنماط وصول مختلفة مُحسَّنة لكل حالة استخدام

التنفيذ: استراتيجية وقف إطلاق النار مع إطار وإطار تمويل الطوارئ

// Commands: Use EF Core for change tracking and validation
public class BlogCommandService
{
    private readonly BlogDbContext _context;
    private readonly ILogger<BlogCommandService> _logger;

    public BlogCommandService(BlogDbContext context, ILogger<BlogCommandService> logger)
    {
        _context = context;
        _logger = logger;
    }

    public async Task<int> CreatePostAsync(CreatePostCommand command)
    {
        // Business logic and validation
        var post = new BlogPost
        {
            Title = command.Title,
            Content = command.Content,
            CategoryId = command.CategoryId,
            PublishedDate = DateTime.UtcNow
        };

        _context.BlogPosts.Add(post);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Created blog post {PostId}", post.Id);

        return post.Id;
    }

    public async Task UpdatePostAsync(UpdatePostCommand command)
    {
        var post = await _context.BlogPosts.FindAsync(command.Id);

        if (post == null)
            throw new InvalidOperationException($"Post {command.Id} not found");

        post.Title = command.Title;
        post.Content = command.Content;
        post.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();

        _logger.LogInformation("Updated blog post {PostId}", post.Id);
    }

    public async Task DeletePostAsync(int id)
    {
        var post = await _context.BlogPosts.FindAsync(id);

        if (post != null)
        {
            _context.BlogPosts.Remove(post);
            await _context.SaveChangesAsync();

            _logger.LogInformation("Deleted blog post {PostId}", id);
        }
    }
}

// Queries: Use Dapper for read performance
public class BlogQueryService
{
    private readonly string _connectionString;
    private readonly ILogger<BlogQueryService> _logger;

    public BlogQueryService(IConfiguration configuration, ILogger<BlogQueryService> logger)
    {
        _connectionString = configuration.GetConnectionString("DefaultConnection");
        _logger = logger;
    }

    public async Task<BlogPostDto> GetPostBySlugAsync(string slug)
    {
        using var connection = new NpgsqlConnection(_connectionString);

        const string sql = @"
            SELECT
                p.id,
                p.title,
                p.slug,
                p.content,
                p.published_date,
                c.id as category_id,
                c.name as category_name,
                (SELECT COUNT(*) FROM comments WHERE blog_post_id = p.id) as comment_count
            FROM blog_posts p
            INNER JOIN categories c ON p.category_id = c.id
            WHERE p.slug = @Slug";

        var post = await connection.QueryFirstOrDefaultAsync<BlogPostDto>(sql, new { Slug = slug });

        if (post != null)
        {
            _logger.LogInformation("Retrieved blog post by slug {Slug}", slug);
        }

        return post;
    }

    public async Task<PagedResult<BlogPostSummaryDto>> GetRecentPostsAsync(int page, int pageSize)
    {
        using var connection = new NpgsqlConnection(_connectionString);

        const string sql = @"
            SELECT
                p.id,
                p.title,
                p.slug,
                LEFT(p.content, 200) as summary,
                p.published_date,
                c.name as category_name
            FROM blog_posts p
            INNER JOIN categories c ON p.category_id = c.id
            ORDER BY p.published_date DESC
            LIMIT @PageSize OFFSET @Offset";

        const string countSql = "SELECT COUNT(*) FROM blog_posts";

        var posts = await connection.QueryAsync<BlogPostSummaryDto>(
            sql,
            new { PageSize = pageSize, Offset = (page - 1) * pageSize }
        );

        var totalCount = await connection.ExecuteScalarAsync<int>(countSql);

        return new PagedResult<BlogPostSummaryDto>
        {
            Items = posts.ToList(),
            TotalCount = totalCount,
            Page = page,
            PageSize = pageSize
        };
    }

    public async Task<List<BlogPostDto>> SearchPostsAsync(string searchTerm)
    {
        using var connection = new NpgsqlConnection(_connectionString);

        const string sql = @"
            SELECT
                p.id,
                p.title,
                p.slug,
                p.content,
                p.published_date,
                c.name as category_name,
                ts_rank(p.search_vector, query) as relevance_score
            FROM blog_posts p
            INNER JOIN categories c ON p.category_id = c.id,
                 to_tsquery('english', @SearchTerm) query
            WHERE p.search_vector @@ query
            ORDER BY relevance_score DESC
            LIMIT 50";

        var posts = await connection.QueryAsync<BlogPostDto>(sql, new { SearchTerm = searchTerm });

        _logger.LogInformation(
            "Searched posts with term {SearchTerm}, found {Count} results",
            searchTerm,
            posts.Count()
        );

        return posts.ToList();
    }
}

// Service layer orchestrating commands and queries
public class BlogService
{
    private readonly BlogCommandService _commands;
    private readonly BlogQueryService _queries;

    public BlogService(BlogCommandService commands, BlogQueryService queries)
    {
        _commands = commands;
        _queries = queries;
    }

    // Write operations delegate to command service
    public Task<int> CreatePostAsync(CreatePostCommand command) => _commands.CreatePostAsync(command);
    public Task UpdatePostAsync(UpdatePostCommand command) => _commands.UpdatePostAsync(command);
    public Task DeletePostAsync(int id) => _commands.DeletePostAsync(id);

    // Read operations delegate to query service
    public Task<BlogPostDto> GetPostBySlugAsync(string slug) => _queries.GetPostBySlugAsync(slug);
    public Task<PagedResult<BlogPostSummaryDto>> GetRecentPostsAsync(int page, int pageSize)
        => _queries.GetRecentPostsAsync(page, pageSize);
    public Task<List<BlogPostDto>> SearchPostsAsync(string searchTerm)
        => _queries.SearchPostsAsync(searchTerm);
}

EF أساس مع عرضي SQL

فيما يتعلق بالتطبيقات التي هي في المقام الأول تطبيقات أساسية ولكنها تحتاج إلى الاستخدام الأمثل للأداء العرضي:

public class BlogService
{
    private readonly BlogDbContext _context;

    // 95% of queries: Use EF Core LINQ
    public async Task<List<BlogPost>> GetPostsByCategoryAsync(int categoryId)
    {
        return await _context.BlogPosts
            .Where(p => p.CategoryId == categoryId)
            .Include(p => p.Comments)
            .ToListAsync();
    }

    // 5% of queries: Use raw SQL for complex analytics
    public async Task<List<PostAnalytics>> GetPostAnalyticsAsync()
    {
        using var connection = _context.Database.GetDbConnection();
        await _context.Database.OpenConnectionAsync();

        using var command = connection.CreateCommand();
        command.CommandText = @"
            WITH post_metrics AS (
                SELECT
                    p.id,
                    p.title,
                    COUNT(DISTINCT c.id) as comment_count,
                    COUNT(DISTINCT v.id) as view_count,
                    AVG(c.sentiment_score) as avg_sentiment
                FROM blog_posts p
                LEFT JOIN comments c ON p.id = c.post_id
                LEFT JOIN post_views v ON p.id = v.post_id
                WHERE p.published_date >= NOW() - INTERVAL '30 days'
                GROUP BY p.id, p.title
            )
            SELECT * FROM post_metrics
            ORDER BY view_count DESC";

        var analytics = new List<PostAnalytics>();
        using var reader = await command.ExecuteReaderAsync();

        while (await reader.ReadAsync())
        {
            analytics.Add(new PostAnalytics
            {
                PostId = reader.GetInt32(0),
                Title = reader.GetString(1),
                CommentCount = reader.GetInt64(2),
                ViewCount = reader.GetInt64(3),
                AverageSentiment = reader.IsDBNull(4) ? 0 : reader.GetDouble(4)
            });
        }

        return analytics;
    }
}

دعونا ننظر إلى معايير أداء العالم الحقيقي للعمليات المشتركة مع PostgresQL:

النقطة المرجعية: قراءة 000 1 من المحاضر

BenchmarkDotNet Results (Lower is Better):

Method                    | Mean       | Allocated
------------------------- |----------- |-----------
EF Core (No Tracking)    | 12.34 ms   | 2.4 MB
EF Core (With Tracking)  | 15.67 ms   | 4.8 MB
Dapper                   | 8.21 ms    | 1.8 MB
Raw Npgsql               | 7.45 ms    | 1.2 MB

نقطة القياس: إدراج

Method                    | Mean       | Allocated
------------------------- |----------- |-----------
EF Core (SaveChanges)    | 245.3 ms   | 15.2 MB
EF Core (BulkInsert)     | 42.1 ms    | 8.4 MB
Dapper (Loop)            | 189.7 ms   | 2.1 MB
Npgsql COPY              | 18.3 ms    | 0.8 MB

الرقم القياسي: المُعْسِس

Method                    | Mean       | Allocated
------------------------- |----------- |-----------
EF Core (Include)        | 28.5 ms    | 5.2 MB
EF Core (Split Query)    | 24.1 ms    | 4.8 MB
Dapper (Multi-Map)       | 16.8 ms    | 3.1 MB
Raw Npgsql               | 15.2 ms    | 2.4 MB

يُقتدِد على

  1. راو Npgsql هو أسرع ولكن يتطلب معظم المدونات
  2. أداء أداء ممتاز (60 إلى 70 في المائة أسرع من EF RE)
  3. الاستفسارات المتعلقة بعدم تتبع التتبع (أ) معقول بالنسبة لمعظم السيناريوهات
  4. **** أظهِر أكبر ثغرات الأداء (فارق 10-13x!)
  5. المحفوظات نمط مماثل لوقت التنفيذ

ما هو النهج الذي يمكن اتباعه في الاستخدام

  • بناء تطبيق تطبيق جديد ذي متطلبات متطورـة
  • ● تصميم نطاقي مدفوع بنماذج كيانات غنية
  • تحتاج إلى الهجرة والإدارة
  • الفريق أكثر راحة مع C# من SQL
  • اقر قراءة/كتابة نسبة متوازِل
  • □ يكون أداء الاستفسار مقبولاً في حدود ما يتراوح بين 20 و50 في المائة من المستوى الأمثل
  • تريد تتبع التغيير ووحدة نمط العمل

انظر S انظر الجزء الأول (ج) التوجيه الأساسي الشامل بشأن الإطار البيئي الشامل.

  • □ الأداء هام ولكنه ليس بالغ الأهمية
  • لديك إستفسارات معقدة التي لا تُرسِل بشكل جيد إلى LINQ
  • أنت تَقْرأُ كتابة SQL
  • تحتاج إلى سيطرة دقيقة على توليد SQL
  • العمل مع قواعد البيانات الموجودة حالياً
  • □ اقرأ - أعباء عمل ثقيلة بكتابات بسيطة
  • هل تريد حدّاً أدنى من الارتفاع

انتفّر هذا الوقت:

  • □ الأداء الأقصى هو أمر بالغ الأهمية
  • بناء وحدات تجهيز بيانات عالية الجودة
  • العمل على نطاق واسع مع المعالم المحددة في PostgreSQL
  • عمليات الشحن وواردات السوائب
  • □ كل ميلي ثاني وكل ميغابايت في المسائل
  • تحتاج إلى التحكم المطلق

النهج المُنتَج:

  • ● لمختلف أجزاء التطبيق احتياجات مختلفة
  • □ نمط CQRS (الإطار الإلكتروني للكتب، جدول للقرر)
  • معظم الاستفسارات تستخدم EF Courre، ولكن عدداً قليلاً يحتاج إلى SQL الخام
  • هل تريد الانتقال التدريجي بين النهجين
  • التطبيقات التطبيقية الكبيرة والمعقدة ذات الاحتياجات المتنوعة

أولاً - موجز

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

  1. ابدأ مع EF أساس المشاريع الجديدة ما لم يكن لديك شروط أداء محددة
  2. أولاً - المجموع قبل - لا تفترض أنك تحتاج إلى Daper / ra SQL
  3. نُهُج الاستخدام المُهَجْجِز - الجمع بين مجموعة من الأدوات المختلفة
  4. الحفاظ على وصول البيانات - نمط المستودعات الذي يساعد على تنفيذ عمليات التشغيل
  5. **** - اضبط بشكل مناسب لعبء العمل الخاص بك
  6. مورSSSQL - لا تخرج عن قدرات قاعدة بيانات قوية

أولاً- مقدمة

  1. استخدام لمنع حدوث حقن SQL
  2. كِف كِش للاستفسارات المتكررة والغالية التكلفة
  3. (أ) المستخدم لـ انضمامات بدلاً من متعدد ذهاباً ذهاباً
  4. **** من بين
  5. مَثَلَ مِنَ ٱلْمَكْسِفِيَّةِ ٱلْمَتَانِيَّةِ لـ بسيط CRUD

بيانات أداء أداء

  1. ألف - الفهرسة لاستفسارات - تحليل الاستفسارات البطيئة عن تحليل التفسيرات
  2. رابعاً - البيانات لتكرارها
  3. عمليات الاستكمال ما كان ممكناً
  4. **** لمدرجات السس في المرفق
  5. مر - كِسَسِس كِسَسِس المكَسَسَسَسَسِس
  6. (أ) النظر في اقتباسات مقروءة (أ) عبء العمل الثقيل

ثالثاً - استنتاج

اختيار طريقة الوصول إلى البيانات الصحيحة لتطبيقك على الإنترنت مع PostgreSQL ليس عن إيجاد أداة "أفضل" بل عن مضاهاة الأداة الصحيحة مع احتياجاتك المحددة:

  • النفقات الأساسية (انظر ( الجزء الأول) في التنمية السريعة، ونماذج المجالات، والتطبيقات حيث تتفوق إنتاجية المطور على الأداء الخام
  • مُنْظَر (أ) توفير أرضية وسطى ممتازة مع أداء شبه أمثل وخلاصة معقولة
  • الدالة ARSNPggsql تحقيق الحد الأقصى من الأداء والمراقبة للعمليات الكثيفة البيانات
  • مكتبات ما مثل Mapster وCoOMAMapper اقلل مغلّي الطّور عند العمل مع وصول بيانات المستوى الأدنى

وفي الممارسة العملية، كثيرا ما تستخدم التطبيقات الأكثر نجاحا النهج المختلط(ب) الاستفادة من مواطن قوة كل أداة عند الاقتضاء:

  • النفقات الأساسية لمنطقك وكتابتك
  • مُنْظَر معقـد المعقـد ومقـررة بما يلي: الاستفسارات والابلاغ
  • الخام Nppggsql لأغراض العمليات السائبة والتحليلية

والمفتاح الرئيسي هو ما يلي:

  1. تُفِس متطلباتك - الأداء، والسرعة الإنمائية، ومهارات الفريق
  2. ملف ملف طلب التطبيق - تحديد الاختنازات الفعلية، لا المحاكات المفترضة
  3. **** - استخدام الأداة البسيطة التي تلبي احتياجاتك
  4. لا يُ - يمكنك أن تخلط النُهج داخل نفس التطبيق

تذكّر: الإستفادة المبكرة هي جذر كل الشرور، لكن كذلك بناء نظام لا يمكن أن يُقاس عند الحاجة. ابدأ ببساطة، قياس الأداء، واحسن حيث يهم.

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

الجزء الأول من هذه السلسلة:

الوثائق: الوثائق الرسمية

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


وبهذا نختتم سلسلتنا المكونة من جزأين عن الوصول إلى البيانات في شبكة الإنترنت! لقد غطينا كل شيء من خلاصات EF Cours القوية إلى الأداء الأقصى لـ SQL الخام، مع توجيه عملي حول الجمع بين النُهُج لتحقيق النتائج المثلى.

Finding related posts...
logo

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