Domanda

Io sono nel processo di progettazione di mio ASP.NET applicazione MVC e mi sono imbattuto in un paio di considerazioni interessanti.

Tanti i campioni che ho visto descrivere e utilizzare il modello di Repository (IRepository) quindi questo è il modo in cui l'ho fatto mentre stavo imparando MVC.

Ora so di cosa si sta facendo, ho iniziato a guardare il mio attuale design e chiedo se è il modo migliore per andare.

Attualmente ho una base IUserRepository, che definisce i metodi come FindById(), SaveChanges(), etc.

Attualmente, ogni volta che voglio caricare/query la tabella utente nel DB, faccio qualcosa lungo le linee delle seguenti operazioni:

    private IUserRepository Repository;

    public UserController()
        : this(new UserRepository())
    { }

    [RequiresAuthentication]
    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Edit(string ReturnUrl, string FirstRun)
    {
        var user = Repository.FindById(User.Identity.Name);

        var viewModel = Mapper.Map<User, UserEditViewModel>(user);
        viewModel.FirstRun = FirstRun == "1" ? true : false;

        return View("Edit", viewModel);
    }

    [AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken(Salt = "SaltAndPepper")]
    public ActionResult Edit(UserEditViewModel viewModel, string ReturnUrl)
    {
        //Map the ViewModel to the Model
        var user = Repository.FindById(User.Identity.Name);

        //Map changes to the user
        Mapper.Map<UserEditViewModel, User>(viewModel, user);

        //Save the DB changes
        Repository.SaveChanges();

        if (!string.IsNullOrEmpty(ReturnUrl))
            return Redirect(ReturnUrl);
        else
            return RedirectToAction("Index", "User");
    }

