Wstrzykiwanie zależności za pomocą n-tierowego rozwiązania Entity Framework

12

Obecnie projektuję rozwiązanie n-tier, które wykorzystuje Entity Framework 5 (.net 4) jako swoją strategię dostępu do danych, ale martwię się, jak włączyć wstrzykiwanie zależności, aby uczynić go testowalnym / elastycznym.

Mój obecny układ rozwiązania jest następujący (moje rozwiązanie nazywa się Alcatraz):

Alcatraz.WebUI : Projekt strony internetowej asp.net, interfejs użytkownika, odwołuje się do projektów Alcatraz.Business i Alcatraz.Data.Models .

Alcatraz.Business : Projekt biblioteki klas, zawiera logikę biznesową, odwołuje się do projektów Alcatraz.Data.Access , Alcatraz.Data.Models

Alcatraz.Data.Access : Projekt biblioteki klas, zawierający AlcatrazModel.edmx i AlcatrazEntitiesDbContext, odwołuje się do projektów Alcatraz.Data.Models .

Alcatraz.Data.Models : Projekt biblioteki klas, zawiera POCO dla modelu Alcatraz, bez odniesień.

Moja wizja działania tego rozwiązania polegałaby na tym, że interfejs sieciowy tworzyłby repozytorium w bibliotece biznesowej. Repozytorium to miałoby zależność (za pośrednictwem konstruktora) ciągu połączenia (nie AlcatrazEntitiesinstancji). Interfejs WWW znałby parametry połączenia z bazą danych, ale nie to, że był to łańcuch połączenia struktury encji.

W projekcie biznesowym:

public class InmateRepository : IInmateRepository
{
    private string _connectionString;

    public InmateRepository(string connectionString)
    {
        if (connectionString == null)
        {
            throw new ArgumentNullException("connectionString");
        }

        EntityConnectionStringBuilder connectionBuilder = new EntityConnectionStringBuilder();

        connectionBuilder.Metadata = "res://*/AlcatrazModel.csdl|res://*/AlcatrazModel.ssdl|res://*/AlcatrazModel.msl";
        connectionBuilder.Provider = "System.Data.SqlClient";
        connectionBuilder.ProviderConnectionString = connectionString;

        _connectionString = connectionBuilder.ToString();
    }

    public IQueryable<Inmate> GetAllInmates()
    {
        AlcatrazEntities ents = new AlcatrazEntities(_connectionString);

        return ents.Inmates;
    }
}

W interfejsie internetowym:

IInmateRepository inmateRepo = new InmateRepository(@"data source=MATTHEW-PC\SQLEXPRESS;initial catalog=Alcatraz;integrated security=True;");

List<Inmate> deathRowInmates = inmateRepo.GetAllInmates().Where(i => i.OnDeathRow).ToList();

Mam kilka powiązanych pytań dotyczących tego projektu.

  1. Czy ten projekt ma w ogóle sens pod względem możliwości Entity Frameworks? Słyszałem, że środowisko Entity już używa wzorca Unit-of-work, czy po prostu niepotrzebnie dodaję kolejną warstwę abstrakcji?

  2. Nie chcę, aby mój interfejs WWW komunikował się bezpośrednio z Entity Framework (a nawet odwoływał się do niego w tym zakresie), chcę, aby cały dostęp do bazy danych przechodził przez warstwę biznesową, ponieważ w przyszłości będę mieć wiele projektów korzystających z tej samej warstwy biznesowej (usługa sieci web, aplikacja systemu Windows itp.). Chcę mieć łatwą konserwację / aktualizację dzięki logice biznesowej w jednym centralnym obszarze. Czy to odpowiedni sposób na osiągnięcie tego?

  3. Czy warstwa biznesowa powinna nawet zawierać repozytoria, czy powinna zawierać się w warstwie dostępu? Jeśli ich położenie jest w porządku, to czy przekazywanie ciągu połączenia jest dobrą zależnością do założenia?

Dziękujemy za poświęcenie czasu na czytanie!

Mateusz
źródło

Odpowiedzi:

11

Sposób, w jaki robisz DI, jest zły.

Po pierwsze, ciąg połączenia należy do warstwy danych. Lub w pliku web.config.

