2013-07-24 17 views
7

Mam zamiar rozpocząć nowy projekt, który będzie używać Entity Framework (EF) jako ORM do połączenia z SQL Server. Dużo czytałem, aby zrozumieć, jakie są dobre podejścia do pracy z EF w odniesieniu do abstrakcji i ostatecznie sprawiają, że mój kod można przetestować.Czy korzystanie z repozytorium abstrakcji w ramach struktury Entity Framework jest dobre, czy nie?

Problem polega na tym, że im więcej czytasz, tym bardziej jestem zdezorientowany, że staję się z tym wszystkim.

Myślałem że to dobry sposób, aby przejść:

miałem zamiar zejść repozytorium trasy, należy je więc są łatwe do wstrzykiwania poprzez interfejsy i łatwo drwić dla celów testowych, z partii przykładów, które wydaje mi się znaleźć, jest wielu, którzy przysięgają na to podejście. Dla części repozytoriów, które współdziałają z EF, miałem zamiar przejść testy integracyjne, ponieważ w moich repozytoriach nie byłoby logiki biznesowej, przesunąłbym to na klasy Service/Controller, aby sobie z tym poradzić.

Niektórzy mówią, że Repository Wzór jest nieuzasadnione abstrakcji

http://ayende.com/blog/3955/repository-is-the-new-singleton

Były też inne linki, ale nie chciał zalać to pytanie zbyt wiele

I niby dostać to rozumiem, że EF i jego realizacja są w zasadzie abstrakcją, ale wydaje mi się, że jest to konkretna (trafia ona w coś nieobjętego zakresem zastosowania ... DB) Problemem jest dla mnie p używa dla mnie kwestii testowania (przynajmniej w początkowej architekturze), w której prawdopodobnie będę wchodził w interakcję z danymi dotyczącymi danych i jednostką pracy z poziomu mojej usługi, ale wydaje mi się, że łączę logowanie dostępu do danych z logiką biznesową na tym poziomie a potem, jak do cholery mam to sprawdzić?

Wydaje mi się, że czytałem tak dużo, że nie mam teraz jasnego kierunku i nieunikniony blok programistów włączył się. Próbuję szukać podejścia, które jest czyste i mogę testować przeciwko.

AKTUALIZACJA - Rozwiązanie mam zamiar zacząć od

poszedłem nieco inną ścieżkę następnie jeden w odpowiedzi udzielonej przez Simona ale dzięki artykuł, który czytałem, ale on ponownie przedłożony tak ja przeszedł to jeszcze raz. Po pierwsze używam Code First, ponieważ mam istniejącą bazę danych iz jakiegoś powodu nie lubię narzędzi projektantów. Stworzyłem kilka prostych obiektów POCO i kilka mapowań, aby wszystko było proste. Po prostu pokażę jedno POCO/Mapping, a co nie.

POCO

public abstract class BaseEntity<T> 
{ 
    [Key] 
    public T Id { get; set; } 
    public string Name { get; set; } 
    public DateTime CreatedOn { get; set; } 
}  

public class Project : BaseEntity<int> 
{ 
    public virtual ICollection<Site> Sites { get; set; } 
    public bool Active { get; set; } 
}  

Jednostka interfejsu Work

public interface IUnitOfWork 
{ 
    IDbSet<Project> Projects { get; } 

    void Commit(); 
} 

Kontekst

internal class MyContext : DbContext 
{ 
    public MyContext(string connectionString) 
     : base(connectionString) 
    { 

    } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Configurations.Add(new ProjectMap()); 
    } 
} 

Concrete UnitOfWork Kod

public class UnitOfWork : IUnitOfWork 
{ 
    readonly MyContext _context; 
    const string ConnectionStringName = "DBConn"; 

    public UnitOfWork() 
    { 
     var connectionString = ConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString; 
     _context = new FosilContext(connectionString); 
    } 

    public IDbSet<Project> Projects 
    { 
     get { return _context.Set<Project>(); } 
    } 

    public void Commit() 
    { 
     _context.SaveChanges(); 
    } 
} 

podstawowy test

 var _repo = new UnitOfWork().Projects; 
     var projects = from p in _repo 
         select p; 

     foreach (var project in projects) 
     { 
      Console.WriteLine(string.Format("{0} - {1} - {2} - {3})", project.Id, project.Name, project.Active.ToString(), project.CreatedOn.ToShortDateString())); 
     } 

     Console.Read(); 

UnitOfWork jest oczywista, ale w oparciu o konkretny kontekst, mogę stworzyć fałszywy IUnitOfWork i przekazać go w fałszywym IDBSet do testowania . Dla mnie, i to może wrócić, by mnie ugryźć w architekturę, tym bardziej dostaję się do tego, ale nie mam Repo na tym oddalającym się EF (myślę). Jak wyjaśnia artykuł, IDbSet jest odpowiednikiem Repo, więc to, czego używam. Trochę ukrywam swój niestandardowy kontekst za UU, ale zobaczę, jak to wygląda.

