2012-11-07 10 views
8

Durante il tentativo di creare un livello di accesso ai dati per un nuovo progetto, mi sono imbattuto in ciò che posso solo immaginare come un problema OOP/Design/Generics (utilizzando EF 4.3 per accedere al database).Come evitare la necessità di fare riferimento a Entity Framework sul mio livello di servizio?

Principalmente ho voluto realizzare due cose con questo livello di dati:

  • Diversi oggetti di contesto che ho sul mio progetto dovrebbe condividere la stessa stringa di connessione.
  • Classe di repository astratta con implementazione comune.

Per qualche motivo, non riesco a compilare la mia soluzione senza fare riferimento a EntityFramework sul livello di servizio. Quello che sto cercando è un modo per risolvere questo problema. Ecco cosa ho:

//Project/Namespace BusinessLogicLayer.DomainClasses 
//POCO classes mapped on Entity Framework. 

//Project/Namespace DataAccessLayer.Base 
//Base classes and interfaces for all data access layer, such as: 

public abstract class BaseContext<TContext> : DbContext where TContext : DbContext 
{ 
    //To allow multiple contexts sharing the same connection string 
    protected BaseContext(): base("name=MyConnectionString") {} 
} 

//Generic interface for a read-only repository 
public interface IReadOnlyRepository<T> : IDisposable where T : class 

//Generic interface for a read/write repository 
public interface IRepository<T> : IReadOnlyRepository<T> where T : class 

//Basic implementation for a read-only repository 
public abstract class BaseReadOnlyRepository<C, T> : IReadOnlyRepository<T> 
    where T : class 
    where C : BaseContext<C>, new() 
{ 
} 

//Basic implementation for a read/write repository 
public abstract class BaseRepository<C, T> : IRepository<T> 
    where T : class 
    where C : BaseContext<C>, new() 
{ 
} 

//Project DataAccessLayer.AccountContext/ Namespace DataAccessLayer 
//Context class: 

public class AccountContext : BaseContext<AccountContext> {} 

//With this, I can have simple repositories: 

public class UserRepository : BaseRepository<AccountContext, User> 
{ //All implementation comes from the base abstract class, unless I need to change it (override methods) 
} 

Ho un livello di servizio tra l'accesso ai dati e l'applicazione (Windows Form). Poiché ho un repository generico, mi è sembrata un'idea logica e buona avere servizi generici. Alla fine, molto simile alla struttura repository:

//Project/Namespace BusinessLogicLayer.Services 
//Service layer supposed to reference only the repository project and not Entity Framework. 

//Generic interface for a read-only service working with a read-only repository 
public interface IReadOnlyService<T> where T : class {} 

//Generic interface for a read/write service working with a read/write repository 
public interface IService<T> : IReadOnlyService<T> where T : class 

//Base implementation for a read-only service 
public abstract class BaseReadOnlyService<T, R> : IReadOnlyService<T> 
    where T : class 
    where R : IReadOnlyRepository<T>, new() 
{ 
} 

//Base implementation for a read/write service 
public abstract class BaseService<T, R> : IService<T> 
    where T : class 
    where R : IRepository<T>, new() 
{ 
} 

//Concrete sample service 
public class UserService : BaseService<User, UserRepository> 
{ //As with the repository I can change the default behavior of implementation overriding methods 
} 

Con questa configurazione, l'unico modo per compilare è quello di riferimento Entity Framework sul progetto livello di servizio. Come evitare la necessità di fare riferimento a Entity Framework lì?

A questo punto, sono disposto a buttare tutto fuori e ricostruire tutto ma questo è l'unico modo in cui ho trovato per farlo funzionare date le mie esigenze (DbContext condivisione stringhe di connessione, repository generico per evitare la replica del codice) .

Apprezzare qualsiasi aiuto. Grazie.

--edit - anche qui qualche passo in più che ho fatto 3 ore dopo che ho postato l'interrogo

Per capire questo, ho iniziato a creare un progetto di esempio con lo stesso codice di cui sopra oltre a qualche implementazione per simulare il più possibile i risultati del progetto originale.

Ho creato il progetto delle classi di dominio, l'intero progetto del livello dati di base e quindi il progetto di contesto. Ho notato che ho bisogno di fare riferimento a Entity Framework sul progetto di contesto anche se la classe di contesto non deriva direttamente da DbContext. Invece, deriva da una classe astratta che deriva da DbContext. Questo è ok anche se il mio contesto avrà DbSets e qualsiasi altra implementazione relativa a DbContext.

Il prossimo è il progetto di repository. È necessario fare riferimento a tutti gli altri tre (dominio, livello dei dati di base e contesto). Il mio repository non ha codice. Tutta la funzionalità si trova sull'antenato. Provo a compilare il progetto di repository e VS mi impone di fare riferimento a Entity Framework. Mi chiedo se sia davvero solo questione di incorporare le librerie. Se questo è confermato, sarà una sorpresa. La libreria Entity Framework è presente sull'output degli altri progetti. Perché dovrei fare riferimento anche qui? Cosa sta facendo VS richiede questo?

