Я играл с Dapper, но не уверен, как лучше всего обрабатывать соединение с базой данных.
В большинстве примеров показано, что объект подключения создается в классе примера или даже в каждом методе. Но мне кажется неправильным ссылаться на строку подключения в каждом clss, даже если она берется из web.config.
Мой опыт был с использованием DbDataContext
или DbContext
с Linq для SQL или Entity Framework, так что это для меня ново.
Как структурировать свои веб-приложения при использовании Dapper в качестве стратегии доступа к данным?
Ответы:
Microsoft.AspNetCore.All : v2.0.3 | Dapper : v1.50.2
Я не уверен, правильно ли я использую лучшие практики, но я делаю это таким образом, чтобы обрабатывать несколько строк подключения.
Это просто, если у вас всего 1 строка подключения
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>(); // ...... } } }
ДиаметрRepository.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
наследуетсяDbConnection
иDbConnection
дополняется не только классом,IDbConnection
но иComponent
классом. Таким образом, ваши пользовательские интерфейсы не смогут использовать толькоSqlConnection
реализацию.Я также попытался создать свой собственный
DbConnection
класс, который принимает другую строку подключения. Это слишком сложно, потому что вам нужно реализовать все методы изDbConnection
класса. Вы потеряли помощь отSqlConnection
.Что я в итоге делаю
Startup
этого я загрузил все значения строки подключения в словарь. Я также создалenum
для всех имен подключения к базе данных, чтобы избежать волшебных строк.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 - моя собственная реализация фабрики
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>(); // ...... } } }
ДиаметрRepository.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); } // ...... } }
Надеюсь, что все это поможет.
источник
Я создал методы расширения со свойством, которое извлекает строку подключения из конфигурации. Это позволяет вызывающим абонентам ничего не знать о соединении, открыто оно или закрыто и т. Д. Этот метод немного ограничивает вас, так как вы скрываете некоторые функции 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); } }
источник
buffered: false
?Его спрашивали года 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). Все настоящие репозитории - это чистые и простые однострочные методы.
Надеюсь, это кому-то поможет.
источник
BaseRepository
является ненужным наследованием, поскольку не предоставляет никаких общедоступных или абстрактных методов или свойств. Вместо этого это может бытьDBHelper
класс.CreateConnection
в собственный класс?services.AddScoped<IDbConnection>(p => new SqlConnection(connString)
тогда просто попросите его там, где это необходимоЯ так делаю:
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; });
источник
IEnumerable<T>
он уже материализован. Если вы пройдетеbuffered: false
, да, вам нужно будет использовать вывод перед выходом изusing
блока.Лучшая практика - это действительно загруженный термин. Мне нравится
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(); } } }
источник
Попробуй это:
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; } }
источник
Кажется, все слишком рано открывают свои связи? У меня был тот же вопрос, и после того, как я покопался здесь в источнике - https://github.com/StackExchange/dapper-dot-net/blob/master/Dapper/SqlMapper.cs
Вы обнаружите, что каждое взаимодействие с базой данных проверяет соединение, чтобы увидеть, закрыто ли оно, и открывает его при необходимости. По этой причине мы просто используем операторы using, подобные приведенным выше, без 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(); } }
Ниже приведен небольшой пример того, как мы используем оболочку для 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; } } }
источник
Оборачиваю соединение вспомогательным классом:
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>()
можно найти в этом ответе .источник
Привет, @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); } } }
И работает нормально.
источник