Как обрабатывать соединения базы данных с Dapper в .NET?
Я играл с Dapper, но я не уверен в том, как лучше всего обращаться с подключением к базе данных.
В большинстве примеров объект соединения создается в классе примера или даже в каждом методе. Но мне нехорошо ссылаться на строку соединения в каждом clss, даже если она вытягивается из web.config.
Мой опыт заключался в использовании DbDataContext
или DbContext
с Linq to SQL или Entity Framework, поэтому это ново для меня.
Как я могу структурировать свои веб-приложения при использовании Dapper в качестве моей стратегии доступа к данным?
Ответы
Ответ 1
Я создал методы расширения с свойством, которое извлекает строку соединения из конфигурации. Это позволяет вызывающим абонентам ничего не знать о соединении, открывать или закрывать его и т.д. Этот метод немного ограничивает вас, поскольку вы скрываете некоторые функциональные возможности 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);
}
}
Ответ 2
Было спрошено около 4 лет назад... но в любом случае, может быть, ответ будет полезен кому-то здесь:
Я делаю это так во всех проектах.
Во-первых, я создаю базовый класс, который содержит несколько вспомогательных методов, таких как:
public class BaseRepository
{
protected T QueryFirstOrDefault<T>(string sql, object parameters = null)
{
using (var connection = CreateConnection())
{
connection.Open();
return connection.QueryFirstOrDefault<T>(sql, parameters);
}
}
protected List<T> Query<T>(string sql, object parameters = null)
{
using (var connection = CreateConnection())
{
connection.Open();
return connection.Query<T>(sql, parameters).ToList();
}
}
protected int Execute(string sql, object parameters = null)
{
using (var connection = CreateConnection())
{
connection.Open();
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-строчными методами.
Я надеюсь, что это поможет кому-то.
Ответ 3
Лучшая практика - это действительно загруженный термин. Мне нравится контейнер стиля 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();
}
}
}
Ответ 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;
}
}
Ответ 5
Я делаю это так:
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 [email protected]", 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;
});
Ответ 6
Кажется, что все открываются слишком рано? У меня был такой же вопрос, и после того, как я прокопал источник здесь - 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();
}
}
Ниже приведен небольшой пример того, как мы используем Wrapper для Dapper, называемый 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;
}
}
}
Ответ 7
Microsoft.AspNetCore.All: v2.0.3 | Dapper: v1.50.2
Я не уверен, правильно ли я использую лучшие методы, но я делаю это так, чтобы обрабатывать строки multiple.
Это легко, если у вас есть только одна строка соединения
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);
}
// ......
}
}
Проблемы, если у вас более одной строки подключения
Так как Dapper
использует IDbConnection
, вам нужно подумать о способе дифференцировать различные подключения к базе данных.
Я попытался создать несколько интерфейсов, "унаследованных" из IDbConnection
, соответствующих различным соединениям с базой данных, и вставить SqlConnection
с различными строками подключения базы данных на Startup
.
Не удалось, потому что SqlConnection
наследует от DbConnection
, а DbConnection
использует не только IDbConnection
, но также и класс Component
. Таким образом, ваши пользовательские интерфейсы не смогут использовать только импентацию SqlConnection
.
Я также попытался создать свой собственный класс DbConnection
, который использует другую строку соединения. Это слишком сложно, потому что вы должны реализовать все методы из класса DbConnection
. Вы потеряли помощь SqlConnection
.
Что я делаю
- Во время
Startup
я загрузил все значения строки подключения в словарь. Я также создал enum
для всех имен подключения к базе данных, чтобы избежать магических строк.
- Я ввел словарь как Singleton.
- Вместо ввода
IDbConnection
я создал IDbConnectionFactory
и ввел его как Transient для всех репозиториев. Теперь все репозитории принимают IDbConnectionFactory
вместо IDbConnection
.
- Когда нужно выбрать правильное соединение? В конструкторе всех репозиториев! Чтобы сделать вещи чистыми, я создал базовые классы репозитория и хранилища, наследуемые от базовых классов. Правильный выбор строки подключения может произойти в базовых классах.
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 - моя реализация factory
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 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);
}
// ......
}
}
Надеюсь, что все это поможет.
Ответ 8
Привет @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);
}
}
}
И он отлично работает.