2015-04-06 18 views
8

Po pierwsze, jestem programistą Java i jestem nowy na C# i potrzebuję opinii programistów C#. Zajmuję się tworzeniem aplikacji, która łączy się z bazą danych (firebird 1.5), wysyła zapytanie do niektórych danych i wraca do mnie, więc nic nie musi być skomplikowane, ale niestety utknąłem w kilku rzeczach:Najlepsze praktyki: C# praca z DB

Jak wiemy, połączenie z bazą danych powinno być realizowane w oddzielnym wątku, ponieważ jest to operacja o dużej masie i wszystkie połączenia powinny znajdować się w puli połączeń, aby ponownie użyć już otwartego połączenia, a zamiast tego utworzyć nowe.

Oto moje pierwsze pytanie - jak poprawnie zorganizować basen? (co z puli połączeń Czytałem, że zwykle basen połączenie jest już realizowany przez dostawców danych i można po prostu ustawić go w parametrach połączenia jakoś jak „connectionBuilder.Pooling = true;”)

Co o zapytaniach? Chodzi mi o to, że zawsze używam kwerendy na wątek (i myślę, że to jest w porządku, ponieważ wykonujemy również operację z dużą masą, czy nie myliłem się? W każdym razie, chciałbym zobaczyć twoje najlepsze praktyki z organizowaniem pracy z bazami danych) i Java po prostu robię wyniku kwerendy powrócić z osobnym wątku przez użyć interfejsy i klas anonimowych tak:

w DBHelper.class (DBHelper jest pojedyncza)

public interface QueryListener { 

    public void onSuccess(ArrayList<?>); 

    public void onError(Exception e); 
} 

public synchronized void getPromoActions(final QueryListener listener) { 
    if (listener != null) { 
     try { 
     ArrayList<String> myPromoActions; 
     ............. 
     // some query's code 
     ..... 
     listener.onSucces(myPromoActions); 
     } catch(Exception e) { 
     listener.onError(e); 
     } finally { 
     closeDatabase(); 
     } 
    } 
} 

w niektórych Klasa UI (dla eaxample MainWindow)

public void getPromoActions(){ 
    new Thread(new Runnable() { 
    @Override 
    public void run() { 
     DBHelper.getInstance().getPromoActions(new QueryListener() { 

     @Override 
     public void onSuccess(ArrayList<?>) { 
      // set Data to UI element such as Table 
     } 

     @Override 
     public void onError(Exception e){ 
      // Handling exception 
     } 
     }); 
    } 
    }).start(); 
} 

W języku C# należy użyć delegatów z okazji, która metoda będzie wykonywał w wątku, ale unfortionally nie mogę wysłać żadnego zwrotnego jako parametr - tak jak mam wrócić mojej kwerendy wyniki do głównego wątku UI?

UPD

mam rozumieć trochę jak pracować z delegatów i zdarzeń, ale mają problemy z podnoszeniem zdarzenia niestandardowego. Miałem ogłoszony eventhandler i niestandardowych EventArgs:

public delegate void QueryResultEventHandler(object sender, QueryResultEventArgs e); 

public class QueryResultEventArgs : EventArgs 
{ 
    public List<String> QueryResult { get; set; } 
    public int QueryRecordsCount { get; set; } 
} 

i moim DBHelper.class oświadczyłem kolejne pole i zdarzenie:

private QueryResultEventHandler _queryResult; 

public event QueryResultEventHandler onQueryResult 
{ 
    add 
    { 
    lock (this) 
    { 
     _queryResult += value; 
    } 
    } 

    remove 
    { 
    lock (this) 
    { 
     _queryResult -= value; 
    } 
    } 
} 

W klasy UI (MainWindow) używam następny kod:

public void GetAllDistricts() { 
     DBHelper.Instance.onQueryResult += new QueryResultEventHandler(GetAllDistricsResultHandler); 
     DBHelper.Instance.GetAllDistricts(); 
    } 

public void GetAllDistricsResultHandler(object sender, QueryResultEventArgs e){ 
     // Here I'm adding the query result to Table 
    } 

Więc moim problemem jest teraz jak asynchronicznie podnieść wydarzenie? W moim DBHelper.class Próbuję użyć beginInvoke & endInvoke z delegacja _query, ale wydaje się, że brakowało niektórych linii kodu, co to było, nie mogę zrozumieć, co robię źle, jak podnieść zdarzenia asynchronicznie? Tutaj mój DBHelper .Klasa Kod:

public void GetAllDistricts() { 
    try 
    { 
     if (_queryResult != null) 
     { 
     //** This code should run asynchronously ----------> 

     using (FbConnection connection = GetConnection()) 
     { 
      FbCommand getAllDistrictsCommand = new FbCommand(); 

      getAllDistrictsCommand.CommandText = "SELECT * FROM SEND"; 
      getAllDistrictsCommand.Connection = connection; 

      QueryResultEventArgs args = new QueryResultEventArgs(); 
      using (FbDataReader reader = getAllDistrictsCommand.ExecuteReader()) 
      { 
      while (reader.Read()) 
      { 
      //Here must be the processing of query results and filling the 
      //QueryResultEventArgs 
       args.QueryResult.Add(reader[0].ToString()); 
      }      
      args.QueryRecordsCount = reader.GetInt32(reader.GetOrdinal("Rows")); 

      // And here after sucessfull query I should call OnQueryResult() 
      OnQueryResult(args); 
      } 
     } 
     //**<-------------------- 
     } 
     else 
     { 
     throw new Exception("...Some exception message..."); 
     } 
    } 
    catch (Exception e) 
    { 
    log.ErrorException(e.Message, e); 
    throw new Exception("...Some exception message...");; 
    } 
    finally { 
    CloseConnection(); 
    } 
} 

