如何在 .NET 中使用 Dapper 处理数据库连接?
Posted
技术标签:
【中文标题】如何在 .NET 中使用 Dapper 处理数据库连接?【英文标题】:How do I handle Database Connections with Dapper in .NET? 【发布时间】:2012-03-02 09:28:48 【问题描述】:我一直在使用 Dapper,但我不确定处理数据库连接的最佳方式。
大多数示例显示了在示例类中,甚至在每个方法中创建的连接对象。但是我觉得在每个 clss 中引用一个连接字符串是错误的,即使它是从 web.config 中提取的。
我的经验是使用 DbDataContext
或 DbContext
与 Linq to SQL 或实体框架,所以这对我来说是新的。
当使用 Dapper 作为我的数据访问策略时,我如何构建我的 Web 应用程序?
【问题讨论】:
为时已晚,但是;我是这样实现的:***.com/a/45029588/5779732 using-dapper-asynchronously-in-asp-net-core-2 - exceptionnotfound.net/… 【参考方案1】:更新:MarredCheese 评论的澄清:
"不需要使用using语句。Dapper会自动打开, 关闭,并为您处理连接。”这是不正确的。 Dapper 会自动打开关闭的连接,它会 自动关闭连接that it auto-opened,但不会 自动处理连接。 Marc Gravell 和 Eric Lippert 两者都提倡与 Dapper 一起使用 here。
Microsoft.AspNetCore.All:v2.0.3 | 小巧玲珑:v1.50.2
我不确定我是否正确使用了最佳实践,但我这样做是为了处理多个连接字符串。
如果你只有 1 个连接字符串很容易
Startup.cs
using System.Data;
using System.Data.SqlClient;
namespace DL.SO.Project.Web.UI
public class Startup
public IConfiguration Configuration get; private set;
// ......
public void ConfigureServices(IServiceCollection services)
// Read the connection string from appsettings.
string dbConnectionString = this.Configuration.GetConnectionString("dbConnection1");
// Inject IDbConnection, with implementation from SqlConnection class.
services.AddTransient<IDbConnection>((sp) => new SqlConnection(dbConnectionString));
// Register your regular repositories
services.AddScoped<IDiameterRepository, DiameterRepository>();
// ......
DiameterRepository.cs
using Dapper;
using System.Data;
namespace DL.SO.Project.Persistence.Dapper.Repositories
public class DiameterRepository : IDiameterRepository
private readonly IDbConnection _dbConnection;
public DiameterRepository(IDbConnection dbConnection)
_dbConnection = dbConnection;
public IEnumerable<Diameter> GetAll()
const string sql = @"SELECT * FROM TABLE";
// No need to use using statement. Dapper will automatically
// open, close and dispose the connection for you.
return _dbConnection.Query<Diameter>(sql);
// ......
如果您有超过 1 个连接字符串会出现问题
由于Dapper
使用IDbConnection
,你需要想办法区分不同的数据库连接。
我尝试创建多个接口,从IDbConnection
“继承”,对应于不同的数据库连接,并在Startup
上注入具有不同数据库连接字符串的SqlConnection
。
失败是因为SqlConnection
继承自DbConnection
,而DbConnection
不仅补充了IDbConnection
,还补充了Component
类。因此,您的自定义接口将无法仅使用 SqlConnection
实现。
我还尝试创建自己的DbConnection
类,它采用不同的连接字符串。这太复杂了,因为您必须实现 DbConnection
类中的所有方法。你失去了来自SqlConnection
的帮助。
我最终做了什么
-
在
Startup
期间,我将所有连接字符串值加载到字典中。我还为所有数据库连接名称创建了一个enum
,以避免使用魔法字符串。
我将字典作为 Singleton 注入。
我没有注入IDbConnection
,而是创建了IDbConnectionFactory
,并将其作为瞬态注入所有存储库。现在所有存储库都采用IDbConnectionFactory
而不是IDbConnection
。
何时选择正确的连接?在所有存储库的构造函数中!为了使事情变得干净,我创建了存储库基类并让存储库从基类继承。正确的连接字符串选择可以发生在基类中。
数据库连接名称.cs
namespace DL.SO.Project.Domain.Repositories
public enum DatabaseConnectionName
Connection1,
Connection2
IDbConnectionFactory.cs
using System.Data;
namespace DL.SO.Project.Domain.Repositories
public interface IDbConnectionFactory
IDbConnection CreateDbConnection(DatabaseConnectionName connectionName);
DapperDbConenctionFactory - 我自己的工厂实现
namespace DL.SO.Project.Persistence.Dapper
public class DapperDbConnectionFactory : IDbConnectionFactory
private readonly IDictionary<DatabaseConnectionName, string> _connectionDict;
public DapperDbConnectionFactory(IDictionary<DatabaseConnectionName, string> connectionDict)
_connectionDict = connectionDict;
public IDbConnection CreateDbConnection(DatabaseConnectionName connectionName)
string connectionString = null;
if (_connectDict.TryGetValue(connectionName, out connectionString))
return new SqlConnection(connectionString);
throw new ArgumentNullException();
Startup.cs
namespace DL.SO.Project.Web.UI
public class Startup
// ......
public void ConfigureServices(IServiceCollection services)
var connectionDict = new Dictionary<DatabaseConnectionName, string>
DatabaseConnectionName.Connection1, this.Configuration.GetConnectionString("dbConnection1") ,
DatabaseConnectionName.Connection2, this.Configuration.GetConnectionString("dbConnection2")
;
// Inject this dict
services.AddSingleton<IDictionary<DatabaseConnectionName, string>>(connectionDict);
// Inject the factory
services.AddTransient<IDbConnectionFactory, DapperDbConnectionFactory>();
// Register your regular repositories
services.AddScoped<IDiameterRepository, DiameterRepository>();
// ......
DiameterRepository.cs
using Dapper;
using System.Data;
namespace DL.SO.Project.Persistence.Dapper.Repositories
// Move the responsibility of picking the right connection string
// into an abstract base class so that I don't have to duplicate
// the right connection selection code in each repository.
public class DiameterRepository : DbConnection1RepositoryBase, IDiameterRepository
public DiameterRepository(IDbConnectionFactory dbConnectionFactory)
: base(dbConnectionFactory)
public IEnumerable<Diameter> GetAll()
const string sql = @"SELECT * FROM TABLE";
// No need to use using statement. Dapper will automatically
// open, close and dispose the connection for you.
return base.DbConnection.Query<Diameter>(sql);
// ......
DbConnection1RepositoryBase.cs
using System.Data;
using DL.SO.Project.Domain.Repositories;
namespace DL.SO.Project.Persistence.Dapper
public abstract class DbConnection1RepositoryBase
public IDbConnection DbConnection get; private set;
public DbConnection1RepositoryBase(IDbConnectionFactory dbConnectionFactory)
// Now it's the time to pick the right connection string!
// Enum is used. No magic string!
this.DbConnection = dbConnectionFactory.CreateDbConnection(DatabaseConnectionName.Connection1);
然后对于需要与其他连接进行通信的其他存储库,您可以为它们创建不同的存储库基类。
using System.Data;
using DL.SO.Project.Domain.Repositories;
namespace DL.SO.Project.Persistence.Dapper
public abstract class DbConnection2RepositoryBase
public IDbConnection DbConnection get; private set;
public DbConnection2RepositoryBase(IDbConnectionFactory dbConnectionFactory)
this.DbConnection = dbConnectionFactory.CreateDbConnection(DatabaseConnectionName.Connection2);
using Dapper;
using System.Data;
namespace DL.SO.Project.Persistence.Dapper.Repositories
public class ParameterRepository : DbConnection2RepositoryBase, IParameterRepository
public ParameterRepository (IDbConnectionFactory dbConnectionFactory)
: base(dbConnectionFactory)
public IEnumerable<Parameter> GetAll()
const string sql = @"SELECT * FROM TABLE";
return base.DbConnection.Query<Parameter>(sql);
// ......
希望所有这些帮助。
【讨论】:
正是我正在寻找的。我遇到了同样的问题并以同样的方式解决了它,我仍然不知道这是否是一个好习惯,但在我看来,我认为是。 为 IServiceProvider 范围注册 IDbConnection 会更好吗?可以创建服务并注册为具有不同连接并使用 var scope = factory.CreateNonDefaultScope(); 的单例范围工厂;使用 var connection = scope.ServiceProvider.GetRequiredServiceusing
语句。Dapper 将自动为您打开、关闭和处理连接。"这是不正确的。 Dapper 会自动打开关闭的连接,它会自动关闭连接that it auto-opened,但不会自动释放连接。 Marc Gravell 和 Eric Lippert 都提倡使用 using
和 Dapper here。
@DavidLiang 那么请您更新您说“无需使用 using 语句...”的部分我也对此感到困惑,直到我随机扩展评论部分,幸运的是我看到了 MarredCheese 的评论,顺便说一句,感谢您的有用解释!【参考方案2】:
这是大约 4 年前被问到的……但无论如何,也许答案对这里的人有用:
我在所有项目中都这样做。 首先,我创建了一个基类,其中包含一些这样的辅助方法:
public class BaseRepository
protected T QueryFirstOrDefault<T>(string sql, object parameters = null)
using (var connection = CreateConnection())
return connection.QueryFirstOrDefault<T>(sql, parameters);
protected List<T> Query<T>(string sql, object parameters = null)
using (var connection = CreateConnection())
return connection.Query<T>(sql, parameters).ToList();
protected int Execute(string sql, object parameters = null)
using (var connection = CreateConnection())
return connection.Execute(sql, parameters);
// Other Helpers...
private IDbConnection CreateConnection()
var connection = new SqlConnection(...);
// Properly initialize your connection here.
return connection;
有了这样一个基类,我可以轻松地创建真正的存储库,而无需任何样板代码:
public class AccountsRepository : BaseRepository
public Account GetById(int id)
return QueryFirstOrDefault<Account>("SELECT * FROM Accounts WHERE Id = @Id", new id );
public List<Account> GetAll()
return Query<Account>("SELECT * FROM Accounts ORDER BY Name");
// Other methods...
所以所有与 Dapper、SqlConnection-s 和其他数据库访问相关的代码都位于一个地方(BaseRepository)。所有真正的存储库都是干净简单的 1 行方法。
我希望它会对某人有所帮助。
【讨论】:
BaseRepository
是不必要的继承,因为它不提供任何公共或抽象方法或属性。这可能是一个 DBHelper
类。
将CreateConnection
移动到自己的班级会更好吗?
可能是......但我个人喜欢保持一切简单。如果您在 CreateConnection(...) 中有很多逻辑,那可能是个好主意。在我的项目中,这个方法就像“return new Connection(connectionString)”一样简单,所以它可以在没有单独的 CreateConnection(...) 方法的情况下内联使用。
另外,正如 nick-s 所指出的,在最新版本的 Dapper 中,您不需要手动打开数据库连接。 Dapper 会自动为您打开它。更新了帖子。
顺便说一句,如果我们使用一些 DI 框架,我们并不总是需要使用接口。如果我们编写单元测试并模拟我们的存储库,我们肯定需要接口。如果没有,我们可以使用没有接口的类。此外,通常我们不需要使用 AddScoped 或 AddTransient,因为存储库没有任何状态(不是静态变量),我们可以使用 AddSingleton 代替并节省一些 CPU 周期以进行更有用的工作。 (这部分与@FelixOuttaSpace 评论有关)【参考方案3】:
我使用从配置中检索连接字符串的属性创建了扩展方法。这让调用者不必知道有关连接的任何信息,无论它是打开还是关闭等。此方法确实会限制您,因为您隐藏了一些 Dapper 功能,但在我们相当简单的应用程序中,它对我们来说工作得很好,如果我们需要 Dapper 的更多功能,我们总是可以添加一个新的扩展方法来公开它。
internal static string ConnectionString = new Configuration().ConnectionString;
internal static IEnumerable<T> Query<T>(string sql, object param = null)
using (SqlConnection conn = new SqlConnection(ConnectionString))
conn.Open();
return conn.Query<T>(sql, param);
internal static int Execute(string sql, object param = null)
using (SqlConnection conn = new SqlConnection(ConnectionString))
conn.Open();
return conn.Execute(sql, param);
【讨论】:
这里有一个问题。由于 conn.Query 返回 IEnumerablebuffered: false
?【参考方案4】:
我是这样做的:
internal class Repository : IRepository
private readonly Func<IDbConnection> _connectionFactory;
public Repository(Func<IDbConnection> connectionFactory)
_connectionFactory = connectionFactory;
public IWidget Get(string key)
using(var conn = _connectionFactory())
return conn.Query<Widget>(
"select * from widgets with(nolock) where widgetkey=@WidgetKey", new WidgetKey=key );
然后,无论我在哪里连接我的依赖项(例如:Global.asax.cs 或 Startup.cs),我都会执行以下操作:
var connectionFactory = new Func<IDbConnection>(() =>
var conn = new SqlConnection(
ConfigurationManager.ConnectionStrings["connectionString-name"];
conn.Open();
return conn;
);
【讨论】:
这里有一个问题。由于 conn.Query 返回 IenumerableIEnumerable<T>
已经实现。如果您通过buffered: false
,是的,您需要在退出using
块之前消耗输出。【参考方案5】:
最佳实践是一个真正的加载术语。我喜欢DbDataContext
风格的容器,比如Dapper.Rainbow 推广。它允许您耦合CommandTimeout
、事务和其他助手。
例如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using Dapper;
// to have a play, install Dapper.Rainbow from nuget
namespace TestDapper
class Program
// no decorations, base class, attributes, etc
class Product
public int Id get; set;
public string Name get; set;
public string Description get; set;
public DateTime? LastPurchase get; set;
// container with all the tables
class MyDatabase : Database<MyDatabase>
public Table<Product> Products get; set;
static void Main(string[] args)
var cnn = new SqlConnection("Data Source=.;Initial Catalog=tempdb;Integrated Security=True");
cnn.Open();
var db = MyDatabase.Init(cnn, commandTimeout: 2);
try
db.Execute("waitfor delay '00:00:03'");
catch (Exception)
Console.WriteLine("yeah ... it timed out");
db.Execute("if object_id('Products') is not null drop table Products");
db.Execute(@"create table Products (
Id int identity(1,1) primary key,
Name varchar(20),
Description varchar(max),
LastPurchase datetime)");
int? productId = db.Products.Insert(new Name="Hello", Description="Nothing" );
var product = db.Products.Get((int)productId);
product.Description = "untracked change";
// snapshotter tracks which fields change on the object
var s = Snapshotter.Start(product);
product.LastPurchase = DateTime.UtcNow;
product.Name += " World";
// run: update Products set LastPurchase = @utcNow, Name = @name where Id = @id
// note, this does not touch untracked columns
db.Products.Update(product.Id, s.Diff());
// reload
product = db.Products.Get(product.Id);
Console.WriteLine("id: 0 name: 1 desc: 2 last 3", product.Id, product.Name, product.Description, product.LastPurchase);
// id: 1 name: Hello World desc: Nothing last 12/01/2012 5:49:34 AM
Console.WriteLine("deleted: 0", db.Products.Delete(product.Id));
// deleted: True
Console.ReadKey();
【讨论】:
OP 不是在询问有关 SqlConnection([[CONN STRING HERE]]) 部分的更多信息吗?他说“但是在每个类中(甚至在每个方法中)引用连接字符串对我来说感觉不对”我想他想知道我们 Dapper 用户是否已经生成了一种模式(某种)围绕将事物的连接创建方面包装到干燥/隐藏该逻辑。 (除了 OP,如果您可以使用 Dapper.Rainbow,请这样做......这真的很好!)【参考方案6】:试试这个:
public class ConnectionProvider
DbConnection conn;
string connectionString;
DbProviderFactory factory;
// Constructor that retrieves the connectionString from the config file
public ConnectionProvider()
this.connectionString = ConfigurationManager.ConnectionStrings[0].ConnectionString.ToString();
factory = DbProviderFactories.GetFactory(ConfigurationManager.ConnectionStrings[0].ProviderName.ToString());
// Constructor that accepts the connectionString and Database ProviderName i.e SQL or Oracle
public ConnectionProvider(string connectionString, string connectionProviderName)
this.connectionString = connectionString;
factory = DbProviderFactories.GetFactory(connectionProviderName);
// Only inherited classes can call this.
public DbConnection GetOpenConnection()
conn = factory.CreateConnection();
conn.ConnectionString = this.connectionString;
conn.Open();
return conn;
【讨论】:
您如何处理解决方案中的关闭/处置连接? @JPshook - 我相信他正在使用。 (参考***.com/a/4717859/2133703)【参考方案7】:似乎每个人都过早地打开他们的连接?我也有同样的问题,在这里挖掘了源代码后 - https://github.com/StackExchange/dapper-dot-net/blob/master/Dapper/SqlMapper.cs
您会发现每次与数据库的交互都会检查连接以查看它是否已关闭,并在必要时打开它。因此,我们只使用上面没有 conn.open() 的 using 语句。这样,连接就会在尽可能接近交互的地方打开。如果您注意到,它也会立即关闭连接。这也将比在处置过程中自动关闭更快。
上面的 repo 中的许多例子之一:
private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action<IDbCommand, object> paramReader)
IDbCommand cmd = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
cmd = command.SetupCommand(cnn, paramReader);
if (wasClosed) cnn.Open();
int result = cmd.ExecuteNonQuery();
command.OnCompleted();
return result;
finally
if (wasClosed) cnn.Close();
cmd?.Dispose();
下面是一个小例子,说明我们如何为 Dapper 使用 Wrapper,称为 DapperWrapper。这允许我们包装所有 Dapper 和 Simple Crud 方法来管理连接、提供安全性、日志记录等。
public class DapperWrapper : IDapperWrapper
public IEnumerable<T> Query<T>(string query, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
using (var conn = Db.NewConnection())
var results = conn.Query<T>(query, param, transaction, buffered, commandTimeout, commandType);
// Do whatever you want with the results here
// Such as Security, Logging, Etc.
return results;
【讨论】:
这真的很有用,因为 Dapper 会在连接时保持打开状态,如果它在获得连接时已经打开。我现在在通过/使用 Dapper 之前预先打开数据库连接,我获得了 6 倍的性能提升 - 谢谢! 事务由连接创建,如IDbTransaction tran = conn.BeginTransaction();
。在您的代码中,包装器创建自己的连接。因此,包装器的事务参数被破坏/误导,不是吗?
@MarredCheese 我已经有几年没有使用 Dapper 了。但是,我相信当我们使用这个包装器时,我们使用的 TransactionScope 类似于这里的讨论 - ***.com/a/10363978/3845625。 Dapper * 可能足够聪明,可以将来自多个连接的事务连接在一起,但我不确定这一点。这是来源 - github.com/DapperLib/Dapper/blob/… 祝你好运。【参考方案8】:
我用辅助类包装连接:
public class ConnectionFactory
private readonly string _connectionName;
public ConnectionFactory(string connectionName)
_connectionName = connectionName;
public IDbConnection NewConnection() => new SqlConnection(_connectionName);
#region Connection Scopes
public TResult Scope<TResult>(Func<IDbConnection, TResult> func)
using (var connection = NewConnection())
connection.Open();
return func(connection);
public async Task<TResult> ScopeAsync<TResult>(Func<IDbConnection, Task<TResult>> funcAsync)
using (var connection = NewConnection())
connection.Open();
return await funcAsync(connection);
public void Scope(Action<IDbConnection> func)
using (var connection = NewConnection())
connection.Open();
func(connection);
public async Task ScopeAsync<TResult>(Func<IDbConnection, Task> funcAsync)
using (var connection = NewConnection())
connection.Open();
await funcAsync(connection);
#endregion Connection Scopes
使用示例:
public class PostsService
protected IConnectionFactory Connection;
// Initialization here ..
public async Task TestPosts_Async()
// Normal way..
var posts = Connection.Scope(cnn =>
var state = PostState.Active;
return cnn.Query<Post>("SELECT * FROM [Posts] WHERE [State] = @state;", new state );
);
// Async way..
posts = await Connection.ScopeAsync(cnn =>
var state = PostState.Active;
return cnn.QueryAsync<Post>("SELECT * FROM [Posts] WHERE [State] = @state;", new state );
);
所以我不必每次都显式打开连接。 此外,为了方便未来的重构,您可以这样使用它:
var posts = Connection.Scope(cnn =>
var state = PostState.Active;
return cnn.Query<Post>($"SELECT * FROM [TableName<Post>()] WHERE [nameof(Post.State)] = @nameof(state);", new state );
);
TableName<T>()
是什么this answer。
【讨论】:
【参考方案9】:嗨@donaldhughes 我也是新手,我经常这样做: 1 - 创建一个类来获取我的连接字符串 2 - 在 Using 中调用连接字符串类
看:
DapperConnection.cs
public class DapperConnection
public IDbConnection DapperCon
get
return new SqlConnection(ConfigurationManager.ConnectionStrings["Default"].ToString());
DapperRepository.cs
public class DapperRepository : DapperConnection
public IEnumerable<TBMobileDetails> ListAllMobile()
using (IDbConnection con = DapperCon )
con.Open();
string query = "select * from Table";
return con.Query<TableEntity>(query);
而且效果很好。
【讨论】:
以上是关于如何在 .NET 中使用 Dapper 处理数据库连接?的主要内容,如果未能解决你的问题,请参考以下文章