Połączenie z bazą dwóch aplikacji oraz sens ORM

0

Ok, trochę mi rozjaśniliście sytuację.

A teraz takie pytanie trochę odbiegające. A jakbym chciał połączenie z bazą realizować wykorzystując czysty ADO.NET, to tworzę sobie klasę do połączenia. Z tym, że teoretycznie przy każdej funkcjonalności w nowym oknie muszę tworzyć nowy obiekt tej klasy, a co za tym idzie nowe połączenie do bazy.
Pamiętam, że w Javie miałem ten problem, pisałem nie wykorzystując ORMa aplikację okienkową i przez to, że w każdym formie miałem nowe połączenie to aplikacja nie była zbyt wydajna, w tamtym projekcie rozwiązałem to tak, że w konstruktorze Forma przekazywałem obiekt klasy połączenia i wtedy wszystko działało w obrębie jednego połączenia.
I tutaj pytanie? Jak to się rozwiązuje w aplikacjach biznesowych produkcyjnie? Zakładając, że korzystamy tylko z ADO. Na początku myślałem o singletonie, ale później usłyszałem, że generalnie singleton do trzymania połączenia nie jest zalecany, a moim zdaniem przekazywanie obiektu połączenia do każdego okienka też chyba nie jest zbyt eleganckie.

0

W praktyce najczęście wygląda to tak że do danej klasy przekazujesz właśnie obiekt połączenia i zapisujesz do prywatnego pola i w ten sposób go używasz. Ważne żeby połącenie było tak skonfigurowane żeby nie tworzyć go na nowo za każdym razem, ale używać puli połączeń - ADO powinno się tym zająć w tle.

1

Okienka mają być odpowiedzialne za GUI. Tylko. Tam nie ma być żadnej logiki biznesowej. Powinieneś mieć to rozdzielone w taki sposób, że masz osobną klasę (najlepiej w osobnym projekcie) do ogarniania danych. I wszystko powinno być oparte na interfejsach. Coś takiego:

