2009-05-06 8 views
5

Ecco la mia proposta (molto semplificata per illustrare lo spazio del problema) di progettazione per un'applicazione di console C#. Le connessioni del database implementano IDisposable e questa soluzione non consente gli oggetti di connessione del database per using. Qualcuno può proporre una struttura più corretta per un'applicazione di console? Questo è un problema che devo risolvere spesso.Come si struttura una console C# per utilizzare in modo efficiente le risorse del database IDisposable?

class Program 
{ 
    SQLiteConnection sourceConnection; 
    SQLiteConnection destinationConnection; 

    static void Main(string[] args) 
    { 
     Program shell = new Program(); 

     // get connection strings from command line arguments 
     string sourceConnectionString = shell.getConnectionString(args); 
     string destinationConnectionString = shell.getConnectionString(args); 

     // call non-static methods that use 
     shell.setUpConnections(sourceConnectionString, destinationConnectionString); 

     shell.doDatabaseWork(); 
    } 

    private void setUpConnections(string sourceConnectionString, string destinationConnectionString) 
    { 
     sourceConnection = new SQLiteConnection(sourceConnectionString); 
     destinationConnection = new SQLiteConnection(destinationConnectionString); 
    } 

    private void doDatabaseWork() 
    { 
     // use the connections here 
    } 
} 

Edit:

Alcune persone non possono capire perché io li vorrei come variabili membro. Ecco il mio caso d'uso (un po 'psuedocoded) di ciò che sarebbe andato in doDatabaseWork:

foreach (Row sourceRow in DBResultSet) 
{ 
    string sourceXml = sourceRow.Columns["MyColumnName"].Value; 
    string destinationXML = transformUsingXSLT(sourceXml); 
    writeToDestination(destinationXml); 
} 

vedere come avrei voluto mantenere queste connessioni aperte per la vita di questo ciclo?

+0

RE: Modifica. Ancora ... no –

risposta

2

Penso che la soluzione migliore sia estrarre la logica principale dalla classe Program. La classe Program è una sorta di antipasto per il lavoro principale. E fornire wrapper per SqlConnections non è una buona idea, perché sono già risorse gestite, il loro wrapping è ridondante. Così la mia soluzione è simile al seguente:

class ProgramCore : IDisposable 
{ 
    internal ProgramCore(string sourceConnectionString, string destinationConnectionString) 
    { 
     setUpConnections(sourceConnectionString, destinationConnectionString); 
    } 

    internal void Execute() 
    { 
     // do whatever you want 
     doDatabaseWork(); 
     // do whatever you want 
    } 

    public void Dispose() 
    { 
     if (_sourceConnection != null) 
      _sourceConnection.Dispose(); 
     if (_destinationConnection != null) 
      _destinationConnection.Dispose(); 
    } 

    private void setUpConnections(string sourceConnectionString, string destinationConnectionString) 
    { 
     _sourceConnection = new SQLiteConnection(sourceConnectionString); 
     _destinationConnection = new SQLiteConnection(destinationConnectionString); 
    } 

    private void doDatabaseWork() 
    { 
     // use the connections here 
    } 

    private SQLiteConnection _sourceConnection; 
    private SQLiteConnection _destinationConnection; 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     // get connection strings from command line arguments 
     string sourceConnectionString = GetConnectionString(args); 
     string destinationConnectionString = GetConnectionString(args); 

     using (ProgramCore core = new ProgramCore(sourceConnectionString, destinationConnectionString)) 
     { 
      core.Execute(); 
     } 
    } 

    static string GetConnectionString(string[] args) 
    { 
     // provide parsing here 
    } 
} 
+0

Le connessioni sono prese dal pool di connessioni. Quindi se non li apri e li chiudi 1000 volte al secondo, ciò non influirà sulle prestazioni del tuo programma. Ma se è necessario aprire e chiudere le connessioni molto spesso, è necessario utilizzare la memorizzazione nella cache dei dati all'interno del programma. Ma questo riguarda l'ottimizzazione, e dovresti profilare attentamente il tuo programma per ottimizzarlo. Ricorda cosa ha detto Donald Knuth sull'ottimizzazione: "Dovremmo dimenticare le piccole efficienze, diciamo circa il 97% delle volte: l'ottimizzazione prematura è la radice di tutti i mali". –

+0

Abbastanza giusto, ma la mia prima strategia era aprire e chiudere le connessioni db solo quando ne avevo bisogno. Ha rallentato a passo d'uomo durante la vita della sceneggiatura. Non ho fatto il profilo, ma anche il solo raggruppamento delle scritture in gruppi di 1000 per connessione ha reso le prestazioni gestibili. – danieltalsky

+0

Onestamente, nessuno ha migliorato la tua strategia, ma finora è il più vicino a rispondere alla mia domanda. – danieltalsky

6

Come scrivere una classe che implementa IDisposable.

All'interno del costruttore della classe, è possibile creare un'istanza delle connessioni DB.

Quindi all'interno del metodo IDisposable.Dispose, si scrive il codice di rimozione per chiudere le connessioni DB.

Ecco un esempio di codice per dimostrare quello che voglio dire:

public class DBWrapper : IDisposable 
{ 
    public SqlConnection Connection1 { get; set; } 
    public SqlConnection Connection2 { get; set; } 