To, co teraz myślę, to mogę użyć tego w warstwie usługowej, która będzie zawierała pobieranie danych i reguł biznesowych, ale powinna być testowana, ponieważ mogę podrobić określone elementy EF. Miejmy nadzieję, że uczyni moje kontrolery dość szczupły, ale zobaczymy :)

+0

Nie zapominaj, że jeśli używasz Entity Framework, 'DbContext' _is_ jest podobne do UnitOfWork i' DbSet' _is_ jest podobne do repozytorium. To pytanie, jak sądzę, zachęca do osobistej opinii i może nie być odpowiednie dla Stack Overflow, ale uważam, że jeśli zamierzasz używać Entity Framework, równie dobrze możesz przyjąć schemat. Dalsze pakowanie 'DbContext' i' DbSet's jest naprawdę bardzo cienką abstrakcją dla konkretnej klasy dla łatwości testowania jednostkowego i, do pewnego stopnia, Dependency Injection. –

+0

Dzięki za komentarz. To, czego nie dostaję (jestem bardzo nowy w EF) to wygląd cienkiej warstwy. Oglądałem film o EF i testowaniu, autor wykorzystał IDBSet i wprowadził interfejs IContext, ale z osobną jednostką pracy (te przykłady używały repozytorium), która została przekazana. Cienka warstwa była IDBSet i IContext, ale nawet autor zauważył, że ukrywałeś EF za pomocą tych abstrakcji, które wydawały się dokładnie takie same, jak problem, jaki ludzie mają ze Wzorcem Repozytorium? – Modika

+0

Przenoszę ścianę tekstu do poprzedniej odpowiedzi. Ograniczenia postaci powodują, że formatowanie jest trochę trudne. –

Odpowiedz

7

nie należy zapominać, że w przypadku korzystania z Entity Framework, DbContextjest zbliżona do UnitOfWork i DbSetjest zbliżona do repozytorium. To pytanie, jak sądzę, zachęca do osobistej opinii i może nie być odpowiednie dla Stack Overflow, ale uważam, że jeśli zamierzasz używać Entity Framework, równie dobrze możesz przyjąć schemat. Dalsze pakowanie modeli DbContext i DbSet jest naprawdę bardzo cienką abstrakcją dla konkretnej klasy dla łatwości testowania jednostkowego i, do pewnego stopnia, Dependency Injection.

Istnieje temat na MSDN (Testability and Entity Framework 4.0), który może dać dobry punkt wyjścia. Istnieje kilka źródeł tu i tam sugestii, jak wdrożyć ten wzorzec w kontekście Entity Framework. W większości przypadków użyjesz DbContext do wykonania SaveChanges() i DbSet<T> do wykonywania operacji CRUD za pomocą narzędzia IQueryable<T> na DbSet<T>. Możesz wdrożyć swój IUnitOfWork, aby naśladować to, co potrzebujesz na swoim DbContext i to samo dla DbSet.

wdrożenia w tym zakresie byłoby prawie będzie odwzorowaniem 1-do-1 z metod DbContext i DbSet<T>, ale można również realizować niestandardowe IUnitOfWork który implementuje w pamięci opartej IRepository<T> (używając HashSet s na przykład), że ułatwiać jednostkowe testowanie logiki zapytań względem komponentów Queryable. Czy wzór repozytorium jest dobrym pomysłem? To jest dyskusyjne. To naprawdę zależy od projektów i elastyczności, której potrzebujesz i od tego, co chcesz robić na różnych warstwach (i czego nie chcesz).

Edytuj, aby odpowiedzieć na komentarz: Implementacja UnitOfWork nie powinny dziedziczyć DbContext ale utworzyć instancję, utrzymać go i przekazać prywatny własną metodę połączenia z kontekstu. Twoja implementacja Repository<T> prawdopodobnie zajęłaby DbSet<T> w wewnętrznym konstruktorze. Jedną z największych zalet podejścia Model-First jest swoboda przyznana organizacji montażu. To, co zwykle robię, polega na oddzieleniu modelu i DbContext w dwóch oddzielnych złożeniach, przy czym ten drugi odnosi się do pierwszego. Ten scenariusz wyglądałby następująco:

1 - Twój zespół Models.dll zawiera Twój POCO

2 - Twój zespół Data.dll zawiera interfejsy i wdrażania. Możesz także mieć (na przykład) plik Data.Core.dll, który zawiera interfejsy i typowe narzędzia dotyczące warstwy dostępu do danych i ma wymienny plik Data.Entity.dll (lub Data.List.dll) dla rzeczywistej implementacji. Jeśli pójdziemy z pierwszej opcji, może to wyglądać tak:

public interface IUnitOfWork { /* Your methods */ } 

public interface IRepository<T> where T : class { /* Your methods */ } 

internal class YourDbContext : DbContext { /* Your implementation */ } 

public class YourDatabaseContext : IUnitOfWork 
{ 
    private readonly YourDbContext dbContext; 