Interfejs (to może być osobny projekt "Infrastructure", w którym trzymasz wszystkie tego typu interfejsy") - taki prosty przykład:

public interface IClientDataExchange
{
  void AddClient(Client c);
  void DeleteClient(Client c);
  void UpdateClient(Client c);
}

Klasa Client powinna być w osobnym projekcie (lub namespace): Models

Następnie powinieneś mieć klasę, która implementuje ten interfejs. Może być w projekcie DAL (DataAccessLayer):

public class DbClientDataExchange: IClientDataExchange
{
  DbContext db;

  public DbClientDataExchange(DbContext db)
  {
    this.db = db;
  }

  public void AddClient(Client c)
  {
    string sql = "INSERT INTO...";
    db.AddParameter(...);
    db.ExecuteNonQuery();
  }

//itd.
}

I teraz tak. DbContext to jest jakaś Twoja klasa, która wykonuje już bezpośrednio przekazane zapytania. Może to być też repozytorium.
Ta klasa może też być klasą, którą będziesz testował, a więc nie powinna mieć wtedy dostępu do bazy danych, tylko do jakiegoś repozytorium właśnie. A dalej to repozytorium już sobie obrabia dane. Tutaj, dla ułatwienia, trochę to połączyłem.

Na koniec Twoja forma powinna przyjmować w konstruktorze ten interfejs:

public class Form1
{
  IClientDataExchange data;
  
  public Form1(IClientDataExchange data)
  {
    this.data = data;
  }

  void OnButtonClick()
  {
    Client c = new Client();
    c.FirstName = firstNameEdit.Text;
    //itd
 
    data.AddClient(c);
  }
}

Tak to mniej - więcej powinno wyglądać. Forma ma nie wiedzieć co musi zrobić, żeby dodać klienta. Forma ma to przekazać jakiejś innej klasie, której zadaniem jest obsługa danych. I która dokładnie wie, co ma zrobić. Forma ma tylko pokazać kontrolki i ewentualnie reagować na ich zdarzenia. Nic więcej. Forma nie powinna mieć żadnej logiki biznesowej. Te wszystkie elementy powinny być oddelegowywane do osobnych klas. To się może wydawać skomplikowane, ale bardzo ułatwia pisanie i testowanie aplikacji.

0

Wiesz co, postanowiłem sprawdzić jak to działa i przepisałem Twój kod, a w klasie DbClientDataExchange metody rzucają mi NotImplException. Jednak nie do końca to działa, bo w data.AddClient dostaję wyjątek NullReferenceException.

Wiesz nie mam zbyt dużego doświadczenia, a na studiach uczą tylko podstawowych rzeczy o interfejsach, bo ich jedyne wykorzystanie to udostępnienie "szablonu" metod dla klasy. Tutaj widzę, że wykonujesz metodę bezpośrednio interfejsu.

Dodam, że wszystkie projekty oprócz jednego WPFa to biblioteki klas.

0

Zacznij od ORM - bedzie Ci latwiej i wystarczy. ADO.Net warto znac ale nie zawsze dobrze od tego zaczynac (idealnie jest zaczac od tego (ORM wykorzystuja zazwyczaj ado.net) ale jak ma sie juz jakas wiedze a nie w momencie gdy wszystko jest nowe).

0

Ja Ci podałem przykład, jak to powinno wyglądać. Ty musisz sam to sobie wszystko napisać :|
Ale widzę, że masz chyba też braki jeśli chodzi o programowanie obiektowe. Poczytaj o polimorfizmie.

0

Juhas, rozumiem. Fakt, jeśli chodzi o interfejsy, nie jestem mocno doświadczony. Zapomniałem o konstruktorze w klasie MainWindow. Niestety jak dodam tam konstruktor to aplikacja mi się przekręca. Próbuję przekazać zatem to w miejscu, gdzie obiekt tej klasy jest tworzony, jednak dostaję błąd IClientDataExchange is a type, which is not valid in the given context.

Ok, dziękuję wszystkim za odpowiedzi.

0

Dobra, szybka lekcja.

public interface IMojInterfejs
{
  void PokazKomunikat();
}

public class MojaKlasa: IMojInterfejs
{
  void PokazKomunikat()
  {
    MessageBox.Show("Siema, jestem komunikatem");
  }
}

public class Form1
{
  void ButtonClick()
  {
    IMojInterfejs interfejs = new MojaKlasa();
    ZrobDobrze(interfejs);
  }

  void ZrobDobrze(IMojInterfejs interfejs)
  {
    interfejs.PokazKomunikat();
  }
}

Rozumiesz teraz, jak to powinno działać?

0

Ok, wiem o co chodzi, w sumie wiedziałem, że to na tym polega. A że zdaję sobie sprawę, że nie wiem jeszcze wszystkiego, a dodatkowo z .NET dopiero zaczynam, to nie widząc nigdzie przypisania nowej instancji klasy do obiektu typu interfejs myślałem, że .NET robi to jakoś niejawnie :)

0

Witam wszystkich ponownie. Cały czas siedzę nad .NETem i mam pytanie.

W sumie pytanie możliwe, że już zadałem podobne, ale chodzi mi o to, czym głównie kieruje się w projektach odrzucając ORMy. Chodzi mi o duże aplikacje np. Comarch Optima - nie wiem, czy dobry przykład, bo program rozwijany od wielu lat, moduły pewnie przepisywane z Clariona, ale taka Enova365, może trochę naiwnie sugerować się dllkami z programu, ale nie widzę nic poza plikiem adodb.dll, czyli mniemam, że również nie korzysta z ORM. Może moje pytanie jest głupie jednak jestem ciekaw :)

2

Wielu odrzuca ORMy, bo ich nie rozumieją albo się ich boją. Niektórzy też nie wiedzą o ich istnieniu.

1

