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

Don*_*hes 68 .net c# dapper

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

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

我的经验是使用DbDataContextDbContext使用Linq to SQL或Entity Framework,所以这对我来说是新的.

使用Dapper作为我的数据访问策略时,如何构建我的Web应用程序?

Sha*_*ard 25

我创建了一个扩展方法,其中包含一个从配置中检索连接字符串的属性.这使得调用者不必知道关于连接的任何信息,无论是打开还是关闭等等.这个方法确实限制了你,因为你隐藏了一些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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • 上面代码的问题是,如果将buffered:true传递给Query方法,连接将在返回数据之前进行处理.在内部,Dapper会在返回之前将可枚举变为列表. (3认同)
  • 如果您使用的是 dapper Query 扩展方法,则不需要显式打开连接,因为它是在方法本身中完成的。 (2认同)

Dav*_*ang 24

Microsoft.AspNetCore.All:v2.0.3 | 小巧玲珑:v1.50.2

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

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

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

            // ......
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

        // ......
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您有多个连接字符串,则会出现问题

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
    }
}
Run Code Online (Sandbox Code Playgroud)

IDbConnectionFactory.cs

using System.Data;

namespace DL.SO.Project.Domain.Repositories
{
    public interface IDbConnectionFactory
    {
        IDbConnection CreateDbConnection(DatabaseConnectionName connectionName);
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

            // ......
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

        // ......
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,对于需要与其他连接进行通信的其他存储库,您可以为它们创建不同的存储库基类.

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

        // ......
    }
}
Run Code Online (Sandbox Code Playgroud)

希望所有这些帮助.

  • “无需使用 `using` 语句。Dapper 会自动为您打开、关闭和处置连接。” 这是不正确的。Dapper 会自动打开关闭的连接,并且会自动关闭[自动打开的连接](https://github.com/DapperLib/Dapper/issues/672#issuecomment-269972918),但它不会自动释放连接。Marc Gravell 和 Eric Lippert 都主张在 Dapper 中使用“using”[此处](/sf/ask/2028384711/)。 (2认同)

Pav*_*kov 20

大约在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

有了这样的基类,我可以轻松地创建真正的存储库,而无需任何样板代码:

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...
}
Run Code Online (Sandbox Code Playgroud)

所以与Dapper,SqlConnection-s和其他数据库访问相关的所有代码都位于一个地方(BaseRepository).所有真正的存储库都是简洁的1行方法.

我希望它能帮助别人.

  • `BaseRepository` 是不必要的继承,因为它不提供任何公共或抽象方法或属性。这可能是一个`DBHelper` 类。 (4认同)

小智 7

我是这样做的:

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 });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,无论我在哪里连接我的依赖项(例如:Global.asax.cs或Startup.cs),我都会这样做:

var connectionFactory = new Func<IDbConnection>(() => {
    var conn = new SqlConnection(
        ConfigurationManager.ConnectionStrings["connectionString-name"];
    conn.Open();
    return conn;
});
Run Code Online (Sandbox Code Playgroud)


Sam*_*ron 6

最佳实践是一个真正的加载术语.我喜欢DbDataContextDapper.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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 是不是OP要求更多关于SqlConnection([[CONN STRING HERE]])部分?他说:"但是我在每个类中引用连接字符串都感觉不对(即使在每种方法中也是如此)"我认为他想知道我们Dapper用户是否已经生成了一种模式(包括各种各样的模式),将连接创建方面包装起来干/隐藏那个逻辑.(除了OP之外,如果你可以使用Dapper.Rainbow,那么这样做......真的很棒!) (14认同)