Pattern del repository: esposizione del contesto dei dati ai layer sottostanti

6

Il mio team sta aggiornando un progetto legacy. Abbiamo deciso di incorporare il modello di repository insieme a Entity Framework nel nostro livello di accesso ai dati. Di seguito è riportata una vista di alto livello di questa organizzazione:

IRepository<TEntity>èun'interfacciagenericautilizzatapereseguireoperazionicomunisuunsetdientità:

publicinterfaceIRepository<TEntity>whereTEntity:class{ObjectSet<TEntity>EntitySet{get;set;}TEntityGet(objectkey);voidInsert(TEntityentity);voidUpdate(TEntityentity);voidSave();}

Adesempio,sehoun'entitàEmployee,possocrearel'oggettoEmployeeRepository:IRepository<Employee>.LaclasseDataFactoryvienequindiutilizzatacomeoggettowrapperperoccuparsidiObjectContextdigenerazione,eliminazioneegestionedelleeccezioni:

publicclassDataFactory<TContext,TEntity>:IDisposablewhereTContext:ObjectContext,new()whereTEntity:class{privatebool_disposed=false;privateTContext_context;privateIRepository<TEntity>_repository;publicDataFactory(){_context=newTContext();IRepository<TEntity>repoistory;boolisRepositoryFound=TryGetRepository(outrepoistory);if(!isRepositoryFound){thrownewInvalidOperationException(string.Format("Unable to find repository of type {0}.", typeof(TEntity).FullName));
        }

        _repository = repoistory;
    }

    public DataFactory(TContext context, IRepository<TEntity> repository)
    {
        _context = context;
        _repository = repository;
    }

    public void Do(Action<TContext, IRepository<TEntity>> action)
    {
        try
        {
            action(_context, _repository);
        }
        catch (Exception ex)
        {
            ProcessException(ex);
        }
    }

    public TResult DoAndReturn<TResult>(Func<TContext, IRepository<TEntity>, TResult> action)
    {
        try
        {
            return action(_context, _repository);
        }
        catch (Exception ex)
        {
            ProcessException(ex);
            return default(TResult);
        }
    }

    private bool TryGetRepository(out IRepository<TEntity> repository)
    {
        Type repositoryType = Assembly.GetExecutingAssembly().GetTypes().SingleOrDefault(t =>
            typeof(IRepository<TEntity>).IsAssignableFrom(t));

        if (repositoryType == null)
        {
            repository = null;
            return false;
        }

        repository = (IRepository<TEntity>)Activator.CreateInstance(repositoryType, _context );
        return true;
    }
}

Infine, per utilizzare questa classe nel Business Layer, avremmo qualcosa di simile a:

using (var factory = new DataFactory<MyDataContext, Employee>())
{
    factory.Do((context, repository) =>
    {
       // Interact with the repository
    });
}

Il mio collega e io abbiamo avuto una lunga conversazione sul fatto che l'oggetto contesto dati debba essere esposto ai livelli sottostanti nei metodi Do() e DoAndReturn() . Mentre vedo casi in cui l'accesso diretto al contesto potrebbe essere utile (ad esempio nel caso in cui potremmo voler attivare / disattivare il caricamento lazy per un particolare set di entità), penso che così facendo sconfigge l'intero scopo di astrarre il Livello di accesso ai dati (che si ottiene fornendo un comune IRepository di contratto) poiché ora gli oggetti possono essere direttamente accessibili / manipolati attraverso ObjectContext .

Ha suggerito di avere due versioni differenti di questi metodi: uno che espone solo il repository e uno che espone sia il contesto che il repository. È un approccio accettabile? Qualsiasi suggerimento è apprezzato.

    
posta PoweredByOrange 05.09.2014 - 19:05
fonte

1 risposta

4

Il tuo setup lancia una domanda: perché hai introdotto le astrazioni? Se non riesci a rispondere (e ho la sensazione che non sei sicuro che tu possa, data la natura della domanda che hai postato) sbarazzati di loro. L'Entity Framework e altri ORM formano un'astrazione propria per l'interazione con il database, l'unità di lavoro, ecc. Avvolgere quello in un altro insieme di classi di astrazione che effettivamente astraggono la stessa cosa crea semplicemente più complessità. Se posso indovinare, penso che tu abbia creato questa astrazione, essere in grado di ordinare il lavoro di DB senza esporre le classi orientate ai DB, quindi essere in grado di immagazzinare il lavoro attraverso un repository senza lavorare con un contesto a livello BL. Ma in generale è solo un errore: chiamare in codice orientato al DB attraverso un wrapper non sta davvero astratizzando nulla.

Che cosa aiuta e cosa non hai fatto, sta creando classi per utilizzare l'ORM con la logica di business. Un tipico esempio di ciò è la logica che assicura che diversi metodi di business logic partecipino alla stessa transazione del database sottostante o meglio: il lavoro generato dalla logica di business viene raccolto nella stessa unità di lavoro.

Per riuscirci, è un inizio per modellare le azioni della logica di business in classi che possono ad es. portare a una serie di comandi. Il BL non ha accesso a nessun codice orientato al DB, semplicemente formulano ciò che dovrebbe essere fatto. Gli oggetti comando / comando vengono quindi inviati alla classe che utilizza la logica orientata DB e che esegue effettivamente i comandi / le azioni.

Il pattern del repository è "bello" ma IMHO è troppe volte in cui le persone si mettono nei guai abbastanza rapidamente poiché crea una gerarchia di dipendenze separata accanto alle dipendenze delle entità stesse (ad esempio, Customer.Orders, con repository che non dovresti essere in grado di accedi alla raccolta degli ordini, poiché fanno parte del repository degli ordini) e non è una risposta reale a ciò con cui realmente stanno lottando.

Ora sarebbe stato bello se EF avesse una unità separata di classe lavorativa che sarebbe stata una soluzione facile per il tuo problema, ma ahimè ... anche la MS ha seguito la progettazione di un ORM di Ambler e si è conclusa con una sessione centrale / contesto / unità di lavoro ibrido.

    
risposta data 06.09.2014 - 14:32
fonte

Leggi altre domande sui tag