Odrzucając ORM słyszy się prawie zawsze jeden argument: na czystym ADO lub z całą logiką po stronie procedur składowanych będzie szybciej. Co prawda ORM'y potrafią być szybkie i jest to dzisiaj trochę nieaktualny argument, aczkolwiek często stosowany. Powodem takiego narzekania jest często propagowany EF, który nawet dzisiaj bywa wolniejszy niż większość ORM'ów dla .NET'a.

Poza tym ORM nie ogarnie wszystkiego. Masz choćby takie rzeczy jak raportowanie czy OLAP z zapytaniami MDX gdzie ORM'a nie wykorzystasz. Poza tym jeżeli cos jest wygodniej i czytelniej zrobić bez ORM to czemu nie?

Kolejnym argumetem jest to, że ludzie odrzucając ORM tłumaczą to faktem, że kod SQL łatwiej kontrolować ale... to juz zostawmy bez komentarza.

0

Dzięki za odpowiedź. Rozumiem.

Ja sam mam mieszane uczucia szczerze mówiąc, aktualnie próbuję robić proste rzeczy z EF zacząłem od DB First. Wcześniej robiłem dla siebie projekty w Java Swing bez żadnych ORMów, w Spring Boocie z Hibernate i zdaję sobię sprawę z tego, że nie były to rozbudowane projekty, zazwyczaj prosty CRUD jednak co mi się podobało to to, że bez ORMa miałem pełną kontrolę z poziomu aplikacji nad connection stringiem i bazą, miałem tabelę z preferencjami aplikacji i mogłem z zapytania wynik po prostu wyciągnąć do stringa bez tworzenia żadnych klas-modeli. Nie twierdzę, że nie da się tak z ORMem oraz, że to było poprawne rozwiązanie.

0

Wyciąganie całego obiektu z bazy do stringa zamiast deserializowanie go do klasy, gdzie są ładnie zdefiniowane pola, zakrawa trochę o patologię. ;) Poza tym cięcie takiego stringa w przypadku kiedy mamy dużo danych to jest dopiero spadek wydajności, ponieważ string w C# jest immutable. Ściągając do czegoś takiego duzego dataseta i pozniej go odbcinajac robisz sobie harakiri :)

Poza tym w jaki sposób tracisz kontrolę nad connection stringiem? Bez sensu trochę oba te argumenty.

1

Pracowałem przy oprogramowaniu gdzie brak ORM tłumaczono złożonością systemu. Dziś wiem że wynika to z braku wystarczającej wiedzy (na temat ORM) i/lub złej architekturze.

0

@Aventus: dokładnie, bo "gołe procki składowane przecież doskonale się debugują. Zwłaszcza nasze zagnieżdżone kursory. Nie wiem po co ORM. Poza tym już kiedyś, dawno raz spróbowaliśmy i nie wyszło więc po co się męczyć. Lepiej pomęczyć się gołym SQL'em na 3000 linijek. Trapy w Visualu są dla mięczaków. Prawdziwy bazodanowiec kopiuje kawałki zapytań do osobnego okna i tam je debuguje!" :)

0

Wiesz, zdaję sobie sprawę, że moje argumenty są bez sensu, bo nie pracowałem nigdy zawodowo przy dużym projekcie używającym ORMa. W pracy niestety tylko czysty PHP i JS :/

Dobra, co do connection stringa. Chodzi o to, że z poziomu okienek chcę zarządzać bazami danych. Czyli mogę w programie przełączać się pomiędzy bazami, mogę skonfigurować nowe połączenie. Obecnie ze względu na brak doświadczenia łatwiej mi to zrobić bez ORMa. Poza tym większość kursów, czy to Entity czy Hibernate pokazuje konfigurację połączenia podczas konfiguracji programu, tak jakby cała baza była już elementem oprogramowania z narzuconymi credentialsami do niej.

