如何在.NET中使用Dapper处理数据库连接?


83

我一直在与Dapper一起玩,但是我不确定处理数据库连接的最佳方法。

大多数示例显示在示例类甚至每个方法中创建的连接对象。但是我觉得在每个clss中引用一个连接字符串是错误的,即使它是从we​​b.config中提取的。

我的经验是对SQL或Entity Framework使用LinqDbDataContextDbContext与Linq一起使用,因此这对我来说是新的。

将Dapper用作数据访问策略时,如何构造Web应用程序?


为时已晚,我是这样实现的:stackoverflow.com/a/45029588/5779732
Amit Joshi

使用-hardy中-异步式-ASP净核心-2 - exceptionnotfound.net/...
喜马拉雅加尔格

Answers:


48

Microsoft.AspNetCore.All:v2.0.3 | Dapper:v1.50.2

我不确定我是否正确地使用了最佳实践,但是我这样做是为了处理多个连接字符串。

如果只有一个连接字符串,这很容易

启动文件

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,分别对应于不同的数据库连接,并在上注入SqlConnection不同的数据库连接字符串Startup

这失败,因为SqlConnection从继承DbConnectionDbConnectioninplements不仅IDbConnection还要Component类。因此,您的自定义界面将无法仅使用这种SqlConnection功能。

我还尝试创建自己的使用DbConnection不同连接字符串的类。这太复杂了,因为您必须实现DbConnection类中的所有方法。您失去了的帮助SqlConnection

我最终要做什么

  1. 在期间Startup,我将所有连接字符串值加载到字典中。我还enum为所有数据库连接名称创建了一个以避免魔术字符串。
  2. 我将字典作为Singleton注入。
  3. 相反注射的IDbConnection,我创建IDbConnectionFactory并注入作为瞬态所有存储库。现在,所有的仓库取IDbConnectionFactory来代替IDbConnection
  4. 何时选择正确的连接?在所有存储库的构造函数中!为了使内容整洁,我创建了存储库基类,并使存储库从基类继承。正确的连接字符串选择可以在基类中进行。

DatabaseConnectionName.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();
        }
    }
}

启动文件

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);
        }

        // ......
    }
}

希望所有这些帮助。


正是我在寻找。我遇到了相同的问题,并以相同的方式解决了该问题,但我仍然不知道这是否是一个好习惯,但我认为是。
Ewerton

1
为IServiceProvider范围注册IDbConnection会更好吗?可以使用不同的连接并使用var scope = factory.CreateNonDefaultScope();创建服务并将其注册为单例作用域工厂。使用var connection = scope.ServiceProvider.GetRequiredService <IDbConnection>(),您将获得非默认连接。较少的继承也将有助于扩展性……
也是

27

我创建了具有属性的扩展方法,该属性从配置中检索连接字符串。这使调用者不必知道任何有关连接的信息,无论它是打开还是关闭等。由于隐藏了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);
        }
    }

1
这里有一个问题。由于conn.Query返回IEnumerable <T>,立即处置连接对象是否安全?IEnumerable不需要连接以便在读取元素时实现它们吗?我们应该运行一个ToList()吗?
阿德里安·纳苏

我必须回到Dapper进行验证,但是我很确定我采用了这种模式,就像在生产代码中那样。没关系-当然,您应该在互联网上测试任何代码。
肖恩·哈伯德

1
如果使用的是dapper Query扩展方法,则不需要显式打开连接,因为它是在方法本身中完成的。
h-rai

4
上面的代码的问题在于,如果您将buffer:true传递给Query方法,则连接将在返回数据之前释放。在内部,Dapper会在返回之前将可枚举数转换为列表。
布莱恩·瓦莱伦加

@BrianVallelunga不会buffered: false吗?
乔德雷

24

这个问题是在大约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行方法。

希望对您有所帮助。


1
BaseRepository是不必要的继承,因为它不提供任何公共或抽象的方法或属性。相反,这可能是一DBHelper门课。
乔什·诺

CreateConnection上课可能更好 吗?
hellboy

可能是...但是个人而言,我喜欢使一切保持简单。如果CreateConnection(...)中有很多逻辑,则可能是一个好主意。在我的项目中,此方法就像“返回新的Connection(connectionString)”一样简单,因此可以在没有单独的CreateConnection(...)方法的情况下内联使用。
帕维尔·梅尔尼科夫

1
另外,正如nick-s所指出的,在最新版本的Dapper中,您无需手动打开数据库连接。Dapper会自动为您打开它。更新了帖子。
尼科夫

注入imo。services.AddScoped<IDbConnection>(p => new SqlConnection(connString)然后在需要的地方索要它
-Sinaesthetic

8

我这样做是这样的:

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返回Ienumerable <T>,立即处置该连接是否安全?IEnumerable不需要连接以便在读取元素时实现它们吗?
阿德里安·纳苏

1
@AdrianNasui:目前,Dapper的默认行为是执行SQL并在返回时缓冲整个读取器,因此IEnumerable<T>已经实现了。如果通过buffered: false,则是,您将需要在退出using块之前使用输出。
雅各布·克拉尔

7

最佳实践是一个真正的术语。我喜欢DbDataContextDapper这样的样式容器。它允许您耦合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();
        }
    }
}

15
OP是否在询问有关SqlConnection([[[CONN STRING HERE]])部分的更多信息?他说:“但是在每个类中(甚至在每个方法中)都引用连接字符串对我来说是错误的”,我想他想知道我们的Dapper用户是否围绕封装连接创建部分的内容生成了某种模式?干燥/隐藏该逻辑。(除了OP,如果可以使用Dapper.Rainbow,请这样做...确实很棒!)
ckittel 2012年

4

试试这个:

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;
        }

    }

6
您如何处理解决方案中的关闭/布置连接?
jpshook

@JPShook-我相信他正在使用。(参考文献stackoverflow.com/a/4717859/2133703
马盖

4

每个人似乎都太早打开他们的联系了吗?我有同样的问题,并且在这里浏览了源代码之后-https: //github.com/StackExchange/dapper-dot-net/blob/master/Dapper/SqlMapper.cs

您会发现与数据库的每次交互都检查连接以查看其是否关闭,并在必要时将其打开。因此,我们简单地利用了不带conn.open()的上述语句。这样,打开连接时应尽可能靠近交互。如果您注意到,它也会立即关闭连接。这也比在处置过程中自动关闭快。

上面仓库中的许多示例之一:

    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();
        }
    }

下面是一个小例子,说明如何使用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;
      }
    }
  }

1
知道Dapper在获得连接时已经打开的情况下将保持打开状态,这真的很有用。在与Dapper传递/使用数据库连接之前,我现在正在预先打开数据库连接,并且性能提高了6倍-谢谢!
克里斯·史密斯

2

我用helper类包装了连接:

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>()此答案中可以找到什么。


0

嗨,@ donaldhughes,我也是新手,我经常这样做:1-创建一个类以获取我的连接字符串2-在使用中调用连接字符串类

看:

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);
            }
        }
     }

而且效果很好。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.