Ad ogni modo, a scopo di test, ho aggiunto il riferimento. Dopotutto, sono all'interno del livello dati. Posso vivere con quello. Passare al livello di servizio. Per semplicità, ho inserito tutte le classi di servizio nello stesso progetto.

Una possibile pecca è che uno dei vincoli per le classi di servizi astratti è l'interfaccia di repository.Ciò mi richiede di aggiungere un riferimento al livello dati di base sul mio livello di servizio. Forse già qui c'è qualcosa che posso fare che mi permette di usare solo il riferimento del repository. Non ho altra scelta che fare riferimento al livello dati di base.

Infine, il mio servizio concreto viene creato e VS mi dà il seguente messaggio di errore: Il tipo 'System.Data.Entity.DbContext' è definito in un assembly a cui non viene fatto riferimento. È necessario aggiungere un riferimento all'assembly 'EntityFramework, Versione = 4.3.1.0, Culture = neutral, PublicKeyToken = b77a5c561934e089'.

Quindi, alla fine, l'unico modo per andare avanti è fare riferimento a Entity Framework sul livello di servizio. E a un certo punto, quando costruisci l'app Windows Form, dovrò anche fare riferimento a Entity Framework.

Cosa devo fare per evitare di avere questi riferimenti? Quali miglioramenti posso avere su questa struttura?

Quello che so è che la mia app certamente non deve sapere che Entity Framework è coinvolto ovunque negli altri livelli. Neanche lo strato di servizio. I servizi consumeranno solo repository. I repository possono anche fornire dati falsi per i test.

Nel caso qualcuno fosse interessato, ho caricato il progetto che ho creato mentre scrivevo questo. È un file zip da 1,17 Mb senza binari (eccetto la DLL Entity Framework 4.3.1 che ho ottenuto tramite Nuget). Link: http://www.mediafire.com/?b45zkedy2j7eocc.

Ancora grazie per l'aiuto.

+0

Sembra una domanda stupida, ma sento la necessità di essere sicuro: il livello di accesso ai dati è compilato con il livello di servizio così com'è adesso? – tmesser

+0

Humm ... Non è affatto stupido. In effetti penso che tu mi abbia messo nella giusta direzione. Dopo aver letto il tuo commento ho creato un progetto di esempio che dimostra il problema. Ho messo i risultati dopo la domanda originale sopra. Per quanto riguarda la tua domanda, nessun livello di accesso ai dati viene compilato in dll differenti. –

risposta

8

Invece di avere estratto BaseContext nel BusinessLogicLayer, dichiarare un'interfaccia. Quindi implementalo nel tuo Data Access Layer.

public interface IDataContext : IDisposable 
{ 
    int SaveChanges(); 
} 

//Generic interface for a read-only repository 
public interface IReadOnlyRepository<T> : IDisposable where T : class 

//Generic interface for a read/write repository 
public interface IRepository<T> : IReadOnlyRepository<T> where T : class 

//Basic implementation for a read-only repository 
public abstract class BaseReadOnlyRepository<C, T> : IReadOnlyRepository<T> 
    where T : class 
    where C : IDataContext 
{ 
} 

//Basic implementation for a read/write repository 
public abstract class BaseRepository<C, T> : IRepository<T> 
    where T : class 
    where C : IDataContext 
{ 
} 


public interfaces IAccountContext : IDataContext 
{ 
    //other methods 
} 

Poi nel livello di accesso ai dati

public abstract class BaseContext : DbContext, IDataContext 
{ 
    //To allow multiple contexts sharing the same connection string 
    protected BaseContext(): base("name=MyConnectionString") {} 
} 

public class AccountContext : BaseContext, IAccountContext {} 

//With this, I can have simple repositories: 

public class UserRepository : BaseRepository<AccountContext, User> 
{ //All implementation comes from the base abstract class, unless I need to change it (override methods) 
} 

È possibile utilizzare DI/CIO di iniettare il contesto e il repository per i servizi, invece di istanziare il contesto all'interno del repository.

Questo disaccoppiamento rimuoverà la necessità di fare riferimento all'assieme EF nel livello della logica aziendale, ma ricorda che le entità del dominio non sono completamente indipendenti da EF. Ad esempio, le proprietà di navigazione, le correzioni delle relazioni non funzioneranno al di fuori del contesto EF. Quindi in un certo modo stai nascondendo una dipendenza !!

+0

In effetti le modifiche proposte mi hanno permesso di compilare il progetto del livello di servizio senza fare riferimento a Entity Framework. Sono un po 'bloccato perché ho perso la possibilità di invocare direttamente i metodi di contesto attraverso il repository e non ho molta familiarità con DI/IoC. Lavorerò a modo mio da dove sono adesso. La domanda principale è stata risposta. Voglio sottolineare che non avrò bisogno di problemi di relazione. Il repository sarà responsabile della restituzione degli oggetti con le opzioni Includi quando necessario. Quindi, i miei POCO sono in realtà solo POCO. Grazie. –

Problemi correlati