2010-10-14 24 views
7

Próbuję ustalić, czy możliwe byłoby trzymanie się mantry "Program przeciwko interfejsowi, a nie implementacja". podczas korzystania z Entity Framework 4.0.Programowanie z interfejsami i Entity Framework 4.0

Podczas gdy znalazłem stronę wyjaśniającą, jak trzymać się powyższej zasady podczas korzystania z Linq-SQL (look here) Bardzo chciałbym wiedzieć, czy byłoby to możliwe z Entity Framework (również przy użyciu Linq) .

Jak to zwykle stosuje się:

var query = from pages in dataContext.Pages where pages.IsPublished select pages; 

foreach (Page page in query) 
{ 
    // do something with page... 
    var routeQuery = from routes in page.Route where route.IsValid select routes; 

    foreach (Route route in routeQuery) 
    { 
     // do something with route 
    } 
} 

Ale chciałbym używać go tak:

var query = from pages in dataContext.Pages where pages.IsPublished select pages; 

foreach (IPage page in query) 
{ 
    // do something with page... 
    var routeQuery = from routes in page.Route where route.IsValid select routes; 

    foreach (IRoute route in routeQuery) 
    { 
     // do something with route 
    } 
} 

Zasadniczo chciałbym być w stanie przejść DataContext z następujących Entity Framework z zespołu/podsystemu, w którym jest instancjonowany za pomocą interfejsu. Wszystkie informacje dostarczane przez kontekst danych powinny mieć postać interfejsu, a nie faktycznej klasy.

Chciałbym zachować rzeczywiste klasy wdrażające jednostki wewnątrz zespołu i ujawnić tylko te interfejsy, które implementują.

Czy to możliwe w Entity Framework? Jeśli nie, czy istnieje inny program odwzorowujący O/R, który można wykorzystać w ten sposób?

Jeśli nie jest to dobry sposób na dalsze oddzielenie DB od rzeczywistej aplikacji, chętnie wysłucham sugestii od Ciebie.

Odpowiedz

6

Dobrze lepszym rozwiązaniem (moim zdaniem), to należy wykonać następujące czynności:

Tworzenie Repozytoriami dla Entity Data Model, wystawiając albo ICollection<T> lub IQueryable<T>

Użyj interfejsy na repozytorium :

public interface IRepository 
{ 
    public ICollection<Person> Find(string name); // tighter, more maintanability  
    public IQueryable<Person> Find(); // full power! but be careful when lazy loading 

Ponieważ interfejsów, można zamienić-in-out mocks inny ORM na:

public class MockRepo : IRepository 
{ 
    public List<Person> persons; // mimics entity set 
} 

Jest tylko tyle, ile można oderwać.

Jeśli martwisz się o używanie ObjectSet (który jest powiązany z EF), użyj POCO.

Zobacz niektóre z moich pozostałych pytań, aby uzyskać więcej informacji (ponieważ budujemy teraz tę architekturę).

Zastanów się również nad użyciem Dependency Injection. Tutaj możesz odzyskać repozytorium z zakresu zarządzania obiektem ObjectContext - możesz wprowadzić repozytorium jako jednostkę pracy (która jest opakowaniem dla ObjectContext - tak, aby wiele repozytoriów lub zagregowanych katalogów głównych mogło obsługiwać ten sam kontekst).

W naszym rozwiązaniu nic nie dotyczy Entity Framework (ani żadnej logiki trwałości) poza Repozytorami, które znajdują się w osobnym zestawie.

HTH.

+0

Dzięki za odpowiedź. Czy jest możliwe, aby repozytorium zwróciło ICollection interfejsu, np. ICollection podczas korzystania z EF? –

+0

Kolejne pytanie :-): ile wystawiasz swoje repozytoriom na pozostałe aplikacje? Czy jest jakaś logika, np. IPersonProvider.GetPerson (nazwa łańcucha), która jest wywoływana przez aplikację i ostatecznie ukrywa repozytorium lub czy aplikacja wie o IRepository? –

+0

@Timo Kosig - Oba dobre pytania. :) 1) Tak, myślę, że byłoby to możliwe - tak długo, jak zwracasz ICollection, a nie IQueryable. W tym celu musisz użyć Pure POCO (bez generowania kodu). Które używam. Ale wadą tego jest to, że musisz zaimplementować cały interfejs POCO, aby pomóc repozytorium. Niezły pomysł IMO. Powinieneś odciągać wytrwałość, a nie fakt, że używasz POCO. POCO są proste i fajne - nie trzeba ich ukrywać. :) – RPM1984

0

Możesz tworzyć repozytoria, które wymagają twoich interfejsów nad konkretnymi obiektami, a używanie klas częściowych klas generowanych przez EF implementuje te interfejsy. Czy to naprawdę warte wysiłku, nie jestem pewien.

1

Możesz śledzić większość logiki w przykładzie L2S, który wspomniałeś, aby zaimplementować klasę EF Repository EF. Główną różnicą jest metoda Repozytorium. Jest trochę inaczej, ponieważ EF nie działa z Cast <> podczas przesyłania z jednego IQueryable do drugiego. Użyłem refleksji, aby uzyskać ten problem.

Oto przykład repozytorium metody ponownego pisemnej do repozytorium klasie EF:

public IQueryable<T> Repository<T>() 
    where T : class 
{ 
    if (typeof(T).IsInterface) 
    { 
     // if T is an interface then get the actual EntityObject Type by calling GetEntityType 
     // so that entityType can be used to call back to this method 
     Type entityType = this.GetEntityType<T>(); 
     MethodInfo mi = this.GetType().GetMethod("Repository"); 
     // set the T in Repository<T> to be the entity type 
     Type[] genericTypes = new Type[] { entityType }; 
     mi = mi.MakeGenericMethod(genericTypes); 
     // call Repository<T> 
     object result = mi.Invoke(this, new object[0]); 
     return result as IQueryable<T>; 
    } 
    else 
    { 
     return this._context.CreateQuery<T>(this.GetEntitySetName<T>()); 
    } 
} 

private Type GetEntityType<T>() 
{ 
    if (this.TableMaps.ContainsKey(typeof(T))) 
    { 
     return this.TableMaps[typeof(T)]; 
    } 
    else 
    { 
     return typeof(T); 
    } 
} 
+0

Naprawdę boję się tego kodu. Co u licha robisz? – RPM1984

+0

@Tom: wygląda mi to na odpowiednią czarną magię ;-). Nie jestem pewien, czy rozumiem to w pełni. Czy this.GetEntityType () wymaga pewnego rodzaju odwzorowania interfejsu na EntityType lub czy wystarczy, że nasza połowa częściowej klasy encji jest ustawiona na implementację tego interfejsu? –

+0

Ta metoda repozytorium sprawdza, czy typ T jest interfejsem. Jeśli tak, wyszuka klasę EntityObject, wywołując metodę GetEntityType.Metoda GetEntityType sprawdza, czy typ T został odwzorowany na obiekt EntityObject za pomocą właściwości TableMaps (co wyjaśniono w przykładzie L2S). Gdy obiekt EntityObject jest znany, metoda Repository jest wywoływana ponownie przez odbicie przy użyciu typu EntityObject zamiast interfejsu. Wyniki tego drugiego wywołania repozytorium są następnie rzutowane na typ interfejsu. –

0

udało mi się osiągnąć ten EF 5.0. Rozwiązanie jest zbyt skomplikowane, aby opublikować. Mogę podać opis na bardzo wysokim poziomie.

Klasa bazowa dla repozytorium wygląda jak ...

public class GenericRepository<TEntityInterface, TEntity> 
    where TDataInterface : TEntityInterface 
    where TEntity : class, IBaseEntityInterface, new() 

Podklasa tego repozytorium będzie wyglądać ...

public class EmployeeRepository<TEntity> : GenericRepository<IEmployeeEntity, TEntity>, IEmployeeRepository 
where TEntity : class, IEmployeeEntity, new() 

używam T4 templates dostosować generację jednostek dziedziczyć interfejsy encji, alternatywnie można utworzyć klasy częściowe dla każdej jednostki EF, aby dziedziczyć interfejsy. Wykorzystałem skrypty gen gen do generowania interfejsów dla podmiotów, które korelują z właściwościami na encji. Wszystkie elementy dziedziczą IBaseEntityInterface.

W pewnym momencie w kodzie (w moim przypadku przy użyciu ramy wtrysku wstrzykiwać), ożenisz podmiot EF z repozytorium jak tak ....

Bind<IEmployeeRepository>().To<EmployeeRepository<EmployeeEntity>(); 

Gdzie EmployeeEntity są generowane przez Entity Framework .

Występuje problem z tym podejściem, struktura encji nie lubi łączenia LINQ między interfejsami encji, może to spowodować błąd, w zależności od struktury zapytania.

Musisz wykonać kwerendy w repozytorium przeciwko TEntity. Możesz korzystać z właściwości nawigacji na interfejsach jednostek (a także w TEntity w repozytorium), aby efektywnie robić sprzężenia A-OK.

Jednak czasami będziesz chciał robić sprzężenia bez właściwości nawigacyjnych, obejście polega na ujawnieniu metod na repozytoriach, które zwracają prymitywne obiekty IQueryable do użytku w innych repozytoriach, np. Zapytanie o identyfikatory takie jak IQueryable lub kody IQueryable, tworzenie te w repozytorium dla innego repozytorium, aby uwzględnić je w zapytaniu.

Ogólnie uważam, że w dużej bazie kodu zalety korzystania z interfejsów encji zamiast bezpośredniego odwoływania się do klas elementów EF przeważają nad problemami, które napotkasz używając EF z interfejsami encji. Poza luźnym sprzężeniem, odpornością na zmiany itp., Możesz umieścić dużo kodu w repozytorium bazowym i zaprogramować w stosunku do TEntity za pomocą właściwości w twoim podstawowym interfejsie encji.

e.g Sposób, aby uzyskać zbiór interfejsów Entity pasujące predykat z szeregiem właściwości fabrycznie

public IList<TEntityInterface> Where(Expression<Func<TEntityInterface, bool>> predicate, params string[] includedProperties) 
    { 
     DbQuery<TEntity> query = Context.Set<TEntity>(); 

     foreach (string prop in includedProperties) 
      query = query.Include(prop); 

     return query.Where(predicate).ToList<TEntityInterface>(); 
    } 

O ile mi wiadomo odwoływania się podmioty Entity Framework w kodzie jest w zasadzie ciężko sprzęgania Kod do EDMX, a tym samym do określonego schematu bazy danych. Jeśli potrzebujesz obsługi wielu schematów baz danych z tą samą bazą kodu, Entity Framework po wyjęciu z pudełka pozostawia cię dość wysoko i sucho.

Mam nadzieję, że przy przyszłej wersji EF problemy te zostaną rozwiązane, a my wszyscy możemy być dobrymi małymi programistami i korzystać z interfejsów zamiast klas EF bez takich sztuczek.