// The QueryResultEvent method 
protected void OnQueryResult(QueryResultEventArgs e) 
{ 
    if (_queryResult != null) 
    { 
    _queryResult(this, e); 
    } 
} 
+0

Łączenie połączeń z bazy danych będzie bardzo korzystne, jeśli pozwoli na to architektura. Jeśli tworzysz aplikację typu serwer klienta, tj. Front, który łączy się bezpośrednio z serwerem, to brak jest takiego połączenia w puli połączeń. Czy łączysz się z bazą danych bezpośrednio z aplikacji klienckiej. – Namphibian

+0

@Namphibian Tak, rozwijam aplikację typu klient-serwer i łączę się bezpośrednio z bazą danych. – whizzzkey

+1

W twoim przypadku dodanie puli połączeń to tylko zwiększenie złożoności i brak korzyści. Zazwyczaj pula połączeń znajduje się na serwerze aplikacji. Aby czerpać korzyści z łączenia połączeń, musisz przejść do architektury n-warstwowej. – Namphibian

Odpowiedz

8

Najpierw na temat łączenia połączeń. Jeśli będziesz używać ADO.NET, nie musisz się tym martwić, ponieważ już tam jest. Nie trzeba wykonywać żadnych dodatkowych prac, wystarczy utworzyć połączenie:

using (var connection = new SqlConnection(connectionString)) 
{ 
    // Queries to DB 
} 

należy zawsze Zamknij lub utylizować skorzystać z połączeń. Nazwy metod wyglądają "przerażająco", ale w rzeczywistości połączenia są ponownie wykorzystywane. Przeczytaj ten numer MSDN article, aby uzyskać więcej informacji.

Zaproponowany kod wygląda na zbyt skomplikowany. Myślę, że powinieneś rozważyć użycie wzorca async/await, który ogólnie nie jest wielowątkowy, ale obsługuje problemy z responsywnością interfejsu użytkownika i upraszcza pisanie/czytanie kodu. W nowszych wersjach .NET prawie wszystkie metody, które są potencjalnie długie do wykonania, mają wersje asynchroniczne. Tak na przykład Twój warstwa dostępu do danych może wyglądać tak, że (używam Dapper ORM'sQueryAsync metodę tylko do utrzymania kod krótki i prosty):

public async Task<IList<District>> GetAllDistrictsAsync() 
{ 
    using (var connection = await GetConnectionAsync()) 
    { 
     return (await connection.QueryAsync<District>("select * from Districts")).ToList(); 
    } 
} 

public async Task<IDbConnection> GetConnectionAsync() 
{ 
    var connectionString = 
     ConfigurationManager.ConnectionStrings["DbConnectionString"].ConnectionString; 
    var connection = new SqlConnection(connectionString); 
    await connection.OpenAsync(); 
    return connection; 
} 

A potem gdzieś na interfejsie:

private async void Button_Click(object sender, EventArgs e) 
{ 
    var districts = await GetAllDistrictsAsync(); 
} 

Jeśli nadal trzeba wykonać kod w innym wątku, który powinien wyglądać w przestrzeni nazw Tasks.

Task.Factory 
    .StartNew<IList<District>>(GetAllDistricts) 
    .ContinueWith(districts => 
    { 
     // UI thread 
    }, TaskScheduler.FromCurrentSynchronizationContext()); 

W tym przykładzie GetAllDistricts nie jest asynchroniczny i jest wykonywany w innym wątku. Ale ContinueWith zostanie wykonane w wątku UI z powodu TaskScheduler.FromCurrentSynchronizationContext().

+0

Jeśli masz dodatkowe pytania, mogę też dodać do nich odpowiedzi (jeśli oczywiście mogę). Pozhalujsta;) –

+0

Proszę bądź tak uprzejmy ** druzhishe =) **, wyjaśnij mi następną rzecz - więc czy niewłaściwą praktyką jest używanie Delegatów i wydarzeń do operacji asynchronicznych? A jeśli tak, to jestem zainteresowany dlaczego? Pozdrowienia. – whizzzkey

+1

To stary sposób na zrobienie tego, async to nowa funkcja ułatwiająca. async pozwala uzyskać najlepszą czytelność, utrzymując logikę przetwarzania zdarzenia w funkcji all in i pozwalając ramie obsłużyć fakt, że nie chcesz blokować wątku interfejsu użytkownika, ale chcesz kontynuować przetwarzanie w tym wątku. Jeśli chcesz zrobić to asynchronicznie z wydarzeniami, które musisz obsłużyć toczącym się wątkiem (w tym przejęcie kontroli z powrotem do wątku interfejsu użytkownika, ponieważ twój interfejs prawdopodobnie nie będzie bezpieczny dla wątków - jak to zrobić, zależy od tego, czego używasz do UI, jeśli jesteś w WPF, to za pośrednictwem Dispatchera. – fyjham

1
public void GetAllDistricts() { 

     DBHelper.Instance.onQueryResult += 
        new QueryResultEventHandler(GetAllDistricsResultHandler); 

     new Thread(
      new ThreadStart(DBHelper.Instance.GetAllDistricts) 
      ).Start(); 

    } 

Ale problem będzie twarz jest, że nie będzie w stanie uzyskać dostęp do formantów UI z EventHandler jak zostanie ona odrzucona, ponieważ nie są w tym samym wątku już ...

Patrz tego artykułu z jakiegoś wyjaśnienia

How to update the GUI from another thread in C#?

aby tego uniknąć można może użyć BackgroundWorke r kontrola.

+0

Dziękuję za odpowiedź. – whizzzkey