He estado jugando con Dapper, pero no estoy seguro de cuál es la mejor manera de manejar la conexión de la base de datos.
La mayoría de los ejemplos muestran el objeto de conexión que se crea en la clase de ejemplo, o incluso en cada método. Pero me parece incorrecto hacer referencia a una cadena de conexión en cada clss, incluso si se extrae de web.config.
Mi experiencia ha sido con el uso de DbDataContext
o DbContext
con Linq para SQL o Entity Framework, por lo que esto es nuevo para mí.
¿Cómo estructuro mis aplicaciones web cuando utilizo Dapper como mi estrategia de acceso a datos?
Respuestas:
Microsoft.AspNetCore.All : v2.0.3 | Dapper : v1.50.2
No estoy seguro de si estoy usando las mejores prácticas correctamente o no, pero lo estoy haciendo de esta manera, para manejar múltiples cadenas de conexión.
Es fácil si solo tiene 1 cadena de conexión
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); } // ...... } }
Problemas si tiene más de 1 cadena de conexión
Dado que se
Dapper
utilizaIDbConnection
, debe pensar en una forma de diferenciar las diferentes conexiones de bases de datos.Intenté crear múltiples interfaces, 'heredadas' de
IDbConnection
, correspondientes a diferentes conexiones de base de datos, e inyectarSqlConnection
con diferentes cadenas de conexión de base de datos enStartup
.Eso falló porque
SqlConnection
heredaDbConnection
yDbConnection
complementa no soloIDbConnection
sino también laComponent
clase. Por lo tanto, sus interfaces personalizadas no podrán usar solo laSqlConnection
implementación.También intenté crear mi propia
DbConnection
clase que toma una cadena de conexión diferente. Eso es demasiado complicado porque tienes que implementar todos los métodos de laDbConnection
clase. Perdiste la ayuda deSqlConnection
.Lo que termino haciendo
Startup
, cargué todos los valores de cadena de conexión en un diccionario. También creé unenum
para todos los nombres de conexión de la base de datos para evitar cadenas mágicas.IDbConnection
, lo creéIDbConnectionFactory
e inyecté como Transitorio para todos los repositorios. Ahora todos los repositorios toman enIDbConnectionFactory
lugar deIDbConnection
.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 - mi propia implementación de fábrica
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); } } }
Luego, para otros repositorios que necesitan comunicarse con las otras conexiones, puede crear una clase base de repositorio diferente para ellos.
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); } // ...... } }
Espero que todos estos ayuden.
fuente
Creé métodos de extensión con una propiedad que recupera la cadena de conexión de la configuración. Esto permite que las personas que llaman no tengan que saber nada sobre la conexión, ya sea abierta o cerrada, etc. Este método lo limita un poco ya que está ocultando algunas de las funciones de Dapper, pero en nuestra aplicación bastante simple, funcionó bien para nosotros. , y si necesitáramos más funcionalidad de Dapper siempre podríamos agregar un nuevo método de extensión que lo exponga.
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); } }
fuente
buffered: false
?Se preguntó hace unos 4 años ... pero de todos modos, tal vez la respuesta sea útil para alguien aquí:
Lo hago así en todos los proyectos. Primero, creo una clase base que contiene algunos métodos auxiliares como este:
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; } }
Y al tener una clase base de este tipo, puedo crear fácilmente repositorios reales sin ningún código repetitivo:
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... }
Entonces, todo el código relacionado con Dapper, SqlConnection-s y otras cosas de acceso a la base de datos se encuentra en un solo lugar (BaseRepository). Todos los repositorios reales son métodos limpios y simples de 1 línea.
Espero que ayude a alguien.
fuente
BaseRepository
es una herencia innecesaria ya que no proporciona ningún método o propiedad pública o abstracta. En cambio, esto podría ser unaDBHelper
clase.CreateConnection
a la propia clase?services.AddScoped<IDbConnection>(p => new SqlConnection(connString)
entonces solo pídelo donde sea necesarioLo hago así:
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 }); } } }
Luego, dondequiera que conecte mis dependencias (por ejemplo: Global.asax.cs o Startup.cs), hago algo como:
var connectionFactory = new Func<IDbConnection>(() => { var conn = new SqlConnection( ConfigurationManager.ConnectionStrings["connectionString-name"]; conn.Open(); return conn; });
fuente
IEnumerable<T>
que ya está materializado. Si pasabuffered: false
, sí, deberá consumir la salida antes de salir delusing
bloque.La mejor práctica es un término muy cargado. Me gusta un
DbDataContext
contenedor de estilo como Dapper.Rainbow promueve. Le permite acoplar laCommandTimeout
transacción y otros ayudantes.Por ejemplo:
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(); } } }
fuente
Prueba esto:
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; } }
fuente
¿Todo el mundo parece estar abriendo sus conexiones demasiado pronto? Tenía esta misma pregunta, y después de buscar en la fuente aquí: https://github.com/StackExchange/dapper-dot-net/blob/master/Dapper/SqlMapper.cs
Verá que cada interacción con la base de datos verifica la conexión para ver si está cerrada y la abre según sea necesario. Debido a esto, simplemente utilizamos declaraciones de uso como las anteriores sin conn.open (). De esta manera, la conexión se abre lo más cerca posible de la interacción. Si lo nota, también cierra inmediatamente la conexión. Esto también será más rápido que si se cierra automáticamente durante la eliminación.
Uno de los muchos ejemplos de esto del repositorio anterior:
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(); } }
A continuación se muestra un pequeño ejemplo de cómo usamos un Wrapper para Dapper llamado DapperWrapper. Esto nos permite empaquetar todos los métodos Dapper y Simple Crud para administrar conexiones, proporcionar seguridad, registro, etc.
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; } } }
fuente
Envuelvo la conexión con la clase de ayuda:
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 }
Ejemplos de uso:
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 }); }); } }
Entonces no tengo que abrir explícitamente la conexión cada vez. Además, puede usarlo de esta manera por conveniencia de la futura refactorización:
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 }); });
Lo que se
TableName<T>()
puede encontrar en esta respuesta .fuente
Hola @donaldhughes, yo también soy nuevo en esto, y uso para hacer esto: 1 - Crear una clase para obtener mi Cadena de conexión 2 - Llamar a la clase de cadena de conexión en un uso
Mira:
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); } } }
Y funciona bien.
fuente