Jak obsługiwać połączenia z bazą danych za pomocą Dapper w .NET?

86

Bawiłem się z Dapperem, ale nie jestem pewien, jak najlepiej obsłużyć połączenie z bazą danych.

Większość przykładów przedstawia obiekt połączenia tworzony w klasie przykładowej lub nawet w każdej metodzie. Ale wydaje mi się niewłaściwe odwoływanie się do parametrów połączenia w każdym pliku clss, nawet jeśli jest ono pobierane z pliku web.config.

Moje doświadczenie dotyczy używania DbDataContextlub DbContextz Linq to SQL lub Entity Framework, więc jest to dla mnie nowość.

Jak ustrukturyzować moje aplikacje internetowe, gdy używam Dapper jako strategii dostępu do danych?

Donald Hughes
źródło
Za późno, ale; Zaimplementowałem to w ten sposób: stackoverflow.com/a/45029588/5779732
Amit Joshi
stosując, ruchliwy, asynchronicznie w-Asp-net-rdzeń-2 - exceptionnotfound.net/...
Himalajów Garg

Odpowiedzi:

53

Microsoft.AspNetCore.All : v2.0.3 | Dapper : v1.50.2

Nie jestem pewien, czy prawidłowo używam najlepszych praktyk, czy nie, ale robię to w ten sposób, aby obsłużyć wiele parametrów połączenia.

Jest to łatwe, jeśli masz tylko 1 ciąg połączenia

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

        // ......
    }
}

Problemy, jeśli masz więcej niż 1 ciąg połączenia

Ponieważ Dapperwykorzystuje IDbConnection, musisz wymyślić sposób na rozróżnienie różnych połączeń z bazą danych.

Próbowałem stworzyć wiele interfejsów „dziedziczonych” z IDbConnection, odpowiadających różnym połączeniom z bazą danych i wstrzyknąć SqlConnectionróżne parametry połączenia z bazą danych Startup.

To się nie udało, ponieważ SqlConnectiondziedziczy DbConnectioni DbConnectionuzupełnia nie tylko, IDbConnectionale także Componentklasę. Więc twoje niestandardowe interfejsy nie będą mogły korzystać tylko z SqlConnectionimplementacji.

Próbowałem również stworzyć własną DbConnectionklasę, która przyjmuje różne parametry połączenia. To zbyt skomplikowane, ponieważ musisz zaimplementować wszystkie metody z DbConnectionklasy. Straciłeś pomoc od SqlConnection.

Co w końcu robię

  1. Podczas Startupzaładowania wszystkich wartości parametrów połączenia do słownika. Utworzyłem również enumdla wszystkich nazw połączeń bazy danych, aby uniknąć magicznych ciągów.
  2. Słownik wprowadziłem jako Singleton.
  3. Zamiast wstrzykiwać IDbConnection, utworzyłem IDbConnectionFactoryi wstrzyknąłem to jako przejściowe dla wszystkich repozytoriów. Teraz wszystkie repozytoria biorą IDbConnectionFactoryzamiast IDbConnection.
  4. Kiedy wybrać właściwe połączenie? W konstruktorze wszystkich repozytoriów! Aby wszystko było czyste, stworzyłem klasy bazowe repozytoriów i sprawiłem, że repozytoria dziedziczą z klas bazowych. W klasach podstawowych może nastąpić właściwy wybór parametrów połączenia.

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 - własne wdrożenie fabryczne

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

Następnie dla innych repozytoriów, które muszą komunikować się z innymi połączeniami, możesz utworzyć dla nich inną klasę bazową repozytorium.

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

        // ......
    }
}

Mam nadzieję, że wszystkie te pomogą.

David Liang
źródło
Dokładnie to, czego szukam. Miałem ten sam problem i rozwiązałem go w ten sam sposób, nadal nie wiem, czy to dobra praktyka, ale moim zdaniem tak.
Ewerton
1
Czy lepiej byłoby zarejestrować IDbConnection dla zakresu IServiceProvider? Można stworzyć usługę i zarejestrować się jako fabryka pojedynczego zakresu z różnymi połączeniami i używając var scope = factory.CreateNonDefaultScope (); używając var connection = scope.ServiceProvider.GetRequiredService <IDbConnection> () otrzymasz połączenie inne niż domyślne. Mniej dziedziczenia pomoże również w rozszerzalności ...
także
To jest to, czego szukam. Świetna praca @David. Dzięki
Shashwat Prakash,
27