    public YourDatabaseContext() 
    { 
     // You could also go with the Lazy pattern here to defer creation 
     dbContext = new YourDbContext(); 
    } 
} 

internal class DbSetRepository<T> : IRepository<T> where T : class 
{ 
    private readonly DbSet<T> dbSet; 

    public DbSetRepository(DbSet<T> dbSet) 
    { 
     // You could also use IDbSet<T> for a toned down version 
     this.dbSet = dbSet; 
    } 
} 

w tym scenariuszu, tylko twój interfejs i implementacja IUnitOfWork jest widoczna na zewnątrz zespołu (jeśli chcesz używać Dependency Injection, na przykład). Nie jest to kluczowe i naprawdę zależy od wyboru projektu. Ponieważ twoje DbContext będzie "połączone" z twoim POCO przez definicję twojej wewnętrznej implementacji i wszystko będzie połączone w twoich konfiguracjach.

+0

Dzięki za ten link, przeczytałem to wczoraj, ale myślę, że myliłem się z nazywaniem, ponieważ używają UoW zamiast kontekstu, ale tutaj wygląda to zamiennie. Jedną z rzeczy (ale to nie jest tak naprawdę związane z tym) jest to, że nie mogłem znaleźć miejsca do dodania mapowań, aby połączyć mój model z bazą danych, dlatego próbowałem szukać gdzie indziej. – Modika

+0

@Modika Co masz na myśli przez odwzorowania? Twoje EDMX? –

+0

Nie używam EDMX, ponieważ przechodziłem pierwsze podejście kodu, ale odwzorowania łączące moje POCO z reprezentacją bazy danych. Mam kilka mapowań EntityTypeConfiguration, ale myślę, patrząc na ten artykuł, konkretne implementacje kontekstu (UoF) mogą po prostu dziedziczyć z DbContext, a następnie mam dostęp do metody OnModelCreating ... nie jestem pewien, czy to by działało ... – Modika

0

Korzystamy z naszej własnej platformy abstrakcji ORM, która obsługuje repozytorium, jednostkę pracy i wzorce specyfikacji na kilka lat i na podstawie naszego doświadczenia, zdecydowanie powiedziałbym, że tak!

Taka abstrakcja niekoniecznie gwarantuje wymienność. Kolejną bardzo ważną korzyścią jest rozciągliwość.

Na przykład w przypadku jednego projektu, po wydaniu, nowym wymogiem było "Opublikowane" właściwość w niektórych encji, a interfejs musiał ukryć jednostki, które nie zostały opublikowane. W tym scenariuszu rozszerzyliśmy nasz framework, aby obsługiwał "domyślną specyfikację", która jest bardzo podobna do "filtrów sesji" Hibernate, które mają zastosowanie do wszystkich zapytań wykonanych za pomocą pojedynczego DbContext.

Ponieważ mamy już „żądanie scoped” cykl życia systemu DbContext (co jest rzeczywiście najlepszym sposobem teraz w owin i .NET aplikacji opartych na rdzeń), to było bardzo łatwe dla nas, aby dodać taką API:

CurrentContext.AddDefaultSpec(new Spec<Product>(t => t.Published)) 

toll dodaje daną specyfikację do listy specyfikacji produktu w bieżącym kontekście i stosuje wszystkie specyfikacje produktów do kwerendy wyrażeń zawartych na

Repository<Product>.Query().Where(....) 

oczywiście nasz system Spec był już zdolny do łączenia wyrażeń z wielu lambda i specyfikacje, ale to jest poza zakresem.

W celu wykonania tej funkcji opracowaliśmy prosty atrybut filtru ASP .NET MVC, a w większości naszych działań kontrolerów (z wyjątkiem części administracyjnych) umieszczamy ten atrybut.

Jedna uwaga: ponieważ abstrahowanie zarówno metod synchronizacji, jak i asynchronizacji było bardzo trudne i nie było warte wysiłku, a także potrzebujemy wywołań specyficznych dla EF, takich jak .Include() itp., Kod klienta nadal zawiera odniesienia do EF, które nie pomaga, jeśli musimy zastąpić EF inną ORM. Nie było to jednak wymogiem iw praktyce w większości projektów nie wymyśla się takiego wymogu.

Jeśli więc powinieneś go streścić za pomocą repozytorium, to po prostu stwórz "idealny" niezależny od platformy interfejs API, a jeśli nie tworzysz ogólnej platformy do kierowania na różne ORMy lub interfejsy dostępu do danych, to nie sądzę, że jest właściwą drogą.

Naszym głównym celem było stworzenie wewnętrznego systemu opartego na EF z dodatkowymi narzędziami, wsparcie dla wspólnych wzorców projektowych i sterowanej zdarzeniami jednostki pracy (np. Obserwowanie zdarzeń, gdy jakiś typ obiektu jest dodawany do obiektu), było to odpowiednie rozwiązanie problemu. Chociaż nie było to łatwe do wdrożenia.

Powiązane problemy