.NETでDapperを使用してデータベース接続を処理するにはどうすればよいですか?


83

私はDapperで遊んでいますが、データベース接続を処理するための最良の方法がわかりません。

ほとんどの例は、サンプルクラスまたは各メソッドで作成されている接続オブジェクトを示しています。しかし、web.configから取得している場合でも、すべてのclssで接続文字列を参照するのは間違っていると感じます。

私の経験では、DbDataContextまたはDbContextLinq toSQLまたはEntityFrameworkを使用した経験があるため、これは私にとって初めてのことです。

データアクセス戦略としてDapperを使用する場合、Webアプリをどのように構成しますか?


手遅れですが; 私はそれを次のように実装しました:stackoverflow.com/a/45029588/5779732
Amit Joshi

使用-Dapperの-非同期・イン・ASP-ネットコア-2 - exceptionnotfound.net/...
ヒマラヤガーグ

回答:


48

Microsoft.AspNetCore.All:v2.0.3 | ダッパー: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>();

            // ......
        }
    }
}

DiagramRepository.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

私がやることになること

  1. の間にStartup、すべての接続文字列値を辞書にロードしました。また、enumマジックストリングを回避するために、すべてのデータベース接続名に対してを作成しました。
  2. 辞書をシングルトンとして挿入しました。
  3. を注入する代わりに、すべてのリポジトリの一時的なものとしてIDbConnection作成IDbConnectionFactoryして注入しました。現在、すべてのリポジトリがIDbConnectionFactory代わりに使用しますIDbConnectionます。
  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>();

            // ......
        }
    }
}

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

        // ......
    }
}

これらすべての助けを願っています。


まさに私が探しているもの。私は同じ問題を抱え、同じ方法でそれを解決しました。これが良い習慣であるかどうかはまだわかりませんが、私の意見ではそうだと思います。
エヴェルトン

1
IServiceProviderスコープにIDbConnectionを登録する方がよいでしょうか?サービスを作成し、var scope = factory.CreateNonDefaultScope();を使用して、さまざまな接続を持つシングルトンスコープファクトリとして登録できます。var connection = scope.ServiceProvider.GetRequiredService <IDbConnection>()を使用すると、デフォルト以外の接続が取得されます。あまりの継承は...だけでなく、拡張を支援します
あまりにも

27

構成から接続文字列を取得するプロパティを使用して拡張メソッドを作成しました。これにより、発信者は接続が開いているか閉じているかなど、接続について何も知る必要がなくなります。この方法では、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);
        }
    }

1
ここに1つの質問があります。conn.QueryはIEnumerable <T>を返すので、接続オブジェクトをすぐに破棄しても安全ですか?IEnumerableは、要素が読み取られるときに要素を実体化するために接続を必要としませんか?ToList()を実行する必要がありますか?
エイドリアンナスイ2017

確認するためにDapperに戻る必要がありますが、このパターンは実際の製品コードからそのまま使用したと確信しています。大丈夫なはずですが、もちろん、インターネット上のコードをテストする必要があります。
ショーンハバード

1
dapper Query拡張メソッドを使用している場合は、メソッド自体で行われるため、接続を明示的に開く必要はありません。
h-rai 2017年

4
上記のコードの問題は、buffered:trueをQueryメソッドに渡すと、データが返される前に接続が破棄されることです。内部的には、Dapperは列挙可能なものをリストに変換してから戻ります。
BrianVallelunga18年

@BrianVallelungaではないでしょうbuffered: falseか?
ジョドレル

24

それは約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、およびその他のデータベースアクセス関連のすべてのコードは、1つの場所(BaseRepository)にあります。実際のリポジトリはすべて、クリーンでシンプルな1行の方法です。

私はそれが誰かを助けることを願っています。


1
BaseRepositoryパブリックまたは抽象メソッドまたはプロパティを提供しないため、不要な継承です。代わりに、これはDBHelperクラスである可能性があります。
Josh Noe 2017年

CreateConnection自分のクラスに移動したほうがいいのでは ないでしょうか。
ヘルボーイ2018

たぶん...でも個人的にはすべてをシンプルに保つのが好きです。CreateConnection(...)に多くのロジックがある場合は、それをお勧めします。私のプロジェクトでは、このメソッドは「return new Connection(connectionString)」と同じくらい単純なので、個別のCreateConnection(...)メソッドなしでインラインで使用できます。
Pavel Melnikov 2018

1
また、ニックスが指摘したように、Dapperの最新バージョンでは、データベース接続を手動で開く必要はありません。Dapperが自動的に開きます。投稿を更新しました。
Pavel Melnikov 2018

imoを注入します。services.AddScoped<IDbConnection>(p => new SqlConnection(connString)必要な場所そしてちょうどそれを求める
Sinaesthetic

8

私はこのようにします:

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

ここに1つの質問があります。conn.QueryはIenumerable <T>を返すので、接続をすぐに破棄しても安全ですか?IEnumerableは、要素が読み取られるときに要素を実体化するために接続を必要としませんか?
エイドリアンナスイ2017

1
@AdrianNasui:現在、Dapperのデフォルトの動作は、SQLを実行し、戻ったときにリーダー全体をバッファリングすることであるため、IEnumerable<T>はすでに実現されています。合格した場合buffered: false、はい、usingブロックを終了する前に出力を消費する必要があります。
ジェイコブクラル

7

ベストプラクティスは、実際にロードされた用語です。私のような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();
        }
    }
}

15
OPはSqlConnection([[CONN STRING HERE]])の部分についてもっと質問していませんか?彼は「しかし、すべてのクラスで(各メソッドでも)接続文字列を参照するのは私には間違っていると感じています」と言います。Dapperユーザーが接続作成側をラップするパターンを生成したかどうか疑問に思っています。そのロジックを乾燥/非表示にします。(OPは別として、Dapper.Rainbowを使用できる場合は、そうしてください...本当に素晴らしいです!)
ckittel 2012

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

    }

6
ソリューションで接続を閉じる/破棄する方法をどのように処理しますか?
jpshook 2013

@ JPShook-彼はを使用していると思います。(REF stackoverflow.com/a/4717859/2133703
マクガイバー

4

誰もが完全に早すぎる接続を開いているように見えますか?私はこれと同じ質問をしました、そしてここでソースを掘り下げた後-://github.com/StackExchange/dapper-dot-net/blob/master/Dapper/SqlMapper.cs

データベースとのすべての対話で接続が閉じられているかどうかが確認され、必要に応じて接続が開かれます。このため、conn.open()を使用せずに、上記のようなステートメントを使用するだけです。このようにして、接続はインタラクションのできるだけ近くで開かれます。気付いた場合は、すぐに接続も閉じます。これは、廃棄中に自動的に閉じるよりも速くなります。

上記のリポジトリからのこれの多くの例の1つ:

    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用のラッパーを使用する方法の小さな例です。これにより、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;
      }
    }
  }

1
これは、Dapperが接続を取得したときにすでに開いている場合、接続を開いたままにすることを知っていると非常に便利です。Dapperでパス/使用する前に、db接続を事前に開いています。パフォーマンスが6倍向上しました。ありがとうございます。
クリススミス

2

ヘルパークラスとの接続をラップします。

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>()ます


0

こんにちは@donaldhughes私もそれが初めてで、これを行うために使用します:1-接続文字列を取得するためのクラスを作成します2-Usingで接続文字列クラスを呼び出します

見てください:

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

そしてそれはうまくいきます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.