Utworzyłem metody rozszerzające z właściwością, która pobiera parametry połączenia z konfiguracji. Dzięki temu dzwoniący nie muszą nic wiedzieć o połączeniu, czy jest otwarte, czy zamknięte, itp. Ta metoda ogranicza Cię nieco, ponieważ ukrywasz część funkcji Dapper, ale w naszej dość prostej aplikacji działa dobrze dla nas i gdybyśmy potrzebowali większej funkcjonalności od Dappera, zawsze moglibyśmy dodać nową metodę rozszerzenia, która ją ujawnia.

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);
        }
    }
Shawn Hubbard
źródło
1
Jedno pytanie. Ponieważ conn.Query zwraca IEnumerable <T>, czy można bezpiecznie natychmiast pozbyć się obiektu połączenia? Czy IEnumerable nie potrzebuje połączenia, aby zmaterializować elementy podczas ich odczytywania? Czy powinniśmy uruchomić ToList ()?
Adrian Nasui
Musiałbym wrócić do Dappera, aby zweryfikować, ale jestem prawie pewien, że wziąłem ten wzorzec tak, jak jest z działającego kodu produkcyjnego. Powinno być w porządku - ale oczywiście powinieneś przetestować dowolny kod w Internecie.
Shawn Hubbard
2
Jeśli używasz eleganckiej metody rozszerzenia Query, nie musisz jawnie otwierać połączenia, ponieważ jest to robione w samej metodzie.
h-rai
4
Problem z powyższym kodem polega na tym, że jeśli przekażesz buffered: true do metody Query, połączenie zostanie usunięte przed zwróceniem danych. Wewnętrznie Dapper zamieni wyliczalne w listę przed zwróceniem.
Brian Vallelunga,
@BrianVallelunga nie byłoby to buffered: false?
Jodrell
26

Zapytano o to jakieś 4 lata temu ... ale tak czy owak, może komuś tutaj przyda się odpowiedź:

Robię to tak we wszystkich projektach. Najpierw tworzę klasę bazową, która zawiera kilka metod pomocniczych, takich jak ta:

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

Mając taką klasę bazową mogę łatwo tworzyć prawdziwe repozytoria bez żadnego standardowego kodu:

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

Tak więc cały kod związany z Dapper, SqlConnection-s i innymi elementami dostępu do bazy danych znajduje się w jednym miejscu (BaseRepository). Wszystkie rzeczywiste repozytoria to czyste i proste metody 1-liniowe.

Mam nadzieję, że komuś to pomoże.

