IT Share you

.NET에서 Dapper로 데이터베이스 연결을 어떻게 처리합니까?

shareyou 2020. 11. 8. 11:32
반응형

.NET에서 Dapper로 데이터베이스 연결을 어떻게 처리합니까?


Dapper를 가지고 놀았지만 데이터베이스 연결을 처리하는 가장 좋은 방법을 모르겠습니다.

대부분의 예제는 예제 클래스 또는 각 메서드에서 생성되는 연결 개체를 보여줍니다. 그러나 web.config에서 가져 오는 경우에도 모든 clss에서 연결 문자열을 참조하는 것이 잘못되었습니다.

내 경험은 DbDataContext또는 DbContextLinq to SQL 또는 Entity Framework를 사용하여 왔 으므로 이것은 나에게 새로운 것입니다.

Dapper를 데이터 액세스 전략으로 사용할 때 웹 앱을 어떻게 구성합니까?


구성에서 연결 문자열을 검색하는 속성을 사용하여 확장 메서드를 만들었습니다. 이렇게하면 호출자가 연결이 열려 있는지 닫혀 있는지 여부 등 연결에 대해 알 필요가 없습니다.이 방법은 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);
        }
    }

약 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)에 있습니다. 모든 실제 리포지토리는 깨끗하고 간단한 단선 방법입니다.

누군가를 도울 수 있기를 바랍니다.


Microsoft.AspNetCore.All : v2.0.3 | Dapper : 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>();

            // ......
        }
    }
}

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로부터 상속 DbConnectionDbConnectioninplements뿐만 IDbConnection아니라 Component클래스입니다. 따라서 사용자 정의 인터페이스는 표현만을 사용할 수 없습니다 SqlConnection.

또한 DbConnection다른 연결 문자열 을 사용하는 자체 클래스 를 만들려고했습니다 . DbConnection클래스의 모든 메서드를 구현해야하기 때문에 너무 복잡합니다 . 에서 도움을 잃었습니다 SqlConnection.

내가하는 일

  1. 동안 Startup, 나는 사전에 모든 연결 문자열 값을로드. 또한 enum매직 문자열을 피하기 위해 모든 데이터베이스 연결 이름에 대해을 만들었습니다 .
  2. 저는 사전을 Singleton으로 삽입했습니다.
  3. 주입하는 대신 모든 리포지토리에 대해 Transient를 IDbConnection만들고 IDbConnectionFactory주입했습니다. 이제 모든 리포지토리 IDbConnectionFactoryIDbConnection.
  4. 언제 올바른 연결을 선택해야합니까? 모든 저장소의 생성자에서! 깔끔하게 만들기 위해 저장소 기본 클래스를 만들고 저장소가 기본 클래스에서 상속 받도록했습니다. 올바른 연결 문자열 선택은 기본 클래스에서 발생할 수 있습니다.

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

            // ......
        }
    }
}

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

그런 다음 다른 연결과 통신해야하는 다른 저장소에 대해 다른 저장소 기본 클래스를 만들 수 있습니다.

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

        // ......
    }
}

이 모든 도움을 바랍니다.


나는 이것을 이렇게한다 :

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

모범 사례는 실제로드 된 용어입니다. 나는 DapperDbDataContext 와 같은 스타일 컨테이너를 좋아 합니다. , 트랜잭션 및 기타 도우미 를 연결할 수 있습니다 .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

데이터베이스와의 모든 상호 작용은 연결이 닫혀 있는지 확인하기 위해 연결을 확인하고 필요에 따라 엽니 다. 이로 인해 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();
        }
    }

다음은 DapperWrapper라는 Dapper 용 Wrapper를 사용하는 방법에 대한 간단한 예입니다. 이를 통해 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;
      }
    }
  }

안녕하세요 @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);
            }
        }
     }

그리고 잘 작동합니다.


도우미 클래스와의 연결을 래핑합니다.

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>() 에서 무엇을 찾을 수 있습니다 .

참고 URL : https://stackoverflow.com/questions/9218847/how-do-i-handle-database-connections-with-dapper-in-net

반응형