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
डेटा अभिगम में .NET: ओआरएमों और मानचित्रण रणनीतियों का तुलना करना | ( | भाग |2
Wednesday, 03 December 2025
डेटा अभिगम के लिए हमारे व्यापक मार्गदर्शक का भाग 2 में आपका स्वागत है भाग 1हमने एंटिटी फ्रेमवर्क कोर की गहराई में खोज की।
इस लेख में हम हल्के भार के वैकल्पिकों को खोजेंगे और कैसे अधिकतम निष्पादन के लिए बहुविध दृष्टिकोणों का संयोजन करें
- **डैपर**माइक्रोसॉफ्ट स्पॉट
- कच्चा ADO.NET/एनपीजीएसक्यूएल: अधिकतम निष्पादन और नियंत्रण
- ऑब्जेक्ट मैपिंग पुस्तकालय: मैपस्टर विपरीत स्वतः-मेपर
- हाइब्रिड दृष्टिकोणईएफ कोर और डैपर का संयोजन
- निष्पादन मानक: वास्तविक
- निर्णय मैट्रिक्स: अपने परिदृश्य के लिए सही उपकरण चुनना
विषयसूची
डेपर
डैपर एक हल्का वजन है।
क्यों डैपर मौजूद है
डैपर स्टैक ओवरफ्लो से पैदा हुआ था। एमएसके0 उच्च के लिए की जरूरत है। एम्सके1 निष्पादन डेटा अभिगम। एम एसके2। टीम ने पाया कि अस्तित्व फ्रेमवर्क जैसे पूर्ण ओआरएमों ने अपने उच्च के लिये अधिक ओवरहेड जोड़ दिया।
मुख्य विशेषताएँ
- कच्चा SQLआप सभी SQL स्वयं लिख सकते हैं - पूर्ण नियंत्रण
- उच्च निष्पादनADO के ऊपर न्यूनतम ओवरहेड
- बहु--: एकाधिक संबंधित प्रकारों के लिए जटिल क्वेरी मानचित्रित करें
- पैरामीटर हैंडलिंग: स्वचालित पैरामीटरीकरण SQL इंजेक्शन को रोकता है
- संव्यवहार समर्थन: लेन-देन पर पूर्ण नियंत्रण
- सरलता: कोई कॉन्फ़िगरेशन नहीं
- अतुल्यकालिकआधुनिक .NET के लिए पूर्ण अतुल्यकालिक समर्थन
मूल डैपर उदाहरण
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;
}
उन्नत डैपर तकनीक
जटिल क्वेरी के लिए गतिशील पैरामीटर
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);
}
PostgreSQL प्रकार के लिए मनपसंद प्रकार हैंडलर
// 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>>());
PostgreSQL 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;
}
}
जब डेपर का उपयोग करना है
जब डेपर का उपयोग करें
- निष्पादन महत्वपूर्ण है लेकिन आप नहीं need absolute maximum speed
- You're comfortable writing SQL
- आपके पास जटिल क्वेरी हैं जो LINQ में अच्छी तरह से नक्शे नहीं map
- आपको एसक्यूएल जनन पर अच्छी नियंत्रण की जरूरत है
- आप एक मौजूदा डाटाबेस स्कीमा के साथ काम कर रहे हैं
- आप न्यूनतम ओवरहेड और निर्भरता चाहते हैं
- स्मृति उपयोग एक चिंता है
- आपको डाटाबेस को व्यापक रूप से उपयोग करने की आवश्यकता है
जब डेपर से बचें
- आपकी टीम एसक्यूएल के साथ आरामदायक नहीं है
- आपको स्वचालित परिवर्तन ट्रैकिंग की जरूरत है
- आप स्कीमा माइग्रेशन को कोड के रूप में चाहते हैं
- आप एक तेजी से विकसित स्कीमा है
- आपको cross-डेटाबेस पोर्टेबल की जरूरत है
- आप SQL वाक्य संरचना से अधिक LINQ को पसंद करते हैं
निष्पादन विशेषताएँ
- क्वेरी निष्पादनकच्चे ADO के ऊपर ओवरहेड
- स्मृति उपयोगन्यूनतम - कोई परिवर्तन ट्रैकिंग या प्रॉक्सी नहीं
- बल्क ऑपरेशन: PostgreSQL COPY के साथ उत्कृष्ट
- पहला प्रश्न: तेजी से - कोई संकलन चरण नहीं
- विकासकर्ता उत्पादकता: SQL ज्ञान की आवश्यकता है लेकिन बहुत पूर्वानुमानीय
Npgsql के साथ Raw ADO.NET
पूर्ण अधिकतम निष्पादन और नियंत्रण के लिए, आप उपयोग कर सकते हैं एनपीजीएसक्यूएल सीधे किसी भी ORM परत के बिना
जब कच्चा ADO.NET समझ आता है
Raw ADO.NET उपयुक्त है जब
- आप निष्पादन की हर मिलीसेकेंड की जरूरत है
- आप उच्च डेटा प्रोसेसर बना रहे हैं
- आप PostgreSQL के साथ काम कर रहे हैं
- आप मेमोरी आबंटन पर सही नियंत्रण की जरूरत है
- आप bulk ऑपरेशन कर रहे हैं या बड़ी डेटासेट स्ट्रीमिंग
उदाहरण: Pure Npgsql
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)
};
}
}
}
निष्पादन विशेषताएँ
- क्वेरी निष्पादन: आधारलाइन - सबसे तेजी से संभव
- स्मृति उपयोग: सबसे कम - आवंटनों पर पूर्ण नियंत्रण
- बल्क ऑपरेशन: COPY प्रोटोकॉल के साथ उत्कृष्ट
- विकासकर्ता उत्पादकता: अधिकतर हस्तचालित काम की आवश्यकता होती है
ऑब्जेक्ट मैपिंग पुस्तकालय
जब आप डेपर या कच्चे ADO के साथ काम करते हैं, तो आपको अक्सर विभिन्न वस्तुओं के प्रतिनिधित्वों के बीच नक्शे बनाने की आवश्यकता होती है।
मैपस्टर: उच्च-प्रक्रिया मानचित्रण
मैपस्टर एक त्वरित वस्तु मैपर है जो अधिकतम निष्पादन के लिए स्रोत जनन का उपयोग करता है
// 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
कुंजी ले जाने के लिए
- हस्त मैपिंग सबसे तेज है लेकिन अधिक कोड की आवश्यकता है
- मैपस्टर कम कोड के साथ लगभग उतना ही तेजी से है
- ऑटोमापर सुविधाजनक है लेकिन उच्च ओवरहेड है
हाइब्रिड दृष्टिकोण
वास्तविक अनुप्रयोगों में आप अक्सर एक ही अनुप्रयोजन के भीतर विभिन्न परिदृश्यों के लिए भिन्न दृष्टिकोण का उपयोग करना चाहते हैं सिफारिशित दृष्टिकोण अधिकतर उत्पादन प्रणालियों के लिए
सीक्यूआरएस पैटर्न
CQRS (Command Query Responsibility Segregation) पैटर्न हाइब्रिड डेटा अभिगम दृष्टिकोणों के लिए एक स्वाभाविक फिट है मार्टिनCity name (optional, probably does not need a translation), मेरी लेख पर देखें आधुनिक सीक्यूआरएस और घटना खोज.
मार्टेन इस चर्चा से कैसे संबंध रखते हैं
मार्टेन PostgreSQL पर बनाया गया एक दस्तावेज़ डेटाबेस और घटना भंडार है जो अन्य स्तर तक हाइब्रिड डाटा अभिगम लेता है
- घटना स्रोत लिखने के लिए (परिवर्तनीय घटना प्रवाहों के लिए
- प्रक्षेपण पठन के लिए (आयामी दृश्य क्वेरी के लिए अनुकूलित
- PostgreSQL's JSONB दस्तावेज़ भंडारण के लिए
- सीक्यूआरएस पैटर्न में निर्मित
इस लेख में पारंपरिक रिलेक्शनल डेटा अभिगम पर ध्यान केंद्रित किया जाता है (EF CoreM SK1 Dapper), मार्टेन दिखाता है कि आप किस प्रकार PostgreSQL 's उन्नत सुविधाओं का उपयोग कर सकते हैं।
- अलग लेखन मॉडल (विक्रय और सुसंगतता के लिए अनुकूलित
- अलग पठन मॉडल (अनुसंयुक्त क्वेरी और निष्पादन के लिए
- प्रत्येक कार्य के लिए सही उपकरण का उपयोग करें
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
यह पैटर्न लेबरेज करता है
- ईएफ कोर लिखने के लिए: ट्रैकिंग बदलें, वैधता
- डैपर पढ़ने के लिए: अधिकतम क्वेरी निष्पादन और लचीलापन
- एक ही डेटाबेस, विभिन्न अभिगम पैटर्न प्रत्येक उपयोग के लिए अनुकूलित
कार्यान्वयन EF कोर और डेपर के साथ : CQRS
// 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);
}
आकस्मिक कच्चा एसक्यूएल के साथ एफ कोर
उन अनुप्रयोगों के लिए जो प्राथमिक रूप से एफ कोर हैं लेकिन कभी-कभी निष्पादन अनुकूलन की आवश्यकता है
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 के साथ साझा ऑपरेशनों के लिए वास्तविक निष्पादन मानकों को देखें
बैंचमार्क: पढ़ने 1000 रिकार्ड
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
बैंचमार्क: इनसेटिंग 1000 रिकार्ड
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
कुंजी ले जाने वाले
- Raw Npgsql सबसे तेज है लेकिन अधिक कोड की आवश्यकता है
- डैपर उत्कृष्ट निष्पादन प्रदान करता है न्यूनतम अवstraction लागत के साथ (60-70% EF कोर से अधिक तेजी से
- ईएफ कोर no-ट्रैकिंग क्वेरी अधिकांश परिदृश्यों के लिए उचित हैं
- बल्क ऑपरेशन सबसे बड़े निष्पादन अंतर दिखाएँ (10-13x अंतर
- स्मृति आवंटन निष्पादन समय के लिए एक समान पैटर्न अनुसरण करें
निर्णय मैट्रिक्स: Which Approach to Use
EF कोर का उपयोग करें जब
- ✅ विकसित आवश्यकताओं के साथ एक नया अनुप्रयोग बनाना
- ✅ डोमेन
- ✅ आपको माइग्रेशन और स्कीमा प्रबंधन की जरूरत है
- ✅ टीम एसक्यूएल की तुलना में C# के साथ अधिक आरामदायक है
- ✅ पढ़ना/ लेखन अनुपात संतुलित है
- ✅ अधिकतम के भीतर क्वेरी निष्पादन स्वीकार्य है
- ✅ आप ट्रैकिंग और कार्य पैटर्न की इकाई बदलना चाहते हैं
देखें भाग 1 व्यापक एफ कोर मार्गदर्शन के लिए
जब डेपर का उपयोग करें
- ✅ निष्पादन महत्वपूर्ण है लेकिन महत्वपूर्ण नहीं
- ✅ आपके पास जटिल क्वेरी हैं जो LINQ में अच्छा नक्शे नहीं बनाते
- ✅ आप'एसक्यूएल लिखने में आरामदायक हैं
- ✅ आपको एसक्यूएल जनन पर अच्छी नियंत्रण की जरूरत है
- ✅ मौजूदा डाटाबेस स्कीमा के साथ काम करना
- ✅ Read-सही लेखन के साथ भारी कार्यभार
- ✅ आप न्यूनतम अमूर्ति ओवरहेड चाहते हैं
Raw Npgsql का उपयोग करें जब:
- ✅ अधिकतम निष्पादन महत्वपूर्ण है
- उच्च निर्माण डेटा प्रक्रमक
- ✅ PostgreSQL के साथ व्यापक रूप से काम कर रहा है
- ✅ बैच परिचालन और बल्क आयात
- ✅ हर मिली सेकंड और मेगाबाइट महत्वपूर्ण है
- ✅ आपको पूर्ण नियंत्रण की आवश्यकता है
हाइब्रिड दृष्टिकोण जब
- ✅ अनुप्रयोग के विभिन्न भागों में भिन्न आवश्यकताएं हैं
- CQRS पैटर्न
- Most queries use EF Core, but a few need raw SQL
- ✅ आप दृष्टिकोणों के बीच धीरे-धीरे बदलाव चाहते हैं
- विविध आवश्यकताओं के साथ जटिल अनुप्रयोग
सर्वोत्तम प्रथा सारांश
सामान्य दिशानिर्देश
- ईएफ कोर से प्रारंभ करें नए परियोजनाओं के लिए जब तक आपके पास विशिष्ट निष्पादन आवश्यकताएं नहीं हैं
- अनुकूलन से पहले प्रोफ़ाइल - don'आप को Dapper की आवश्यकता नहीं है
- हाइब्रिड दृष्टिकोण का उपयोग करें - विभिन्न उपकरणों की शक्तियों को जोड़ें
- डेटा अभिगम तर्क को अलग रखें - रिपोजिटर पैटर्न कार्यान्वयनों को स्विच करने में मदद करता है
- कनेक्शन पूलिंग का उपयोग करें - अपने कार्यभार के लिए उचित रूप से कॉन्फ़िगर करें
- पोस्टग्रेएसक्यूएल विशेषताओं का उपयोग करें - don'\t powerful database capabilities away abstract
डैपर विशिष्ट
- परिमितिकृत क्वेरी का उपयोग करें एसक्यूएल इंजेक्शन रोकने के लिए
- क्वेरी परिणाम कैचिंग पर विचार करें expensive, बार-बार पूछने के लिए
- बहु-मैपिंग का उपयोग करें संयुक्त के स्थान पर बहुराउंड-ट्रिप्स
- पुनः कनेक्शन्स कनेक्शन पूल से
- विचार करें Dapper.Contrib सरल क्रूड ऑपरेशन के लिए
निष्पादन टिप
- अपने प्रश्नों को अनुक्रमित करें - व्याख्या विश्लेषण के साथ धीमी क्वेरी का विश्लेषण करें
- तैयार विवरणों का उपयोग करें बार-बार पूछे जाने के लिए
- बैच ऑपरेशन जब संभव हो
- प्रतिलिपि का उपयोग करें पोस्टग्रेएसक्यूएल में बड़े पैमाने पर प्रविष्टियों के लिए
- कनेक्शन पूल मॉनिटर करें न्यूनतम कॉन्फ़िगर करें
- पठित अनुकरणों पर विचार करें पढ़ने के लिए-भारी कार्यभार
निष्कर्ष
PostgreSQL के साथ आपके .NET अनुप्रयोग के लिए सही डेटा अभिगम दृष्टिकोण का चयन करने में यह नहीं है कि आप क्या औज़ार ढूंढ सकते हैं।
- ईएफ कोर (see भाग 1) त्वरित विकास में उत्कृष्टता करता है, डोमेन मॉडलिंगM SK2 और अनुप्रयोग जहां विकासकर्ता उत्पादकता कच्चे निष्पादन से बढ़ती है
- डैपर समीप - के साथ उत्तम निष्पादन और उचित अमूर्ति के साथ एक उत्कृष्ट मध्य भूमि प्रदान करता है
- कच्चा Npgsql डेटा के लिए अधिकतम निष्पादन और नियंत्रण प्रदान करता है
- मैपिंग लाइब्रेरी जैसे Mapster और AutoMapper कम डेटा अभिगम के साथ काम करते समय बॉयलप्लेट घटाएँ
व्यवहार में, सबसे सफल अनुप्रयोग अक्सर एक का उपयोग करते हैं हाइब्रिड दृष्टिकोण, प्रत्येक उपकरण की सामर्थ्यों को जहां उचित हो, बढ़ावा देना
- उपयोग करें ईएफ कोर आपके डोमेन तर्क और लिखता है
- उपयोग करें डैपर जटिल पठन प्रश्नों और रिपोर्टिंग के लिए
- उपयोग करें कच्चा Npgsql थोक परिचालन और विश्लेषण के लिए
कुंजी है
- अपनी आवश्यकताओं को समझें - निष्पादन
- अपना अनुप्रयोग प्रोफ़ाइल करें - वास्तविक bottlenecks पहचानें
- व्यावहारिक रूप से चुनें - अपनी आवश्यकताओं को पूरा करने के लिए सरलतम उपकरण का उपयोग करें
- लचीला रहना - आप एक ही अनुप्रयोग के भीतर दृष्टिकोण मिश्रित कर सकते हैं
याद रखें कि पहले से ही अनुकूलन सभी बुराइयों का मूल है, लेकिन ऐसा एक प्रणाली बनाने के लिए भी होता है जो जरूरत पड़ने पर पैमाना नहीं कर सकता।
संदर्भ और आगे पढ़ने
इस श्रृंखला का भाग 1
आधिकारिक दस्तावेज़
- डेपर जिटहब
- डैपर अभ्यास
- एनपीजीएसक्यूएल दस्तावेज़ीकरण
- Npgsql ADO.NET प्रदाता
- मैपस्टर जिटहब
- स्वतः-मेपर दस्तावेज़ीकरण
- पोस्टग्रेएसक्यूएल दस्तावेज़ीकरण
इस ब्लॉग पर संबंधित लेख
- ब्लॉग पोस्ट के लिए अस्तित्व फ्रेमवर्क जोड़ना
- ईएफ प्रवास सही रास्ते में
- ईएफ कोर के साथ पूर्ण पाठ खोज
- आधुनिक सीक्यूआरएस और घटना खोज
यह हमारे डेटा अभिगम पर दो भागों के श्रृंखला को समाप्त करता है।