Ich habe mit Dapper gespielt, bin mir aber nicht sicher, wie ich die Datenbankverbindung am besten handhaben soll.
Die meisten Beispiele zeigen das Verbindungsobjekt, das in der Beispielklasse oder sogar in jeder Methode erstellt wird. Es fühlt sich für mich jedoch falsch an, in jedem clss auf eine Verbindungszeichenfolge zu verweisen, selbst wenn diese aus der web.config stammt.
Meine Erfahrung war mit der Verwendung eines DbDataContext
oder DbContext
mit Linq to SQL oder Entity Framework, daher ist dies für mich neu.
Wie strukturiere ich meine Web-Apps, wenn ich Dapper als Datenzugriffsstrategie verwende?
Antworten:
Microsoft.AspNetCore.All : v2.0.3 | Dapper : v1.50.2
Ich bin nicht sicher, ob ich die Best Practices richtig verwende oder nicht, aber ich mache es auf diese Weise, um mehrere Verbindungszeichenfolgen zu verarbeiten .
Es ist einfach, wenn Sie nur 1 Verbindungszeichenfolge haben
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); } // ...... } }
Probleme, wenn Sie mehr als eine Verbindungszeichenfolge haben
Da
Dapper
verwendet wirdIDbConnection
, müssen Sie sich eine Möglichkeit überlegen , verschiedene Datenbankverbindungen zu unterscheiden.Ich habe versucht, mehrere Schnittstellen zu erstellen, die von
IDbConnection
verschiedenen Datenbankverbindungen "geerbt" wurden , undSqlConnection
verschiedene Datenbankverbindungszeichenfolgen einzuschaltenStartup
.Das ist fehlgeschlagen, weil
SqlConnection
erbt vonDbConnection
undDbConnection
implementiert nicht nur,IDbConnection
sondern auchComponent
Klasse. Ihre benutzerdefinierten Schnittstellen können also nicht nur dieSqlConnection
Implenentation verwenden.Ich habe auch versucht, eine eigene
DbConnection
Klasse zu erstellen , die eine andere Verbindungszeichenfolge verwendet. Das ist zu kompliziert, weil Sie alle Methoden aus derDbConnection
Klasse implementieren müssen . Du hast die Hilfe von verlorenSqlConnection
.Was ich am Ende mache
Startup
ich alle Verbindungszeichenfolgenwerte in ein Wörterbuch geladen. Ich habe auch einen Namenenum
für alle Datenbankverbindungen erstellt, um magische Zeichenfolgen zu vermeiden.IDbConnection
, habe ichIDbConnectionFactory
das als Transient für alle Repositorys erstellt und injiziert. Jetzt nehmen alle RepositoriesIDbConnectionFactory
stattIDbConnection
.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 - meine eigene Factory-Implementierung
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); } } }
Für andere Repositorys, die mit den anderen Verbindungen kommunizieren müssen, können Sie dann eine andere Repository-Basisklasse für diese erstellen.
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); } // ...... } }
Hoffe all diese Hilfe.
quelle
Ich habe Erweiterungsmethoden mit einer Eigenschaft erstellt, die die Verbindungszeichenfolge aus der Konfiguration abruft. Auf diese Weise müssen die Anrufer nichts über die Verbindung wissen, ob sie offen oder geschlossen ist usw. Diese Methode schränkt Sie ein wenig ein, da Sie einige der Dapper-Funktionen verbergen, aber in unserer recht einfachen App hat es für uns gut funktioniert Wenn wir mehr Funktionen von Dapper benötigen, können wir jederzeit eine neue Erweiterungsmethode hinzufügen, die diese verfügbar macht.
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); } }
quelle
buffered: false
?Es wurde vor ungefähr 4 Jahren gefragt ... aber vielleicht ist die Antwort für jemanden hier nützlich:
Ich mache es in allen Projekten so. Zuerst erstelle ich eine Basisklasse, die einige Hilfsmethoden wie diese enthält:
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; } }
Und mit einer solchen Basisklasse kann ich problemlos echte Repositorys ohne Boilerplate-Code erstellen:
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... }
Der gesamte Code für Dapper, SqlConnection-s und andere Datenbankzugriffsmaterialien befindet sich also an einem Ort (BaseRepository). Alle realen Repositorys sind saubere und einfache 1-Zeilen-Methoden.
Ich hoffe es wird jemandem helfen.
quelle
BaseRepository
ist eine unnötige Vererbung, da keine öffentlichen oder abstrakten Methoden oder Eigenschaften bereitgestellt werden. Dies könnte stattdessen eineDBHelper
Klasse sein.CreateConnection
in die eigene Klasse zu wechseln ?services.AddScoped<IDbConnection>(p => new SqlConnection(connString)
dann fragen Sie einfach danach, wo es nötig istSo mach ich es:
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 }); } } }
Wo immer ich dann meine Abhängigkeiten verkabele (z. B. Global.asax.cs oder Startup.cs), mache ich so etwas wie:
var connectionFactory = new Func<IDbConnection>(() => { var conn = new SqlConnection( ConfigurationManager.ConnectionStrings["connectionString-name"]; conn.Open(); return conn; });
quelle
IEnumerable<T>
bereits materialisiert ist. Wenn Sie bestanden habenbuffered: false
, müssen Sie die Ausgabe verbrauchen, bevor Sie denusing
Block verlassen.Best Practice ist ein wirklich geladener Begriff. Ich mag einen
DbDataContext
Style-Container wie Dapper.Rainbow . Sie können dieCommandTimeout
Transaktions- und andere Helfer koppeln .Zum Beispiel:
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(); } } }
quelle
Versuche dies:
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; } }
quelle
Jeder scheint seine Verbindungen viel zu früh zu öffnen? Ich hatte dieselbe Frage und nachdem ich die Quelle hier durchgesehen hatte - https://github.com/StackExchange/dapper-dot-net/blob/master/Dapper/SqlMapper.cs
Sie werden feststellen, dass jede Interaktion mit der Datenbank die Verbindung überprüft, um festzustellen, ob sie geschlossen ist, und sie bei Bedarf öffnet. Aus diesem Grund verwenden wir einfach Anweisungen wie oben ohne conn.open (). Auf diese Weise wird die Verbindung so nah wie möglich an der Interaktion geöffnet. Wenn Sie bemerken, wird auch die Verbindung sofort geschlossen. Dies ist auch schneller als das automatische Schließen während der Entsorgung.
Eines der vielen Beispiele aus dem obigen 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(); } }
Unten finden Sie ein kleines Beispiel dafür, wie wir einen Wrapper für Dapper verwenden, den DapperWrapper. Auf diese Weise können wir alle Dapper- und Simple Crud-Methoden umschließen, um Verbindungen zu verwalten, Sicherheit zu bieten, Protokolle zu erstellen usw.
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; } } }
quelle
Ich wickle die Verbindung mit der Hilfsklasse ein:
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 }
Anwendungsbeispiele:
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 }); }); } }
Ich muss die Verbindung also nicht jedes Mal explizit öffnen. Darüber hinaus können Sie es auf diese Weise verwenden, um das zukünftige Refactoring zu vereinfachen:
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 }); });
Was ist,
TableName<T>()
kann in dieser Antwort gefunden werden .quelle
Hallo @donaldhughes, ich bin auch neu darin und mache Folgendes: 1 - Erstellen Sie eine Klasse, um meine Verbindungszeichenfolge abzurufen. 2 - Rufen Sie die Verbindungszeichenfolgenklasse in einem Using auf
Aussehen:
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); } } }
Und es funktioniert gut.
quelle