Ora, io non comprendere appieno come MVC funziona in quanto riguarda la creazione di un controller quando un utente crea un link (non so se c'è 1 controller per utente o 1 controller per applicazione), quindi io non sono positivo per il miglior corso di azione.

Ho trovato una grande domanda per quanto riguarda l'uso di un generico repository interfaccia IRepository<T> qui e hanno anche sembrare l'idea di un statici RepositoryFactory su un numero di blog.Fondamentalmente è solo 1 esempio dell'archivio è conservato in assoluto ed è ottenuto tramite questa fabbrica

Quindi la mia domanda ruota intorno a come le persone fanno là dentro apps, e che cosa è considerato una buona pratica.

Le persone hanno le singole repositorys base di ogni tabella (IUserRepository)?
Fanno uso generico IRepository<T>?
Fanno uso deposito di fabbrica?
O qualcosa di completamente?

EDIT:Ho appena capito che io debba chiedere così:

Sta avendo un privato IRepository in ogni controller un buon modo per andare?o devo creare una nuova IRepository ogni volta che voglio usare?

BOUNTY MODIFICA:Sto iniziando una taglia per avere più punti di vista (non che Tim non era disponibile).

Io sono più curioso di sapere ciò che le persone fanno nella loro MVC apps o quello che pensi sia una buona idea.

È stato utile?

Soluzione

Alcuni problemi molto evidenti con l'idea di un IRepository<T> generico:

  • Si presuppone che ogni entità utilizza lo stesso tipo di chiave, che non è vero in quasi tutti i sistemi non banale. Alcune entità utilizzeranno GUID, altri possono avere un qualche tipo di chiave naturale e / o composito. NHibernate in grado di supportare questo abbastanza bene, ma LINQ to SQL è piuttosto male a lui -. Si deve scrivere una buona dose di codice hacker per fare la mappatura automatica delle chiavi

  • Ciò significa che ogni repository può trattare solo con esattamente un tipo di entità e supporta solo le operazioni più banali. Quando un repository è relegata ad un tale semplice involucro CRUD ha molto poco a tutti. Si potrebbe anche solo consegnare al cliente un IQueryable<T> o Table<T>.

  • Si presuppone che che si eseguono esattamente le stesse operazioni su ogni entità. In realtà questo sta per essere molto lontano dalla verità. Certo, forse si vuole ottenere che Order dal suo ID, ma più probabilmente si vuole ottenere un elenco di oggetti Order per un determinato cliente e all'interno di alcuni intervallo di date. La nozione di un IRepository<T> totalmente generica non tiene conto del fatto che sarà quasi certamente desidera eseguire diversi tipi di query su diversi tipi di entità.

Il punto del modello repository è quello di creare un astrazione sopra modelli di accesso di dati comuni. Credo che alcuni programmatori si annoiano con la creazione di repository così dicono "Hey, lo so, creerò un über-repository in grado di gestire qualsiasi tipo di entità!" Il che è ottimo, tranne che il repository è praticamente inutile per l'80% di quello che stai cercando di fare. Va bene come classe base / interfaccia, ma se questo è la piena portata del lavoro che fate, allora siete solo di essere pigro (e garantiscono mal di testa futuro).


Idealmente potrei iniziare con un repository generico che sembra qualcosa di simile:

public interface IRepository<TKey, TEntity>
{
    TEntity Get(TKey id);
    void Save(TEntity entity);
}

Si noterà che questo non hanno una funzione List o GetAll - che è perché è assurdo pensare che sia accettabile per recuperare i dati da un intero tavolo in una sola volta in qualsiasi parte del codice. Questo è quando è necessario iniziare a entrare nel repository specifici:

public interface IOrderRepository : IRepository<int, Order>
{
    IEnumerable<Order> GetOrdersByCustomer(Guid customerID);
    IPager<Order> GetOrdersByDate(DateTime fromDate, DateTime toDate);
    IPager<Order> GetOrdersByProduct(int productID);
}

E così via - si ottiene l'idea. In questo modo abbiamo la repository "generico" per se abbiamo mai effettivamente bisogno incredibilmente semplicistiche recuperare-by-id la semantica, ma in generale siamo mai effettivamente andando a passare che in giro, non certo per una classe controller.


Ora, come per i controllori, devi fare questo diritto, altrimenti hai praticamente negato tutto il lavoro che hai appena fatto a mettere insieme tutti i repository.

Un regolatore ha bisogno di prendere il suo repository dal resto del mondo . La ragione per cui si è creato questi archivi è così si può fare una sorta di Inversion of Control. Il tuo obiettivo finale è quello di essere in grado di scambiare un repository per un altro -. Per esempio, per fare test di unità, o se si decide di passare da LINQ to SQL per Entity Framework ad un certo punto in futuro

Un esempio di questo principio è il seguente:

public class OrderController : Controller
{
    public OrderController(IOrderRepository orderRepository)
    {
        if (orderRepository == null)
            throw new ArgumentNullException("orderRepository");
        this.OrderRepository = orderRepository;
    }

    public ActionResult List(DateTime fromDate, DateTime toDate) { ... }
    // More actions

    public IOrderRepository OrderRepository { get; set; }
}

In altre parole il controllore ha idea di come creare un repository , né deve. Se avete qualche repository-lavori in corso in là, è la creazione di accoppiamento che davvero non si vuole. La ragione per cui i controllori di esempio ASP.NET MVC hanno costruttori senza parametri che crea repository concreti è che i siti devono essere in grado di compilare ed eseguire senza costringere a istituire un intero framework Dependency Injection.

Ma in un sito di produzione, se non sta passando in dipendenza repository attraverso un costruttore o di proprietà pubblica, allora stai perdendo il tuo tempo avere repository a tutti, perché i controllori sono ancora strettamente accoppiati al database layer. È necessario essere in grado di scrivere codice di prova in questo modo:

[TestMethod]
public void Can_add_order()
{
    OrderController controller = new OrderController();
    FakeOrderRepository fakeRepository = new FakeOrderRepository();
    controller.OrderRepository = fakeRepository; //<-- Important!
    controller.SubmitOrder(...);
    Assert.That(fakeRepository.ContainsOrder(...));
}

Non si può fare questo se il vostro OrderController sta andando fuori e la creazione di un proprio repository. Questo metodo di prova non dovrebbe fare alcun accesso ai dati, si fa solo in modo che il controller sia richiamando il metodo repository corretto in base all'azione.


Non è DI ancora, si badi bene, questo è solo fingendo / beffardo. Dove DI entra in scena è quando si decide che LINQ to SQL non sta facendo abbastanza per voi e si vuole veramente il HQL in NHibernate, ma sta andando a prendere 3 mesi di tempo per ogni cosa porta sopra, e si vuole essere in grado di fare questo un repository alla volta. Così, ad esempio, utilizzando un quadro DI come Ninject , tutto ciò che dovete fare è cambiare questo:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<LinqToSqlOrderRepository>();
Bind<IProductRepository>().To<LinqToSqlProductRepository>();

A:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<NHibernateOrderRepository>();
Bind<IProductRepository>().To<NHibernateProductRepository>();

E ci si sta, ora tutto ciò che dipende da IOrderRepository utilizza la versione di NHibernate, hai avuto solo cambiare di una riga del codice a differenza di potenzialmente centinaia di linee. E stiamo correndo il LINQ to SQL e NHibernate versioni fianco a fianco, porting funzionalità nel pezzo per pezzo, senza mai rompere nulla in mezzo.


Quindi, per riassumere tutti i punti che ho fatto:

  1. Non fare affidamento unicamente su un'interfaccia IRepository<T> generica. La maggior parte delle funzionalità che si desidera da un repository è specifica , non generici . Se si desidera includere un IRepository<T> ai livelli superiori della gerarchia di classe / interfaccia, va bene, ma i controllori dovrebbero dipendere specifica repository in modo da non finire per dover modificare il codice in 5 differenti posti quando si scopre che il repository generico manca metodi importanti.

  2. Controllers dovrebbero accettare depositi dal di fuori, non creare il proprio. Questo è un passo importante per eliminare accoppiamento e migliorare controllabilità.

  3. Normalmente ti consigliamo di cablare i controllori che utilizzano un quadro Dependency Injection, e molti di loro possono essere integrati con ASP.NET MVC. Se questo è troppo per voi di prendere in, poi per lo meno si dovrebbe utilizzare un qualche tipo di fornitore di servizi statico in modo è possibile centralizzare tutta la logica repository-creazione. (Nel lungo periodo, probabilmente troverete più facile da imparare e utilizzare solo un quadro DI).

Altri suggerimenti

Le persone hanno individuali repositorys sulla base di ogni tabella (IUserRepository)? Tendo ad avere un repository per ciascun aggregato, non ogni tabella.

Usano un IRepository generica? se possibile, sì

Usano una fabbrica repository statico? Io preferisco l'iniezione di un'istanza del repository attraverso un contenitore IOC

Ecco come lo sto usando. Sto usando IRepository per tutte le operazioni che sono comuni a tutti i miei repository.

public interface IRepository<T> where T : PersistentObject
{
    T GetById(object id);
    T[] GetAll();
    void Save(T entity);
}

e uso anche dedicato un ITRepository per ogni aggregato per l'operazione che sono distinte a questo repository. Ad esempio per l'utente userò IUserRepository per aggiungere metodo che sono distinti a UserRepository:

public interface IUserRepository : IRepository<User>
{
    User GetByUserName(string username);
}

L'implementazione sarà simile a questa:

public class UserRepository : RepositoryBase<User>, IUserRepository
{
    public User GetByUserName(string username)
    {
        ISession session = GetSession();
        IQuery query = session.CreateQuery("from User u where u.Username = :username");
        query.SetString("username", username);

        var matchingUser = query.UniqueResult<User>();

        return matchingUser;
    }
}


public class RepositoryBase<T> : IRepository<T> where T : PersistentObject
{
    public virtual T GetById(object id)
    {
        ISession session = GetSession();
        return session.Get<T>(id);
    }

    public virtual T[] GetAll()
    {
        ISession session = GetSession();
        ICriteria criteria = session.CreateCriteria(typeof (T));
        return criteria.List<T>().ToArray();
    }

    protected ISession GetSession()
    {
        return new SessionBuilder().GetSession();
    }

    public virtual void Save(T entity)
    {
        GetSession().SaveOrUpdate(entity);
    }
}

Che nel UserController guarderà come:

public class UserController : ConventionController
{
    private readonly IUserRepository _repository;
    private readonly ISecurityContext _securityContext;
    private readonly IUserSession _userSession;

    public UserController(IUserRepository repository, ISecurityContext securityContext, IUserSession userSession)
    {
        _repository = repository;
        _securityContext = securityContext;
        _userSession = userSession;
    }
}

Che il repository viene creata un'istanza usando il modello della iniezione di dipendenza utilizzando fabbrica di controllo personalizzato. Sto utilizzando StructureMap come il mio livello di iniezione di dipendenza.

Il livello database è NHibernate. ISession è la porta verso il database in questa sessione.

Sono suggerisco di guardare il CodeCampServer struttura, si può imparare molto da esso.

Un altro progetto che si può imparare fro è che mi può aiutare . Che io non scavare abbastanza in esso ancora.

Le persone hanno le singole repositorys basato su ogni tavolo (IUserRepository)?

Sì, questa è la scelta migliore per 2 motivi :

  • DAL mio si basa su Linq-to-Sql (ma il mio DTOs sono interfacce basate su LTS entità)
  • le operazioni eseguite sono atomic (l'aggiunta è un'operazione atomica, il risparmio è un altro, etc.)

Usa un generico IRepository?

Sì, esclusivamente, ispirato DDD Entità/Valore di schema che ho creato IRepositoryEntity / IRepositoryValue e un generico IRepository per altre cose.

Fanno uso deposito di fabbrica?

Sì e no :Io uso un IOC container richiamato tramite una classe statica.Bene...si può dire che è una specie di fabbrica.

Nota :Ho progettato questa architettura per conto mio e di un mio collega ha trovato così grande che ci sono attualmente la creazione di tutta la nostra società quadro di questo modello (sì, è un'azienda giovane).Questo è sicuramente qualcosa che vale la pena provare, anche se ho la sensazione che i quadri di che tipo saranno rilasciati da grandi attori.

Si può trovare un'ottima Generico Repopsitory library che è stato scritto per poter essere utilizzato come un WebForms asp:ObjectDataSource su codeplex: MultiTierLinqToSql

Ogni mio controller di quelli privati per le azioni che hanno bisogno di sostegno.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top