Alternative al pattern del repository per incapsulare la logica ORM?

22

Ho appena dovuto cambiare un ORM ed è stato un compito relativamente scoraggiante, perché la logica della query stava perdendo ovunque. Se mai avessi dovuto sviluppare una nuova applicazione, la mia preferenza personale sarebbe quella di incapsulare tutta la logica di query (usando un ORM) per renderlo disponibile per il futuro. Lo schema del repository è piuttosto fastidioso da codificare e mantenere quindi mi chiedevo se ci sono altri schemi per risolvere il problema?

Posso prevedere post su come non aggiungere complessità extra prima che sia effettivamente necessario, essere agile ecc., ma sono interessato solo ai pattern esistenti che risolvono un problema simile in un modo più semplice.

Il mio primo pensiero è stato quello di avere un repository di tipo generico, a cui aggiungo i metodi necessari per classi di repository di tipo specifico tramite metodi di estensione, ma i metodi statici di test unitario sono terribilmente dolorosi. IE:

public static class PersonExtensions
{
    public static IEnumerable<Person> GetRetiredPeople(this IRepository<Person> personRep)
    {
        // logic
    }
}
    
posta Dante 02.01.2013 - 00:28
fonte

5 risposte

14

Prima di tutto: un repository generico dovrebbe essere considerato una classe base e non complete implementazioni. Dovrebbe aiutarti a ottenere i metodi CRUD comuni. Ma devi ancora implementare i metodi di query:

public class UserRepository : EntityFrameworkRepository<User>, IUserRepository
{
    public UserRepository(DbContext context){}

    public ICollection<Person> FindRetired()
    {
        return context.Persons.Where(p => p.Status == "Retired").ToList();
    }
}

Secondo:

Non restituire IQueryable . È un'astrazione che perde. Prova ad implementare quell'interfaccia o usala senza conoscenza del provider di database sottostante. Ad esempio, ogni provider db ha la propria API per caricare con entusiasmo le entità. Ecco perché è un'astrazione che perde.

Alternativa

In alternativa puoi utilizzare le query (ad esempio, come definito dal modello di separazione Comando / Query). CQS è descritto in wikipedia.

In pratica, crei le classi di query che invochi:

public class GetMyMessages
{
    public GetMyMessages(IDbConnection connection)
    {
    }


    public Message[] Execute(DateTime minDate)
    {
        //query
    }
}

il bello delle query è che puoi utilizzare diversi metodi di accesso ai dati per diverse query senza ingombrare il codice. puoi ad esempio utilizzare un servizio web in uno, nibire in un altro e ADO.NET in un terzo.

Se sei interessato a un'implementazione .NET leggi il mio articolo: link

    
risposta data 02.01.2013 - 09:42
fonte
8

Per prima cosa, dico che ORM è già un'astrazione abbastanza grande sul tuo database. Alcuni ORM forniscono il binding a tutti i database relazionali comuni (NHibernate ha legami con MSSQL, Oracle, MySQL, Postgress, ecc.) Quindi costruire nuove astrazioni su di esso non mi sembra proficuo. Inoltre, argomentando che è necessario "astrarre" questo ORM non ha senso.

Se vuoi ancora costruire questa astrazione, andrei contro lo schema del Repository. È stato fantastico in epoca di puro SQL, ma è piuttosto fastidioso di fronte al moderno ORM. Soprattutto perché si finisce per implementare nuovamente la maggior parte delle funzionalità di ORM, come le operazioni CRUD e le query.

Se dovessi costruire tali astrazioni, userei queste regole / schemi

  • CQRS
  • Utilizza il più possibile le funzioni ORM esistenti
  • Incapsula solo la logica e le query complesse
  • Prova ad avere "l'impianto idraulico" dell'ORM nascosto all'interno di un'architettura

Nell'implementazione concreta, userei le operazioni CRUD di ORM direttamente, senza alcun involucro. Vorrei anche fare semplici interrogazioni direttamente nel codice. Ma query complesse sarebbero incapsulate nei loro oggetti. Per "nascondere" l'ORM, proverei a iniettare il contesto dei dati in modo trasparente in un servizio / oggetti dell'interfaccia utente e fare lo stesso per interrogare gli oggetti.

L'ultima cosa che vorrei dire è che molte persone usano ORM senza sapere come usarlo e come trarre il massimo "profitto" da esso. Le persone che raccomandano i repository di solito sono di questo tipo.

Come lettura consigliata, direi il blog di Ayende , in particolare questo articolo .

    
risposta data 02.01.2013 - 22:37
fonte
1

Il problema con l'implementazione leaky è che sono necessarie molte condizioni di filtro diverse.

Puoi restringere questa API se implementi un metodo di repository FindByExample e utilizzalo in questo modo

// find all retired persons
Person filter = new Person {Status=PersonStatus.Retired};
IEnumerable<Person> found = personRepository.FindByExample(filter);


// find all persons who have a dog named "sam"
Person filter = new Person();
filter.AddPet(new Dog{Name="sam"});
IEnumerable<Person> found = personRepository.FindByExample(filter);
    
risposta data 02.01.2013 - 20:58
fonte
0

Considera che le tue estensioni funzionano su IQueryable anziché su IRepository.

public static class PersonExtensions
{
    public static IQueryable<Person> AreRetired(this IQueryable<Person> people)
    {
        return people.Where(p => p.Status == "Retired");
    }
}

Al test unitario:

List<Person> people = new List<Person>();
people.Add(new Person() { Name = "Bob", Status = "Retired" });
people.Add(new Person() { Name = "Sam", Status = "Working" });

var retiredPeople = people.AsQueryable().AreRetired();
// assert that Bob is in the list
// assert that Sam is not in the list

Nessuna simulazione di derisione richiesta per il test. Puoi anche combinare questi metodi di estensione per creare query più complesse, se necessario.

    
risposta data 02.01.2013 - 04:06
fonte
0

Ho scritto un modello di oggetto query abbastanza pulito per NHibernate qui: link

Funziona usando un'interfaccia come questa:

public interface IDatabases
{
    ISessionManager SessionManager { get; }

    T Query<T>(IDatabaseQuery<T> query);
    T Query<T>(ICachedDatabaseQuery<T> query);

    void Command(IDatabaseCommand command);
    T Command<T>(IDatabaseCommand<T> command);
}

Data una classe di entità POCO come questa:

class Database1Poco
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
}

Puoi creare oggetti di query come questo:

class Database1PocoByProperty1 : DatabaseQuery<Database1Poco>
{
    public override Database1Poco Execute(ISessionManager sessionManager)
    {
        return sessionManager.Session.Query<Database1Poco>().SingleOrDefault(x => x.Property1 == Property1);
    }

    public int Property1 { get; set; }
}

E poi usali in questo modo:

var database1Poco = _databases.Query(new Database1PocoByProperty1 { Property1 = 1 });
    
risposta data 13.08.2015 - 21:10
fonte

Leggi altre domande sui tag