Co do tego stringa, fakt nie jest to dobrym pomysłem, ale chodzi mi o to, że załóżmy mam tabelę z preferencjami programu. Mogą się one zmieniać dynamicznie, bo użytkownik może w trakcie działania programu je sobie zmieniać, ale mogą być też w polach szablony powiedzmy wydruków w htmlu(czysto teoretycznie przykład, nie twierdzę, że tak je się składuje). I teraz powiedzmy w jednym oknie dostępność jednego przycisku zależy od jednego pola w tej tabeli. To w tym przypadku muszę pobierać cały wiersz tabeli ze sporą ilością danych, aby sprawdzić dostępność przycisku. I tak w każdym oknie.

2

To przenieś konfigurację ORMa ze startu aplikacji do miejsca w którym wybierasz bazę :)
DbContext przyjmuje przecież connection string w konstruktorze, więc możesz łatwo zmienić, podobnie łatwo możesz też tworzyć sobie osobne fabryki ISession NHibernate (bo chyba nie używasz xmla do konfiguracji...)
A co do konfiguracji to czy nie możesz tego keszować w pamięci? A jeśli nie to możesz użyć Selecta z LINQ do wczytania pojedynczego pola, wtedy wygenerowany pod spodem SQL wczyta tylko to jedno wybrane pole :)

1

XML do konfiguracji NH zamiast fluenta: to powinno być karane. ;) Natomiast konfigurację użytkownika aplikacji można zaczynać raz do jakiegoś obiektu zarejestrowanego w IoC jako singleton i będzie sobie to ladnie żyło. Można to zrobić np. kiedy taki user się loguje.

Natomiast @mad_penguin tak jak powiedziałeś, obsługa kilku baz przy zastosowaniu ORM to żaden problem.

W ogóle niektórzy wydają się dostawać pryszczy jak tylko usłyszą słowo ORM. Co ciekawe nie w stosunku do Dappera. Może dlatego, że to leży tak blisko ADO i jest micro, a jak mikro noooo to musi być szybkie. :)

0

Ok moje wątpliwości zostały rozwiane ;)

Co do tych ustawień to jeszcze kwestia rozwojowa, nie mam jeszcze planów.

Ale ciężko się przestawić na WPF ze Swinga :P

0

Ktoś może dać linka do jakiegoś małego projektu, w którym ORM został poprawnie użyty i nie łamie zasad? Pytanie moje stąd, że do tej pory widziałem tylko rzeczy z "kursów", które były nie do przyjęcia w kodzie dużego systemu.

0

Jakie rzeczy były nie do przyjęcia?

1
lukaszek016 napisał(a):

Poza tym większość kursów, czy to Entity czy Hibernate pokazuje konfigurację połączenia podczas konfiguracji programu, tak jakby cała baza była już elementem oprogramowania z narzuconymi credentialsami do niej.

Bo w w więcej niż 99% przypadków tak właśnie jest. Zmiana connection stringa jest potrzebna chyba tylko w przypadku tworzenia jakichś narzędzi bazodanowych.

To w tym przypadku muszę pobierać cały wiersz tabeli ze sporą ilością danych, aby sprawdzić dostępność przycisku. I tak w każdym oknie.

Przede wszystkim nie powinieneś wykonywać żadnych operacji bazodanowych z poziomu okna. Między interfejsem użytkownika, a warstwą dostępu do danych powinieneś mieć jeszcze warstwy aplikacji oraz logiki biznesowej. I takie coś, jak wczytanie ustawień użytkownika powinieneś robić raz przy uruchomieniu programu, a zapisywać również raz, przy jego zamykaniu (no albo pod jakimś przyciskiem na żądanie użytkownika).

0
somekind napisał(a):
lukaszek016 napisał(a):

Przede wszystkim nie powinieneś wykonywać żadnych operacji bazodanowych z poziomu okna. Między interfejsem użytkownika, a warstwą dostępu do danych powinieneś mieć jeszcze warstwy aplikacji oraz logiki biznesowej.

