2014-04-11 4 views
7

Ich habe vor kurzem begonnen zu verwenden Dapper, alles scheint nett und einfach, aber es gibt eine Sache, die mich immer verwirrt: Connection Management.Verwalten von Verbindung mit nicht gepufferten Abfragen in Dapper

Gemäß der documentation:

Dapper nicht die Verbindung des Lebenszyklus nicht verwalten, nimmt er die Verbindung wird es offen ist und keine bestehenden Datareaders Aufzählen (es sei denn, MARS aktiviert ist)

In Anbetracht dessen begann ich dies innerhalb der Implementierung meiner Repository-Methoden:

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

Dann stieß ich auf eine Tabelle mit einer großen Anzahl von Datensätzen, so dass ich eine IEnumerable<T> durch Übergeben buffered: false an die Methode Query<> zurückgab, und als ich begann, die Aufzählung im Front-End aufzuzählen, Boom eine Ausnahme, die die Verbindung geschlossen wurde und entsorgt, was erwartet wird, seit ich meine Anrufe mit dem vorhergehenden Benutzungsblock umschließe.

Frage: Der beste Weg, um das zu lösen?
Seitenfrage: Ist die Art, wie ich die Verbindung verwalte, der bevorzugte Weg, um darüber zu gehen?

+0

Nur falls dies jemand anderes hilft, benutzte ich gepuffert: falsch und es korrigiert meine Verbindungsproblem w/adrett. – wintercyborg

Antwort

10

würde ich dieses Repository-Muster bieten:

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

Für gepufferte Abfragen, die Sie zuerst Überlastung GetConnection Methode verwenden möchten, für ungepufferten Sie zweite verwenden, für die Verarbeitung von Daten specifing Rückruf:

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

Sehr einfache Nutzung:

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

Aber bedenken - In diesem Fall kann die Verbindung zu lange geöffnet werden ...

+0

Oh Mann, was habe ich mir gedacht. Danke, Sergio, dass du mich an dieses Schlüsselwort erinnert hast. Ich weiß nicht, warum ich seine Verwendung vergessen habe. Eine Sache, obwohl Sie nicht alle diese Rohrleitungen benötigen, um dies zum Laufen zu bringen, können Sie einfach 'yield return result' direkt in der Methodenimplementierung im Repository verwenden. Wenn niemand mit einer besseren Antwort kam, werde ich Ihnen das Kopfgeld geben. Vielen Dank. –

+0

Vielleicht muss die Verbindung in der neueren Version von Dapper nicht geöffnet sein. Die SqlMapper-Klasse überprüft den Status der Verbindung und wenn sie geschlossen wird, ruft sie 'Open()' darauf auf. –

6

@Sergio, FANTASTISCH! Danke für so ein tolles Muster. Ich habe es leicht async geändert, damit ich es mit Dappers Async-Methoden verwenden kann. Macht meine gesamte Anfragekette asynchron, von den Controllern bis zur DB! Herrlich!

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

Verwendung mit Dapper wie folgt aus:

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