2014-04-11 15 views
7

Niedawno zacząłem używać Dapper, wszystko wydaje się łatwe i przyjemne, ale jest jedna rzecz, która mnie dezorientuje: Zarządzanie połączeniami.Zarządzanie połączeniami z niepobudzonymi zapytaniami w Dapper

jak na documentation:

Wytworny nie zarządza cyklem Twojego połączenia do, to zakłada gra robi jest otwarta i nie ma żadnych istniejących datareaders wyliczający (chyba, że ​​Mars jest włączona)

W związku z tym zacząłem robić to w ramach wdrażania moich metod repozytorium:

using (var db = new SqliteConnection(connectionString)) { 
    // call Dapper methods here 
} 

Potem natknąłem się na tabelę z dużą liczbą rekordów, więc zwróciłem IEnumerable<T> przekazując buffered: false do metody Query<>, a kiedy zacząłem wyliczać wyliczalne w przednim końcu, bum wyjątek mówiąc, że połączenie zostało zamknięte i rozmieszczone, co jest oczekiwane, ponieważ owijam moje połączenia poprzednim blokiem.

Pytanie: Najlepszy sposób na rozwiązanie tego problemu?
Pytanie boczne: Czy sposób zarządzania połączeniem jest preferowanym sposobem postępowania?

+0

Tylko w przypadku, to pomaga kogoś innego, kiedyś buforowane: false i skorygowane mój problem połączenia w/elegancki. – wintercyborg

Odpowiedz

10

Chciałbym zaoferować tego repozytorium wzór:

public class Repository 
{ 
    private readonly string _connectionString; 

    public Repository(string connectionString) 
    { 
     _connectionString = connectionString; 
    } 

    protected T GetConnection<T>(Func<IDbConnection, T> getData) 
    { 
     using (var connection = new SqlConnection(_connectionString)) 
     { 
      connection.Open(); 
      return getData(connection); 
     } 
    } 

    protected TResult GetConnection<TRead, TResult>(Func<IDbConnection, TRead> getData, Func<TRead, TResult> process) 
    { 
     using (var connection = new SqlConnection(_connectionString)) 
     { 
      connection.Open(); 
      var data = getData(connection); 
      return process(data); 
     } 
    } 
} 

Dla buforowane zapytań chcesz użyć pierwszy przeciążenie GetConnection sposobem, na non-buforowane użyć drugi specifing oddzwanianie do przetwarzania danych:

public class MyRepository : Repository 
{ 
    public MyRepository(string connectionString) : base(connectionString) 
    { 
    } 

    public IEnumerable<MyMapObject> GetData() 
    { 
     return GetConnection(c => c.Query<MyMapObject>(query)); 
    } 

    public IEnumerable<ResultObject> GetLotsOfData(Func<IEnumerable<MyMapObject>, IEnumerable<ResultObject>> process) 
    { 
     return GetConnection(c => c.Query<MyMapObject>(query, buffered: false), process); 
    } 
} 

Bardzo proste użytkowanie:

static void Main(string[] args) 
{ 
    var repository = new MyRepository(connectionString); 
    var data = repository.GetLotsOfData(ProcessData); 
} 

public static IEnumerable<ResultObject> ProcessData(IEnumerable<MyMapObject> data) 
{ 
    foreach (var record in data) 
    { 
     var result = new ResultObject(); 
     //do some work... 
     yield return result; 
    } 
} 

należy jednak pamiętać, - połączenie może być otwarte zbyt długo w tym przypadku ...

+0

Ojej, o czym myślałem. Dziękuję Sergio za przypomnienie mi słowa kluczowego "yield". Nie wiem, dlaczego zapomniałem o jego użyciu. Jedną rzeczą jest to, że nie potrzebujesz całej tej hydrauliki, aby to działało, możesz po prostu użyć 'return return result' w implementacji metody bezpośrednio w repozytorium. Jeśli nikt nie podszedłby z lepszą odpowiedzią, nagrodzę cię nagrodą. Dziękuję Ci. –

+0

Być może połączenie nie musi być otwarte w nowszej wersji Dappera. Klasa SqlMapper sprawdza stan połączenia, a jeśli jest zamknięty, wywoła na nim 'Open()'. –

6

@Sergio, AWESOME! Dzięki za tak wspaniały wzór. Zmodyfikowałem go nieznacznie, by być asynchronicznym, dzięki czemu mogę go używać z metodami asynchronicznymi Dappera. Sprawia, że ​​mój cały łańcuch żądań asynchronizuje się, od kontrolerów aż do DB! Wspaniały!

public abstract class BaseRepository 
{ 
    private readonly string _ConnectionString; 

    protected BaseRepository(string connectionString) 
    { 
     _ConnectionString = connectionString; 
    } 

    // use for buffered queries 
    protected async Task<T> WithConnection<T>(Func<IDbConnection, Task<T>> getData) 
    { 
     try 
     { 
      using (var connection = new SqlConnection(_ConnectionString)) 
      { 
       await connection.OpenAsync(); 
       return await getData(connection); 
      } 
     } 
     catch (TimeoutException ex) 
     { 
      throw new Exception(String.Format("{0}.WithConnection() experienced a SQL timeout", GetType().FullName), ex); 
     } 
     catch (SqlException ex) 
     { 
      throw new Exception(String.Format("{0}.WithConnection() experienced a SQL exception (not a timeout)", GetType().FullName), ex); 
     } 
    } 

    // use for non-buffeed queries 
    protected async Task<TResult> WithConnection<TRead, TResult>(Func<IDbConnection, Task<TRead>> getData, Func<TRead, Task<TResult>> process) 
    { 
     try 
     { 
      using (var connection = new SqlConnection(_ConnectionString)) 
      { 
       await connection.OpenAsync(); 
       var data = await getData(connection); 
       return await process(data); 
      } 
     } 
     catch (TimeoutException ex) 
     { 
      throw new Exception(String.Format("{0}.WithConnection() experienced a SQL timeout", GetType().FullName), ex); 
     } 
     catch (SqlException ex) 
     { 
      throw new Exception(String.Format("{0}.WithConnection() experienced a SQL exception (not a timeout)", GetType().FullName), ex); 
     } 
    } 
} 

Korzystanie z Dapper tak:

public class PersonRepository : BaseRepository 
{ 
    public PersonRepository(string connectionString): base (connectionString) { } 

    // Assumes you have a Person table in your DB that 
    // aligns with a Person POCO model. 
    // 
    // Assumes you have an existing SQL sproc in your DB 
    // with @Id UNIQUEIDENTIFIER as a parameter. The sproc 
    // returns rows from the Person table. 
    public async Task<Person> GetPersonById(Guid Id) 
    { 
     return await WithConnection(async c => 
     { 
      var p = new DynamicParameters(); 
      p.Add("Id", Id, DbType.Guid); 
      var people = await c.QueryAsync<Person>(sql: "sp_Person_GetById", param: p, commandType: CommandType.StoredProcedure); 
      return people.FirstOrDefault(); 
     }); 
    } 
}