    public DBWrapper() 
    { 
     Connection1 = new SqlConnection(); 
     Connection1.Open(); 
     Connection2 = new SqlConnection(); 
     Connection2.Open(); 
    } 
    public void DoWork() 
    { 
     // Make your DB Calls here 
    } 

    public void Dispose() 
    { 
     if (Connection1 != null) 
     { 
      Connection1.Dispose(); 
     } 
     if (Connection2 != null) 
     { 
      Connection2.Dispose(); 
     } 
    } 
} 

E poi, da dentro il principale metodo della classe Programma:

class Program 
{ 
    static void Main(string[] args) 
    { 
     using (DBWrapper wrapper = new DBWrapper()) 
     { 
      wrapper.DoWork(); 
     } 
    } 
} 
+0

Giusto, ma ho due connessioni al database e devono essere in grado di prendere risorse da una, trasformarle e scrivere all'altra. Non voglio ricollegarmi per ogni lettura e scrittura. Voglio solo avere un handle per entrambi aprire e quindi essere in grado di eseguire operazioni su ciascuno di cui ho bisogno. Devo fare sourceWrapper.DoWork (destinationWrapper)? – danieltalsky

+0

Prova ad aprire 2 o più lettori su quell'oggetto di connessione e scopri perché non funzionerebbe così bene –

+0

Ho appena cambiato il mio esempio di codice per essere più esplicito con 2 oggetti di connessione All'interno del metodo DoWork, hai accesso a entrambi SqlConnections, puoi fare tutto ciò che ti serve, senza dover riconnettersi per ogni lettura e scrittura. In pratica, si sta astringendo la logica DB nel metodo DBWrapper.DoWork() e lontano dal metodo Program.Main. –

2

risposta di Scott è un modo per farlo . Potresti anche considerare l'utilizzo di try {}, infine?

static void Main(string[] args) 
{ 
    Program shell = new Program(); 

    // get connection strings from command line arguments 
    string sourceConnectionString = shell.getConnectionString(args); 
    string destinationConnectionString = shell.getConnectionString(args); 

    // call non-static methods that use 
    shell.setUpConnections(sourceConnectionString, destinationConnectionString); 
    try 
    { 
     shell.doDatabaseWork(); 
    } 
    finally 
    { 
     if(sourceConnection != null) 
     sourceConnection.Dispose(); 
     if(destinationConnection != null) 
     destinationConnection.Dispose(); 
    } 
} 
+2

Perché non utilizzare la parola chiave using per sourceConnection e destinationConnection? –

+0

Funzionerà? Rispondi e mostra un esempio di codice valido, Brian? – danieltalsky

2

Personalmente, penso che tu sia sopra pensando che questo e gli esempi di codice in questa discussione sono imho eccessivamente complesso. Non ho idea del motivo per cui le persone stanno implementando IDisposable nella loro classe Program, dal momento che è eliminata quando esce.

Non riesco a pensare a un solo motivo per non utilizzare o perché non è possibile utilizzare l'istruzione using() {}.

Si desidera aprire una connessione e tenerla? Perché? Tutte le connessioni reali sono dietro le quinte nel pooling di connessioni .net, quindi gli oggetti di Connection non sono un grosso problema. Basta aprire e chiudere quando ne hai bisogno e il pool di connessioni gestisce tutto dietro le quinte.

Ho modificato il mio esempio per includerlo in una classe in modo da poter avere anche l'incapsulamento.

class Program 
{ 
    static void Main(string[] args) 
    { 
     DBWorker worker = new DBWorker(); 
     worker.DoDatabaseWork(); 
    } 
} 

public class DBWorker 
{ 

    private void DoDatabaseWork() 
    { 
     using (SQLiteConnection sourceDB = new SQLiteConnection(GetConnectionString())) 
     { 
      sourceDB.Open(); 
      using (SQLiteConnection destDB = new SQLiteConnection(GetConnectionString())) 
      { 
       destDB.Open(); 
      } 
     } 
    } 

} 
+0

Voglio essere in grado di avere metodi di aiuto separati che effettivamente fanno le letture e le scritture, così posso avere la logica che traduce i dati da uno all'altro nel proprio metodo. Se non memorizzo i dati come membro, non ho accesso alle connessioni quando ne ho bisogno. – danieltalsky

+0

Ho aggiunto una modifica per mostrare perché dovrei lottare con quel codice. – danieltalsky

0

Hmm, non vedo nessuno ha menzionato facendo in questo modo. Non è necessario che le variabili utilizzate in using siano dichiarate localmente.


class Program 
{ 
    SQLiteConnection sourceConnection; 
    SQLiteConnection destinationConnection; 

    static void Main(string[] args) 
    { 
     Program shell = new Program(); 

     // get connection strings from command line arguments 
     string sourceConnectionString = shell.getConnectionString(args); 
     string destinationConnectionString = shell.getConnectionString(args); 

     using (sourceConnection = new SQLiteConnection(sourceConnectionString)) 
     using (destinationConnection = new SQLiteConnection(destinationConnectionString)) 
     { 
      shell.doDatabaseWork(); 
     } 
    } 

    private void doDatabaseWork() 
    { 
     // use the connections here 
    } 
}