Pavel Melnikov
źródło
2
BaseRepositoryjest niepotrzebnym dziedziczeniem, ponieważ nie zapewnia żadnych publicznych ani abstrakcyjnych metod ani właściwości. Zamiast tego mogłaby to być DBHelperklasa.
Josh Noe
Może lepiej przenieść się CreateConnectiondo własnej klasy?
hellboy,
Może… Ale osobiście lubię, aby wszystko było proste. Jeśli masz dużo logiki w CreateConnection (...), może to być dobry pomysł. W moich projektach ta metoda jest tak prosta, jak „return new Connection (connectionString)”, więc może być używana w tekście bez oddzielnej metody CreateConnection (...).
Pavel Melnikov
1
Ponadto, jak zauważył nick-s, w najnowszych wersjach Dappera nie trzeba ręcznie otwierać połączenia z bazą danych. Dapper otworzy go automatycznie. zaktualizował post.
Pavel Melnikov
wstrzyknąć imo. services.AddScoped<IDbConnection>(p => new SqlConnection(connString)wtedy po prostu poproś o to w razie potrzeby
Sinaesthetic
8

Robię to tak:

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

Następnie, gdziekolwiek łączę moje zależności (np .: Global.asax.cs lub Startup.cs), robię coś takiego:

var connectionFactory = new Func<IDbConnection>(() => {
    var conn = new SqlConnection(
        ConfigurationManager.ConnectionStrings["connectionString-name"];
    conn.Open();
    return conn;
});
Romi Petrelis
źródło
Jedno pytanie. Ponieważ conn.Query zwraca wartość Ienumerable <T>, czy można bezpiecznie natychmiast pozbyć się połączenia? Czy IEnumerable nie potrzebuje połączenia, aby zmaterializować elementy podczas ich odczytywania?
Adrian Nasui
1
@AdrianNasui: Obecnie domyślnym zachowaniem Dappera jest wykonanie kodu SQL i buforowanie całego czytnika po powrocie, więc IEnumerable<T>jest już zmaterializowany. Jeśli zdasz buffered: false, tak, będziesz musiał skonsumować dane wyjściowe przed wyjściem z usingbloku.
Jacob Krall
7

Najlepsza praktyka to naprawdę obciążony termin. Podoba mi się DbDataContextpojemnik w stylu, który promuje Dapper.Rainbow . Pozwala na połączenieCommandTimeout pomocników, transakcji i innych.

Na przykład:

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();
        }
    }
}
Sam Saffron
źródło
15
Czy OP nie pyta więcej o część SqlConnection ([[CONN STRING HERE]])? Mówi: "Ale czuję się źle, gdybym odnosił się do ciągu połączenia w każdej klasie (nawet w każdej metodzie)". OSUSZ / ukryj tę logikę. (Pomijając OP, jeśli możesz użyć Dapper.Rainbow, zrób to ... to naprawdę fajne!)
ckittel
4

Spróbuj tego:

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

    }
Shuaib
źródło
6
Jak radzisz sobie z zamykaniem / usuwaniem połączenia w swoim rozwiązaniu?
jpshook
@JPShook - Myślę, że używa on. (ref stackoverflow.com/a/4717859/2133703 )
MacGyver,
4

Wygląda na to, że wszyscy otwierają swoje połączenia całkowicie za wcześnie? Miałem to samo pytanie i po przekopaniu się tutaj przez Źródło - https://github.com/StackExchange/dapper-dot-net/blob/master/Dapper/SqlMapper.cs

Przekonasz się, że każda interakcja z bazą danych sprawdza połączenie, aby zobaczyć, czy jest zamknięte, i otwiera je w razie potrzeby. Z tego powodu po prostu używamy takich instrukcji, jak powyżej, bez funkcji conn.open (). W ten sposób połączenie jest otwierane jak najbliżej interakcji. Jeśli zauważysz, natychmiast zamyka połączenie. Będzie to również szybsze niż automatyczne zamykanie podczas usuwania.

Jeden z wielu przykładów tego z powyższego repozytorium:

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

Poniżej znajduje się mały przykład tego, jak używamy Wrappera dla Dappera o nazwie DapperWrapper. To pozwala nam zawrzeć wszystkie metody Dapper i Simple Crud do zarządzania połączeniami, zapewniania bezpieczeństwa, rejestrowania itp.

  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;
      }
    }
  }
Zabbu
źródło
1
Jest to naprawdę przydatne, wiedząc, że Dapper pozostawi otwarte połączenie, jeśli jest już otwarte, gdy je otrzyma. Otwieram teraz połączenie db, zanim przepuszczę / użyję go z Dapper i uzyskałem 6-krotny wzrost wydajności - dzięki!
Chris Smith
2

Owijam połączenie z klasą pomocniczą:

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
}

Przykłady użycia:

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

Nie muszę więc za każdym razem jawnie otwierać połączenia. Dodatkowo możesz to wykorzystać w ten sposób ze względu na wygodę przyszłej refaktoryzacji:

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

Co TableName<T>()można znaleźć w tej odpowiedzi .

Siergiej
źródło
0

Cześć @donaldhughes, też jestem nowy i używam do tego: 1 - Utwórz klasę, aby uzyskać parametry połączenia 2 - Wywołaj klasę parametrów połączenia w Using

Popatrz:

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

I działa dobrze.

Gabriel Scavassa
źródło