mirror of
https://github.com/wshobson/agents.git
synced 2026-03-18 17:47:16 +00:00
style: format all files with prettier
This commit is contained in:
@@ -4,22 +4,24 @@ Advanced patterns for high-performance data access with Dapper in .NET.
|
||||
|
||||
## Why Dapper?
|
||||
|
||||
| Aspect | Dapper | EF Core |
|
||||
|--------|--------|---------|
|
||||
| Performance | ~10x faster for simple queries | Good with optimization |
|
||||
| Control | Full SQL control | Abstracted |
|
||||
| Learning curve | Low (just SQL) | Higher |
|
||||
| Complex mappings | Manual | Automatic |
|
||||
| Change tracking | None | Built-in |
|
||||
| Migrations | External tools | Built-in |
|
||||
| Aspect | Dapper | EF Core |
|
||||
| ---------------- | ------------------------------ | ---------------------- |
|
||||
| Performance | ~10x faster for simple queries | Good with optimization |
|
||||
| Control | Full SQL control | Abstracted |
|
||||
| Learning curve | Low (just SQL) | Higher |
|
||||
| Complex mappings | Manual | Automatic |
|
||||
| Change tracking | None | Built-in |
|
||||
| Migrations | External tools | Built-in |
|
||||
|
||||
**Use Dapper when:**
|
||||
|
||||
- Performance is critical (hot paths)
|
||||
- You need complex SQL (CTEs, window functions)
|
||||
- Read-heavy workloads
|
||||
- Legacy database schemas
|
||||
|
||||
**Use EF Core when:**
|
||||
|
||||
- Rich domain models with relationships
|
||||
- Need change tracking
|
||||
- Want LINQ-to-SQL translation
|
||||
@@ -74,7 +76,7 @@ public class ProductRepository
|
||||
{
|
||||
// Connection opens automatically, closes on dispose
|
||||
using var connection = _factory.CreateConnection();
|
||||
|
||||
|
||||
return await connection.QueryFirstOrDefaultAsync<Product>(
|
||||
new CommandDefinition(
|
||||
"SELECT * FROM Products WHERE Id = @Id",
|
||||
@@ -120,7 +122,7 @@ var inserted = await connection.QuerySingleAsync<Product>(
|
||||
// UPDATE
|
||||
var rowsAffected = await connection.ExecuteAsync(
|
||||
"""
|
||||
UPDATE Products
|
||||
UPDATE Products
|
||||
SET Name = @Name, Price = @Price, UpdatedAt = @UpdatedAt
|
||||
WHERE Id = @Id
|
||||
""",
|
||||
@@ -190,7 +192,7 @@ public async Task<Product?> GetProductWithCategoryAsync(string id)
|
||||
""";
|
||||
|
||||
using var connection = _factory.CreateConnection();
|
||||
|
||||
|
||||
var result = await connection.QueryAsync<Product, Category, Product>(
|
||||
sql,
|
||||
(product, category) =>
|
||||
@@ -218,7 +220,7 @@ public async Task<Order?> GetOrderWithItemsAsync(int orderId)
|
||||
var orderDictionary = new Dictionary<int, Order>();
|
||||
|
||||
using var connection = _factory.CreateConnection();
|
||||
|
||||
|
||||
await connection.QueryAsync<Order, OrderItem, Product, Order>(
|
||||
sql,
|
||||
(order, item, product) =>
|
||||
@@ -254,9 +256,9 @@ public async Task<(IReadOnlyList<Product> Products, int TotalCount)> SearchWithC
|
||||
const string sql = """
|
||||
-- First result set: count
|
||||
SELECT COUNT(*) FROM Products WHERE CategoryId = @CategoryId;
|
||||
|
||||
|
||||
-- Second result set: data
|
||||
SELECT * FROM Products
|
||||
SELECT * FROM Products
|
||||
WHERE CategoryId = @CategoryId
|
||||
ORDER BY Name
|
||||
OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY;
|
||||
@@ -288,14 +290,14 @@ public async Task<IReadOnlyList<Product>> GetByIdsAsync(IEnumerable<string> ids)
|
||||
// Create DataTable matching TVP structure
|
||||
var table = new DataTable();
|
||||
table.Columns.Add("Id", typeof(string));
|
||||
|
||||
|
||||
foreach (var id in ids)
|
||||
{
|
||||
table.Rows.Add(id);
|
||||
}
|
||||
|
||||
using var connection = _factory.CreateConnection();
|
||||
|
||||
|
||||
var results = await connection.QueryAsync<Product>(
|
||||
"SELECT p.* FROM Products p INNER JOIN @Ids i ON p.Id = i.Id",
|
||||
new { Ids = table.AsTableValuedParameter("dbo.StringIdList") });
|
||||
@@ -313,7 +315,7 @@ public async Task<IReadOnlyList<Product>> GetByIdsAsync(IEnumerable<string> ids)
|
||||
public async Task<IReadOnlyList<Product>> GetTopProductsAsync(int categoryId, int count)
|
||||
{
|
||||
using var connection = _factory.CreateConnection();
|
||||
|
||||
|
||||
var results = await connection.QueryAsync<Product>(
|
||||
"dbo.GetTopProductsByCategory",
|
||||
new { CategoryId = categoryId, TopN = count },
|
||||
@@ -334,7 +336,7 @@ public async Task<(Order Order, string ConfirmationCode)> CreateOrderAsync(Order
|
||||
parameters.Add("ConfirmationCode", dbType: DbType.String, size: 20, direction: ParameterDirection.Output);
|
||||
|
||||
using var connection = _factory.CreateConnection();
|
||||
|
||||
|
||||
await connection.ExecuteAsync(
|
||||
"dbo.CreateOrder",
|
||||
parameters,
|
||||
@@ -354,9 +356,9 @@ public async Task<Order> CreateOrderWithItemsAsync(Order order, List<OrderItem>
|
||||
{
|
||||
using var connection = _factory.CreateConnection();
|
||||
await connection.OpenAsync();
|
||||
|
||||
|
||||
using var transaction = await connection.BeginTransactionAsync();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// Insert order
|
||||
@@ -384,7 +386,7 @@ public async Task<Order> CreateOrderWithItemsAsync(Order order, List<OrderItem>
|
||||
transaction);
|
||||
|
||||
await transaction.CommitAsync();
|
||||
|
||||
|
||||
order.Items = items;
|
||||
return order;
|
||||
}
|
||||
@@ -487,7 +489,7 @@ public abstract class DapperRepositoryBase<T> where T : class
|
||||
protected async Task<T?> GetByIdAsync<TId>(TId id, CancellationToken ct = default)
|
||||
{
|
||||
var sql = $"SELECT * FROM {TableName} WHERE Id = @Id";
|
||||
|
||||
|
||||
using var connection = ConnectionFactory.CreateConnection();
|
||||
return await connection.QueryFirstOrDefaultAsync<T>(
|
||||
new CommandDefinition(sql, new { Id = id }, cancellationToken: ct));
|
||||
@@ -496,17 +498,17 @@ public abstract class DapperRepositoryBase<T> where T : class
|
||||
protected async Task<IReadOnlyList<T>> GetAllAsync(CancellationToken ct = default)
|
||||
{
|
||||
var sql = $"SELECT * FROM {TableName}";
|
||||
|
||||
|
||||
using var connection = ConnectionFactory.CreateConnection();
|
||||
var results = await connection.QueryAsync<T>(
|
||||
new CommandDefinition(sql, cancellationToken: ct));
|
||||
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
protected async Task<int> ExecuteAsync(
|
||||
string sql,
|
||||
object? parameters = null,
|
||||
string sql,
|
||||
object? parameters = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
using var connection = ConnectionFactory.CreateConnection();
|
||||
|
||||
@@ -159,13 +159,13 @@ services.AddDbContext<AppDbContext>(options =>
|
||||
maxRetryCount: 3,
|
||||
maxRetryDelay: TimeSpan.FromSeconds(10),
|
||||
errorNumbersToAdd: null);
|
||||
|
||||
|
||||
sqlOptions.CommandTimeout(30);
|
||||
});
|
||||
|
||||
|
||||
// Performance settings
|
||||
options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
|
||||
|
||||
|
||||
// Development only
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
@@ -196,7 +196,7 @@ public class Product
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
|
||||
[Timestamp]
|
||||
public byte[] RowVersion { get; set; } // SQL Server rowversion
|
||||
}
|
||||
@@ -214,13 +214,13 @@ catch (DbUpdateConcurrencyException ex)
|
||||
{
|
||||
var entry = ex.Entries.Single();
|
||||
var databaseValues = await entry.GetDatabaseValuesAsync(ct);
|
||||
|
||||
|
||||
if (databaseValues == null)
|
||||
{
|
||||
// Entity was deleted
|
||||
throw new NotFoundException("Product was deleted by another user");
|
||||
}
|
||||
|
||||
|
||||
// Client wins - overwrite database values
|
||||
entry.OriginalValues.SetValues(databaseValues);
|
||||
await _context.SaveChangesAsync(ct);
|
||||
@@ -237,12 +237,12 @@ try
|
||||
// Multiple operations
|
||||
_context.Orders.Add(order);
|
||||
await _context.SaveChangesAsync(ct);
|
||||
|
||||
|
||||
await _context.OrderItems.AddRangeAsync(items, ct);
|
||||
await _context.SaveChangesAsync(ct);
|
||||
|
||||
|
||||
await _paymentService.ProcessAsync(order.Id, ct);
|
||||
|
||||
|
||||
await transaction.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
@@ -264,14 +264,14 @@ public class ProductConfiguration : IEntityTypeConfiguration<Product>
|
||||
// Unique index
|
||||
builder.HasIndex(p => p.Sku)
|
||||
.IsUnique();
|
||||
|
||||
|
||||
// Composite index for common query patterns
|
||||
builder.HasIndex(p => new { p.CategoryId, p.Name });
|
||||
|
||||
|
||||
// Filtered index (SQL Server)
|
||||
builder.HasIndex(p => p.Price)
|
||||
.HasFilter("[IsDeleted] = 0");
|
||||
|
||||
|
||||
// Include columns for covering index
|
||||
builder.HasIndex(p => p.CategoryId)
|
||||
.IncludeProperties(p => new { p.Name, p.Price });
|
||||
@@ -335,7 +335,7 @@ builder.HasIndex(p => p.FullName);
|
||||
services.AddDbContext<AppDbContext>(options =>
|
||||
{
|
||||
options.UseSqlServer(connectionString);
|
||||
|
||||
|
||||
options.LogTo(
|
||||
filter: (eventId, level) => eventId.Id == CoreEventId.QueryExecutionPlanned.Id,
|
||||
logger: (eventData) =>
|
||||
|
||||
Reference in New Issue
Block a user