Warstwa prezentacji może operować bezpośrednio na middleware operacji bazodanowych. Jeśli owa logika jest powiązana bezpośrednio z prezentacją jak np. serwis, który generuję paginację. Umieszczanie go w warstwie aplikacji to wyciek abstrakcji w dół. Czy warstwę aplikacji interesuje to, jak działa paginacja, robienie oddzielnego serwisu w warstwie aplikacji, żeby przykryć ORM'a, który ma pobrać tylko ilość wierszy to lekka przesada.

0

Bo w w więcej niż 99% przypadków tak właśnie jest. Zmiana connection stringa jest potrzebna chyba tylko w przypadku tworzenia jakichś narzędzi bazodanowych.

Nie wiem, może mówimy o innych aplikacjach. Przecież bez możliwości konfiguracji połączenia z bazą przez użytkownika/admina wyklucza się praca wielostanowiskowa.

Przede wszystkim nie powinieneś wykonywać żadnych operacji bazodanowych z poziomu okna. [...]

Nie chodziło mi o bezpośredni dostęp do bazy. Rozumiem, aby to oddzielać. Ale mogłoby to na przykład działać analogicznie, jak w aplikacji do jakiegoś TextBoxa wstawiana jest wartość do bazy, czyli masz binding z viewmodelu na atrybut Text w xamlu, to analogicznie możesz bindować booleana z viewmodelu na IsEnabled Buttona. A do tej właściewości z viewmodelu już wartość pobierać z bazy tak jak do stringa dla TextBoxa. Nie wiem, czy nie pokręciłęm... :P

1
lukaszek016 napisał(a):

Bo w w więcej niż 99% przypadków tak właśnie jest. Zmiana connection stringa jest potrzebna chyba tylko w przypadku tworzenia jakichś narzędzi bazodanowych.

Nie wiem, może mówimy o innych aplikacjach. Przecież bez możliwości konfiguracji połączenia z bazą przez użytkownika/admina wyklucza się praca wielostanowiskowa.

Nie bardzo rozumiem co masz na myśli pisząc że wyklucza to pracę wielostanowiskową. Mam wrażenie, że trochę za dużą wagę przywiązuesz do use case że to użytkownik może wybrać bazę z którą się łączy i może to zrobić poprzez podanie parametrów połączenia w oknie/ podanie connection stringa. Użytkownik końcowej aplikacji NIE POWINIEN wiedzieć nic o istnieniu bazy danych - aplikacja kliencka łączy się do określonej usługi i ewnetulanie w tym miejejscu można pozwoliś wybrać do której ma się łącyzć.

Parametry bazy danych to powinno być coś zapisane po stronie serwera w jakimś pliku konfiguracyjnym, dostępne jedynie dla admina/dev opsa a dla aplikacji connection-string powinien to być po prostu parametr jak każdy inny.

ViewModel, tak samo jak kontroler WebApi, nie powinien w ogóle wiedzieć o itnieniu bazy. Nie powinno się używać kontekstu bazy w metodach ViewModelu. Od tego jest warstwa logiki biznesowej (przynajmniej).

0

W2K, no tak, przy założeniu, że architektura systemu opiera się o aplikację serwer, która łączy się do bazy i wystawia jakieś API dla aplikacji klienckiej to tak. Ale większość aplikacji, z którymi się spotykałem działała w ten sposób, że to admin systemu przy pierwszym uruchomieniu programu konfiguruje sobie połączenie z bazą. I taki use case: w razie "urośnięcia" bazy po pewnym czasie, kiedy to zasoby obecnej maszyny, na której stoi baza przestają wystarczać przenosi bazę na inny serwer i ponownie konfiguruje sobie połączenie.

Mówiąc o użytkowniku miałem właśnie na myśli admina, nie użytkownika końcowego, trochę to źle ująłem wybacz :P

1
lukaszek016 napisał(a):

W2K, no tak, przy założeniu, że architektura systemu opiera się o aplikację serwer, która łączy się do bazy i wystawia jakieś API dla aplikacji klienckiej to tak. Ale większość aplikacji, z którymi się spotykałem działała w ten sposób, że to admin systemu przy pierwszym uruchomieniu programu konfiguruje sobie połączenie z bazą. I taki use case: w razie "urośnięcia" bazy po pewnym czasie, kiedy to zasoby obecnej maszyny, na której stoi baza przestają wystarczać przenosi bazę na inny serwer i ponownie konfiguruje sobie połączenie.

