欢迎使用 .NET Core 中 Dapper 的终极指南!🌟 在这篇深入的文章中,我们将深入了解 Dapper 的世界,并探讨其功能、优势以及如何在 .NET Core 应用程序中有效地使用它。作为一名拥有 30 多年经验的专业 .NET 和 C# 架构师,我将指导您完成设置 Dapper 的过程,了解其优势,并提供遵循 SOLID 原则、干净代码、可维护性、可伸缩性和可读性的实际示例。📚
Dapper 是由 Stack Overflow 团队构建的轻量级高性能数据访问工具。它为 .NET 应用程序中的数据访问层提供了一种极简方法,专注于原始性能,同时保持代码的简单性。Dapper 使用其他方法来执行 SQL 命令和查询数据,从而扩展了 IDbConnection 接口。
Dapper 是用于 .NET 的轻量级、高性能 micro-ORM(对象关系映射器)。它通过提供一种将数据库结果映射到强类型对象的便捷方法,简化了数据库访问。Dapper 旨在快速、高效且易于使用,使其成为希望最大限度地提高性能和生产力的开发人员的绝佳选择。🎯
与传统的 ORM 和手动数据库访问相比,Dapper 具有以下几个优势:
若要开始使用 Dapper,需要设置 .NET Core 环境。下面是一个快速的纲要:
创建新的 .NET Core 应用程序:Create a new .NET Core application:
dotnet new console -n DapperDemo
cd DapperDemo
添加 Dapper NuGet 包:Add the Dapper NuGet package:
dotnet add package Dapper
在代码中添加必要的语句:using
using Dapper;
using System.Data;
using System.Data.SqlClient;
在文件中配置数据库连接字符串:appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyDatabase;User Id=myuser;Password=mypassword;"
}
}
现在,你已准备好开始在 .NET Core 应用程序中使用 Dapper!🎉
让我们从一些简单的 CRUD 操作开始,了解 Dapper 的基本用法。
让我们从一个简单的例子开始,来演示 Dapper 的基本用法。假设我们的数据库中有一个表,并且我们想要检索所有客户。Customer
从表中检索所有客户,并将结果映射到对象。CustomerCustomer
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public async Task<IEnumerable<Customer>> GetAllCustomers()
{
using (var connection = new SqlConnection(_connectionString))
{
await connection.OpenAsync();
var customers = await connection.QueryAsync<Customer>("SELECT * FROM Customer");
return customers;
}
}
在此示例中,我们定义一个表示表结构的类。然后,我们使用 Dapper 的方法执行一个简单的查询,并将结果映射到对象集合。CustomerCustomerQueryAsyncSELECTCustomer
请注意,我们如何使用该语句来确保正确处置数据库连接和异步数据库访问模式。这遵循资源管理和无阻塞 I/O 的最佳实践。 🔧usingasync/await
Dapper 与存储过程无缝集成,允许您执行它们并将结果映射到对象。
执行按电子邮件域检索客户的存储过程。
public async Task<IEnumerable<Customer>> GetCustomersByEmailDomain(string domain)
{
using (var connection = new SqlConnection(_connectionString))
{
await connection.OpenAsync();
var customers = await connection.QueryAsync<Customer>(
"GetCustomersByEmailDomain",
new { EmailDomain = domain },
commandType: CommandType.StoredProcedure);
return customers;
}
}
在此示例中,我们假设有一个名为的存储过程,该存储过程将电子邮件域作为参数并返回相应的客户。GetCustomersByEmailDomain
我们使用 Dapper 的方法执行存储过程,将参数作为匿名对象传递。我们指定参数以指示我们正在执行存储过程。QueryAsyncEmailDomaincommandTypeCommandType.StoredProcedure
Dapper 会自动将结果集映射到对象集合,从而轻松处理检索到的数据。😊Customer
现在让我们来探讨一些高级概念和场景,让 Dapper 真正大放异彩。🌟
Dapper 允许您使用该方法执行返回多个结果集的查询。当需要在单个数据库调用中从多个表或存储过程检索数据时,这非常有用。QueryMultiple
在单个数据库查询中检索客户及其相应的订单。
public async Task<(IEnumerable<Customer>, IEnumerable<Order>)> GetCustomersWithOrders()
{
using (var connection = new SqlConnection(_connectionString))
{
await connection.OpenAsync();
using (var multi = await connection.QueryMultipleAsync(@"
SELECT * FROM Customer;
SELECT * FROM Order WHERE CustomerId IN (SELECT Id FROM Customer);"))
{
var customers = await multi.ReadAsync<Customer>();
var orders = await multi.ReadAsync<Order>();
return (customers, orders);
}
}
}
在此示例中,我们使用该方法在单个数据库调用中执行两个单独的查询。第一个查询检索所有客户,第二个查询检索与这些客户关联的订单。QueryMultipleAsync
我们使用该方法读取每个结果集并将其映射到相应的 和 对象。最后,我们返回一个包含客户及其订单的元组。ReadAsyncCustomerOrder
此方法通过在单个调用中获取相关数据来优化数据库往返并提高性能。🚀
Dapper 支持数据库事务,允许您将多个数据库操作分组到单个原子工作单元中。
在交易中将资金从一个账户转移到另一个账户。
public async Task TransferFunds(int fromAccountId, int toAccountId, decimal amount)
{
using (var connection = new SqlConnection(_connectionString))
{
await connection.OpenAsync();
using (var transaction = await connection.BeginTransactionAsync())
{
try
{
// Deduct funds from the source account
await connection.ExecuteAsync(
"UPDATE Account SET Balance = Balance - @Amount WHERE Id = @FromAccountId",
new { FromAccountId = fromAccountId, Amount = amount },
transaction: transaction);
// Add funds to the destination account
await connection.ExecuteAsync(
"UPDATE Account SET Balance = Balance + @Amount WHERE Id = @ToAccountId",
new { ToAccountId = toAccountId, Amount = amount },
transaction: transaction);
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
}
}
在这个例子中,我们使用一个交易来确保资金转账操作的原子性。我们使用该方法启动事务,并将事务对象传递给每个数据库操作的方法。BeginTransactionAsyncExecuteAsync
如果在事务期间发生任何异常,我们将使用该方法回滚事务以还原所做的任何更改。如果所有操作都成功,我们将使用该方法提交事务。RollbackAsyncCommitAsync
通过使用交易,我们保持数据完整性,并确保资金转账是全有或全无的操作。💰
Dapper 完全支持异步数据库操作,允许您编写非阻塞和可扩展的代码。
异步检索客户并并发处理它们。
public async Task ProcessCustomersAsync()
{
using (var connection = new SqlConnection(_connectionString))
{
await connection.OpenAsync();
var customers = await connection.QueryAsync<Customer>("SELECT * FROM Customer");
var tasks = customers.Select(async customer =>
{
// Process each customer asynchronously
await ProcessCustomer(customer);
});
await Task.WhenAll(tasks);
}
}
private async Task ProcessCustomer(Customer customer)
{
// Perform some asynchronous processing for each customer
await Task.Delay(1000); // Simulating async work
Console.WriteLine($"Processed customer: {customer. Name}");
}
在此示例中,我们使用该方法异步检索所有客户。然后,我们使用 LINQ 的方法创建任务集合,其中每个任务表示客户的异步处理。QueryAsyncSelect
我们将每个客户传递给该方法,该方法执行一些异步工作(在此示例中模拟)。ProcessCustomerTask.Delay
最后,我们习惯于等待所有客户处理任务完成,然后再退出该方法。Task.WhenAll
这种方法利用异步编程的强大功能来并发处理客户,从而提高应用程序的整体性能和响应能力。⏱️
问题陈述:使用多映射查询订单及其关联项目。
public async Task<Order> GetOrderWithItemsAsync(int orderId)
{
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
var query = @"
SELECT o.*, i.*
FROM Orders o
INNER JOIN Items i ON o.Id = i.OrderId
WHERE o.Id = @OrderId";
var orderDict = new Dictionary<int, Order>();
var order = await connection.QueryAsync<Order, Item, Order>(
query,
(order, item) =>
{
if (!orderDict.TryGetValue(order.Id, out var currentOrder))
{
currentOrder = order;
currentOrder.Items = new List<Item>();
orderDict.Add(currentOrder.Id, currentOrder);
}
currentOrder.Items.Add(item);
return currentOrder;
},
new { OrderId = orderId },
splitOn: "Id"
).Distinct().SingleOrDefault();
return order;
}
问题陈述:基于可选的筛选器参数生成动态查询,用于用户搜索。
public async Task<IEnumerable<User>> SearchUsersAsync(string name, string email)
{
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
var sql = new StringBuilder("SELECT * FROM Users WHERE 1 = 1");
var parameters = new DynamicParameters();
if (!string.IsNullOrEmpty(name))
{
sql.Append(" AND Name LIKE @Name");
parameters.Add("Name", $"%{name}%");
}
if (!string.IsNullOrEmpty(email))
{
sql.Append(" AND Email LIKE @Email");
parameters.Add("Email", $"%{email}%");
}
return await connection.QueryAsync<User>(sql.ToString(), parameters);
}
问题陈述:优化数据检索操作以高效处理大型数据集。
public async Task<IEnumerable<User>> GetAllUsersBufferedAsync()
{
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
var query = "SELECT * FROM Users";
// Setting buffered to false is useful for processing large data streams without holding everything in memory
var users = await connection.QueryAsync<User>(query, buffered: false);
return users;
}
问题陈述:自定义数据库列到实体中不遵循常规命名的属性的映射。
public class CustomUser
{
public int UserId { get; set; } // Custom mapping from 'ID' column
public string UserName { get; set; } // Custom mapping from 'Name' column
}
public async Task<IEnumerable<CustomUser>> GetAllCustomUsersAsync()
{
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
var query = "SELECT ID as UserId, Name as UserName FROM Users";
return await connection.QueryAsync<CustomUser>(query);
}
问题陈述:高效处理批量插入操作,以最大程度地减少数据库往返。
public async Task InsertUsersBulkAsync(List<User> users)
{
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
var dataTable = new DataTable();
dataTable.Columns.Add("Name", typeof(string));
dataTable.Columns.Add("Email", typeof(string));
foreach (var user in users)
{
dataTable.Rows.Add(user.Name, user.Email);
}
using var bulkCopy = new SqlBulkCopy(connection)
{
DestinationTableName = "Users",
BatchSize = 1000
};
bulkCopy.ColumnMappings.Add("Name", "Name");
bulkCopy.ColumnMappings.Add("Email", "Email");
await bulkCopy.WriteToServerAsync(dataTable);
}
问题陈述:实现可靠的错误处理机制,以优雅地管理与数据库相关的异常。
public async Task UpdateUserAsync(User user)
{
using var connection = new SqlConnection(connectionString);
try
{
await connection.OpenAsync();
var query = "UPDATE Users SET Name = @Name, Email = @Email WHERE Id = @Id";
var result = await connection.ExecuteAsync(query, user);
if (result == 0)
{
throw new InvalidOperationException("No user found with the specified ID.");
}
}
catch (SqlException ex)
{
// Log error or handle it based on the exception type
Console.WriteLine($"Database operation failed: {ex.Message}");
throw; // Rethrow to handle it further up the call stack
}
}
问题陈述:使用依赖项注入在 .NET Core 应用程序中实现 Dapper,以实现更好的可管理性和测试。
public interface IDatabaseService
{
Task<IEnumerable<User>> GetAllUsersAsync();
}
public class DatabaseService : IDatabaseService
{
private readonly IConfiguration _configuration;
public DatabaseService(IConfiguration configuration)
{
_configuration = configuration;
}
public async Task<IEnumerable<User>> GetAllUsersAsync()
{
var connectionString = _configuration.GetConnectionString("DefaultConnection");
using var connection = new SqlConnection(connectionString);
return await connection.QueryAsync<User>("SELECT * FROM Users");
}
}
// In your Startup.cs or Program.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IDatabaseService, DatabaseService>();
}
这种与依赖注入的集成不仅使应用程序更简洁,而且还允许您在单元测试中模拟数据库操作,从而增强了其可测试性。
问题陈述:使用 Dapper 的命令查询责任分离 (CQRS) 模式设计可扩展的体系结构。
// Command Model
public class CreateUserCommand
{
public string Name { get; set; }
public string Email { get; set; }
}
public class CommandHandler
{
private readonly IDbConnection _connection;
public CommandHandler(IDbConnection connection)
{
_connection = connection;
}
public async Task<int> Handle(CreateUserCommand command)
{
var sql = "INSERT INTO Users (Name, Email) VALUES (@Name, @Email)";
return await _connection.ExecuteAsync(sql, command);
}
}
// Query Model
public class UserQuery
{
private readonly IDbConnection _connection;
public UserQuery(IDbConnection connection)
{
_connection = connection;
}
public async Task<IEnumerable<User>> GetAllUsers()
{
return await _connection.QueryAsync<User>("SELECT * FROM Users");
}
}
此示例演示如何分离读取和写入操作,这对于随时间扩展和演变的系统至关重要。这种分离允许更优化的性能和可伸缩性。
问题陈述:在处理大型数据集的场景中优化 Dapper 的性能。
public async Task<IEnumerable<User>> GetUsersWithOptimizedPerformance()
{
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
var query = "SELECT * FROM Users";
// Use AsQueryable to defer execution and reduce memory footprint
return connection.Query<User>(query).AsQueryable().Take(1000);
}
问题陈述:在微服务架构中使用 Dapper 来确保轻量级和高效的数据库交互。
public class UserService : IUserService
{
private readonly IDbConnection _connection;
public UserService(IDbConnection connection)
{
_connection = connection;
}
public async Task<User> GetUserByIdAsync(int id)
{
var query = "SELECT * FROM Users WHERE Id = @Id";
return await _connection.QuerySingleOrDefaultAsync<User>(query, new { Id = id });
}
}
// Setup in Startup.cs for a Microservice
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IDbConnection>(sp =>
new SqlConnection(Configuration.GetConnectionString("UserServiceDB")));
services.AddScoped\<IUserService, UserService>();
}
此设置展示了如何在微服务架构中有效地使用 Dapper,为每个服务提供自己的轻量级数据访问机制,而不会给服务带来繁重的 ORM 库负担。
问题陈述:使用 Dapper 实现事件溯源,以将应用程序状态的所有更改捕获为一系列事件。
public class EventStore : IEventStore
{
private readonly IDbConnection _connection;
public EventStore(IDbConnection connection)
{
_connection = connection;
}
public async Task SaveEventsAsync(Guid aggregateId, IEnumerable<Event> events, int expectedVersion)
{
foreach (var event in events)
{
var query = "INSERT INTO Events (AggregateId, EventData, Version) VALUES (@AggregateId, @EventData, @Version)";
await _connection.ExecuteAsync(query, new {
AggregateId = aggregateId,
EventData = JsonConvert.SerializeObject(event),
Version = ++expectedVersion
});
}
}
}
这种方法使系统更具弹性,并提供完整的变更审计跟踪,这在需要高度可追溯性和可审计性的系统中特别有用。
问题陈述:通过使用 Dapper 实现内存缓存策略来减少数据库负载。
public class CachedUserService : IUserService
{
private readonly IDbConnection _connection;
private readonly IMemoryCache _cache;
public CachedUserService(IDbConnection connection, IMemoryCache cache)
{
_connection = connection;
_cache = cache;
}
public async Task<User> GetUserByIdAsync(int id)
{
if (!_cache.TryGetValue($"User_{id}", out User user))
{
user = await _connection.QuerySingleOrDefaultAsync<User>("SELECT * FROM Users WHERE Id = @Id", new { Id = id });
_cache.Set($"User_{id}", user, TimeSpan.FromMinutes(30));
}
return user;
}
}
此示例演示了添加缓存层如何通过减少对数据库的查询数来显著提高应用程序的响应能力,从而降低总体负载。
问题陈述:使用 Dapper 高效处理大型数据流,以防止内存消耗过高并提高应用程序响应能力。
public async IAsyncEnumerable<User> StreamUsersAsync()
{
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
var query = "SELECT * FROM Users";
var reader = await connection.ExecuteReaderAsync(query);
while (await reader.ReadAsync())
{
yield return new User
{
Id = reader.GetInt32(reader.GetOrdinal("Id")),
Name = reader.GetString(reader.GetOrdinal("Name")),
Email = reader.GetString(reader.GetOrdinal("Email"))
};
}
}
此实现利用 C# 8.0 的异步流 () 在从数据库中读取行时有效地处理行,这对于在不消耗大量系统资源的情况下处理大型数据集特别有用。IAsyncEnumerable
问题陈述:实施增强的安全措施,以保护通过 Dapper 访问的敏感数据。
public async Task<User> GetUserSecurelyAsync(int id)
{
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
var query = "SELECT Id, Name, Email FROM Users WHERE Id = @Id FOR JSON PATH, WITHOUT_ARRAY_WRAPPER";
var result = await connection.QuerySingleOrDefaultAsync<string>(query, new { Id = id });
var user = JsonConvert.DeserializeObject<User>(result);
// Assume some decryption logic here if necessary
return user;
}
// Example using SQL Server's FOR JSON to secure data format and minimize injection risks
结合高级安全功能(例如用于更安全的数据处理和潜在加密/解密实践的 JSON 输出模式)可确保您的应用程序在应对新出现的威胁时保持安全。
下面是一些在 .NET Core 应用程序中使用 Dapper 时要牢记的最佳做法和提示:
Dapper 是一种功能强大且高效的微型 ORM,可简化 .NET Core 应用程序中的数据库访问。它的简单性、性能和灵活性使其成为重视速度和对数据库交互的控制的开发人员的绝佳选择。
在本文中,我们探讨了 Dapper 的各个方面,从基本用法到多个结果集、存储过程、事务和异步操作等高级概念。我们还讨论了最佳实践和技巧,以帮助您编写干净、可维护和可缩放的代码。
请记住,Dapper 成功的关键在于了解其优势、应用最佳实践并有效利用其功能。通过遵循 SOLID、干净的代码和性能优化的原则,可以构建利用 Dapper 强大功能的高质量、高效的 .NET Core 应用程序。
因此,请继续深入了解 Dapper 的世界,并在 .NET Core 项目中释放高性能数据库访问的潜力!