Następną abstrakcją, z którą będziesz mieć do czynienia, jest DbContext, a nie ciąg połączenia. Twoje repozytoria nie powinny wiedzieć o ciągach połączeń. Twoja logika biznesowa nie będzie wiedziała o DbContext itp.

Twój interfejs użytkownika nie będzie miał pojęcia i nie będzie tworzył niczego związanego z EF.

Konkretne odpowiedzi na twoje pytania:

  1. Nie dodawaj abstrakcji, dopóki nie zaznajomisz się z EF. Dodaje już dobre abstrakcje, takie jak UoW, zapytania, używanie POCO itp.

  2. Aby funkcja DI działała, masz katalog główny, który odwołuje się do wszystkich potrzebnych składników. To może być lub nie być w projekcie WebUI. Jeśli tak nie jest, należy spodziewać się, że nie odwołuje się do EF ani żadnej innej technologii związanej z danymi.

  3. Zatrzymaj się tutaj. Przestań dodawać abstrakcje zamiast abstrakcji. Zacznij od bezpośredniej i „naiwnej” architektury i rozwijaj ją z czasem.

Abstrakcje są narzędziem radzenia sobie ze złożonością. Brak złożoności oznacza, że ​​abstrakcje nie są potrzebne (jeszcze).

Borys Jankow
źródło
Aby wyjaśnić, że rozumiem, co mówisz: repozytorium (którego interfejs istnieje w biznesie, a konkretny w Alcatraz.Data.Access?) Przyjmuje DbContextjako swoją zależność. Klasy biznesowe mają zależności jako repozytoria. W przypadku wstrzykiwania zależności robię to ręcznie (więc rozumiem, co się dzieje). Powodem, dla którego chcę ustawić parametry połączenia, DbContextjest shardowanie bazy danych, więc w niektórych przypadkach muszę mieć strukturę encji, aby połączyć się z różnymi bazami danych (o tej samej strukturze). Czy dobrze cię rozumiem?
Matthew
Z dostarczonego kodu wynika, że ​​wcale nie robisz DI. Głównym celem DI jest uwolnienie ciebie i twojego kodu od zarządzania zależnościami. Nie wyobrażam sobie, abyś robił to skutecznie ręcznie bez kontenera DI.
Boris Yankov,
również utrzymuj otwarty umysł dzięki DI. już dla zabawy zadałem tutaj dokładnie to samo pytanie na innym forum, aby uzyskać przeciwne odpowiedzi. DI to wzorzec, a nie architektura. W zależności od celu możesz zdecydować, czy chcesz go użyć, czy nie. Używam go, ale nie z powodów, dla których większość ludzi mówi mi, żebym go używał.
Bastien Vandamme
4

Kilka szybkich komentarzy. Osobiście prawdopodobnie nie przekazałbym ciągu połączenia. Jeśli cokolwiek bym spróbował stworzyć interfejsy dla repozytoriów i po prostu przekazać interfejsy? Poproś repozytoria o wdrożenie lub ujawnienie interfejsu IOW.

W ten sposób nie musi to być baza danych, która implementuje twoje repozytoria. mogą być pamięcią podręczną w pamięci lub czymkolwiek. Może więc możesz użyć jakiegoś szkieletu wstrzykiwania zależności, aby nawet utworzyć ich instancję?

W odpowiedzi na niektóre pytania:

  1. Tak, myślę, że jest w porządku
  2. Nadal będę mieć interfejs użytkownika do projektu EF i interfejsy referencyjne warstwy biznesowej, które implementuje warstwa repozytorium EF. W ten sposób inne projekty mogą nadal korzystać z tych samych zestawów, ale mają swobodę wymiany w razie potrzeby?
  3. hmmm, Prawdopodobnie repozytoria w warstwie dostępu, ale implementacja definicji interfejsu ujawnionego w warstwie biznesowej?

To tylko niektóre przemyślenia.

dreza
źródło
Jeśli chodzi o punkt 2, jednym z celów, które starałem się osiągnąć, jest brak CRUD bezpośrednio w warstwie interfejsu użytkownika. Chodzi mi o to, że chcę się upewnić, że tylko CRUD może się zdarzyć, przechodząc przez warstwę biznesową w ten sposób zarządzany.
Matthew