Mówiąc o użytkowniku miałem właśnie na myśli admina, nie użytkownika końcowego, trochę to źle ująłem wybacz :P

Nawet przy takiej architekturze, konfiguracja ORMa nie jest problemem. Kotekest nie jest obiektem ze stałym połaczeniem do bazy - lączy się wtedy gdy potrzebuje. Onzacza to że podczas ładowania aplikacji/po podaniou przez usera parametrów połączenia, zapisujesz sobie do zmiennej podany connection string i używasz jej do każdego utworzenia kontekstu, gdy jest potrzebny. Mozna to całkiem zgrabnie zapakować w kontener Dependency Injection (choć jeśli mam być złośliwy to pewnie w aplikacji o takim modelu jak piszesz to DI to ze świecą szukać ;-) )

0

Hmm, no jak już wcześniej wspomniałem pracowałem m. in. na oprogramowaniu Comarcha, np. Optima, prawdopodobnie XL też tak działa. Masz serwer SQL i do tego aplikacje desktopowe, które łączą się bezpośrednio do bazy.

2
Szalony Kura napisał(a):

Warstwa prezentacji może operować bezpośrednio na middleware operacji bazodanowych.

Nie sądzę. Żadnego kontekstu ORM ani obiektów ADO.NET nie powinno być w klasach formatek i okienek.

robienie oddzielnego serwisu w warstwie aplikacji, żeby przykryć ORM'a, który ma pobrać tylko ilość wierszy to lekka przesada.

Owszem, oddzielny serwis do stronicowania to zły pomysł, bo pobranie tej informacji, tak jak i pozostałych metadanych potrzebnych do działania stronicowania powinno być wykonane przy okazji pobierania aktualnie potrzebnej strony danych, a nie w jakimś oddzielnym serwisie.

lukaszek016 napisał(a):

Nie chodziło mi o bezpośredni dostęp do bazy. Rozumiem, aby to oddzielać. Ale mogłoby to na przykład działać analogicznie, jak w aplikacji do jakiegoś TextBoxa wstawiana jest wartość do bazy, czyli masz binding z viewmodelu na atrybut Text w xamlu, to analogicznie możesz bindować booleana z viewmodelu na IsEnabled Buttona. A do tej właściewości z viewmodelu już wartość pobierać z bazy tak jak do stringa dla TextBoxa. Nie wiem, czy nie pokręciłęm... :P

A od czego zależy, czy ten przycisk ma być aktywny? Ja widzę trzy możliwości:

  • Albo dynamicznie wyliczane (aktywuje się, gdy użytkownik uzupełni inne dane w GUI), więc dostęp do bazy niepotrzebny.
  • Albo od stanu danego obiektu (np. przycisk "Wystaw fakturę" dostępny tylko jeśli status zamówienia == zrealizowane), a wtedy trzeba i tak to wczytać z bazy razem z rekordem.
  • Albo od uprawnień/globalnej konfiguracji użytkownika, którą można wczytać z bazy przy uruchamianiu aplikacji, a potem uzupełniać viewmodele o te już wczytane wartości w miejscu konstruowania viewmodeli. Nie trzeba za każdym razem wysyłać zapytania do bazy.
lukaszek016 napisał(a):

Mówiąc o użytkowniku miałem właśnie na myśli admina, nie użytkownika końcowego, trochę to źle ująłem wybacz :P

Czyli funkcja konfigurowania connection stringa jest potrzebna rzadko, a nie do normalnej pracy, więc możliwość jego ustawiania to nie jest jakieś ważne wymaganie. Szczerze mówiąc, to admin powinien być w stanie to zrobić w pliku konfiguracyjnym.

1 użytkowników online, w tym zalogowanych: 0, gości: 1