it-swarm.com.de

Bestes Repository-Muster für ASP.NET MVC

Ich habe vor kurzem ASP.NET MVC (ich liebe es) gelernt. Ich arbeite mit einem Unternehmen zusammen, das Abhängigkeitsinjektion zum Laden einer Repository-Instanz in jeder Anforderung verwendet, und ich bin mit der Verwendung dieses Repositorys vertraut.

Aber jetzt schreibe ich ein paar eigene MVC-Anwendungen. Ich verstehe nicht ganz genau, wie und warum das Repository meines Unternehmens verwendet wird, und ich versuche, den besten Ansatz für die Implementierung des Datenzugriffs zu ermitteln.

Ich verwende C # und Entity Framework (mit allen aktuellen Versionen).

Ich sehe drei allgemeine Ansätze für den Umgang mit dem Datenzugriff.

  1. Normaler DB-Kontext in einer using-Anweisung bei jedem Zugriff auf Daten. Das ist einfach und funktioniert gut. Wenn jedoch zwei Speicherorte dieselben Daten in einer Anforderung lesen müssen, müssen die Daten zweimal gelesen werden. (Bei einem einzigen Repository pro Anforderung würde dieselbe Instanz an beiden Stellen verwendet werden, und ich verstehe, dass der zweite Lesevorgang einfach die Daten des ersten Lesevorgangs zurückgibt.)

  2. Ein typisches Repository-Muster . Aus Gründen, die ich nicht verstehe, umfasst dieses typische Muster das Erstellen einer Wrapper-Klasse für jede Tabelle, die aus der Datenbank verwendet wird. Das scheint mir falsch zu sein. Da sie auch als Schnittstellen implementiert sind, würde ich technisch zwei Wrapper-Klassen für jede Tabelle erstellen. EF erstellt Tabellen für mich. Ich glaube nicht, dass dieser Ansatz sinnvoll ist.

  3. Es gibt auch ein generisches Repository-Muster , in dem eine einzige Repository-Klasse erstellt wird, um alle Entitätsobjekte zu bedienen. Das macht für mich viel mehr Sinn. Aber macht es für andere Sinn? Liegt der Link über dem besten Ansatz?

Ich würde gerne andere Informationen zu diesem Thema einholen. Schreiben Sie Ihr eigenes Repository, verwenden Sie eines der obigen oder machen Sie etwas ganz anderes. Bitte teilen.

63
Jonathan Wood

Ich habe eine Mischung aus # 2 und # 3 verwendet, aber ich bevorzuge ein striktes generisches Repository, wenn möglich (strenger als in Link 3 für Link 3 vorgeschlagen). # 1 ist nicht gut, weil es bei Unit-Tests schlecht spielt. 

Wenn Sie eine kleinere Domäne haben oder einschränken müssen, welche Entitäten von Ihrer Domäne abgefragt werden dürfen, nehme ich an, Nr. 2- oder Nr. 3, die entitätsspezifische Repository-Schnittstellen definieren, die selbst ein generisches Repository implementieren. Ich finde es jedoch anstrengend und unnötig, für jede Entität, die ich abfragen möchte, eine Schnittstelle und eine konkrete Implementierung zu schreiben. Was nützt public interface IFooRepository : IRepository<Foo> (wieder, es sei denn, ich muss die Entwickler auf eine Menge zulässiger Aggregatstämme beschränken)?

Ich definiere einfach meine generische Repository-Schnittstelle mit den Methoden Add, Remove, Get, GetDeferred, Count und Find (Find gibt eine IQueryable-Schnittstelle zurück, die LINQ ermöglicht), und ruft ein konkretes generisches Repository auf. Ich verlasse mich stark auf Find und damit auf LINQ. Wenn ich eine bestimmte Abfrage mehr als einmal verwenden muss, verwende ich Erweiterungsmethoden und schreibe die Abfrage mit LINQ.

Dies deckt 95% meines Persistenzbedarfs ab. Wenn ich eine Persistenzaktion durchführen muss, die nicht generisch ausgeführt werden kann, verwende ich eine ICommand-API aus eigenem Hause. Angenommen, ich arbeite mit NHibernate und muss eine komplexe Abfrage als Teil meiner Domäne ausführen, oder ich muss vielleicht einen Massenbefehl ausführen. Die API sieht ungefähr so ​​aus:

// marker interface, mainly used as a generic constraint
public interface ICommand
{
}

// commands that return no result, or a non-query
public interface ICommandNoResult : ICommand
{
   void Execute();
}

// commands that return a result, either a scalar value or record set
public interface ICommandWithResult<TResult> : ICommand
{
   TResult Execute();
}

// a query command that executes a record set and returns the resulting entities as an enumeration.
public interface IQuery<TEntity> : ICommandWithResult<IEnumerable<TEntity>>
{
    int Count();
}

// used to create commands at runtime, looking up registered commands in an IoC container or service locator
public interface ICommandFactory
{
   TCommand Create<TCommand>() where TCommand : ICommand;
}

Jetzt kann ich eine Schnittstelle erstellen, um einen bestimmten Befehl darzustellen.

public interface IAccountsWithBalanceQuery : IQuery<AccountWithBalance>
{
    Decimal MinimumBalance { get; set; }
}

Ich kann eine konkrete Implementierung erstellen und Raw SQL, NHibernate HQL oder was auch immer verwenden, und es bei meinem Service Locator registrieren.

Jetzt kann ich in meiner Geschäftslogik so etwas tun:

var query = factory.Create<IAccountsWithBalanceQuery>();
query.MinimumBalance = 100.0;

var overdueAccounts = query.Execute();

Sie können ein Spezifikationsmuster auch mit IQuery verwenden, um sinnvolle, vom Benutzer eingabegetriebene Abfragen zu erstellen, anstatt eine Schnittstelle mit Millionen verwirrender Eigenschaften zu haben. Dies setzt jedoch voraus, dass Sie das Spezifikationsmuster nicht als verwirrend empfinden;).

Ein letzter Teil des Puzzles ist, wenn Ihr Repository bestimmte Vor-und-Nachlager-Operationen ausführen muss. Jetzt können Sie ganz einfach eine Implementierung Ihres generischen Repositorys für eine bestimmte Entität erstellen, die relevanten Methoden überschreiben und tun, was Sie tun müssen, und Ihre IoC- oder Service Locator-Registrierung aktualisieren und damit fertig sein.

Manchmal ist diese Logik jedoch übergreifend und unpraktisch zu implementieren, indem eine Repository-Methode überschrieben wird. Also habe ich IRepositoryBehavior erstellt, was im Grunde eine Ereignissenke ist. (Unten ist nur eine grobe Definition von meinem Kopf)

public interface IRepositoryBehavior
{
    void OnAdding(CancellableBehaviorContext context);
    void OnAdd(BehaviorContext context);

    void OnGetting(CancellableBehaviorContext context);
    void OnGet(BehaviorContext context);

    void OnRemoving(CancellableBehaviorContext context);
    void OnRemove(BehaviorContext context);

    void OnFinding(CancellableBehaviorContext context);
    void OnFind(BehaviorContext context);

    bool AppliesToEntityType(Type entityType);
}

Jetzt können diese Verhaltensweisen alles sein. Überwachung, Sicherheitsüberprüfung, Soft-Delete, Durchsetzung von Domäneneinschränkungen, Validierung usw. Ich erstelle ein Verhalten, registriere es beim IoC oder Service Locator und modifiziere mein generisches Repository so, dass es eine Sammlung registrierter IRepositoryBehaviors enthält, und prüfe jedes Verhalten den aktuellen Repository-Typ, und wickeln Sie die Operation in die Pre-/Post-Handler für jedes anwendbare Verhalten ein.

Hier ist ein Beispiel für ein Soft-Delete-Verhalten (Soft-Delete bedeutet, dass jemand, wenn er zum Löschen einer Entität aufgefordert wird, nur als gelöscht markiert wird, damit sie nicht erneut zurückgegeben werden kann, sondern niemals physisch entfernt wird).

public SoftDeleteBehavior : IRepositoryBehavior
{
   // omitted

   public bool AppliesToEntityType(Type entityType)
   {
       // check to see if type supports soft deleting
       return true;
   }

   public void OnRemoving(CancellableBehaviorContext context)
   {
        var entity = context.Entity as ISoftDeletable;
        entity.Deleted = true; // when the NHibernate session is flushed, the Deleted column will be updated

        context.Cancel = true; // set this to true to make sure the repository doesn't physically delete the entity.
   }
}

Ja, dies ist im Grunde eine vereinfachte und abstrahierte Implementierung der Ereignis-Listener von NHibernate, aber deswegen gefällt es mir. A) Ich kann ein Verhalten testen, ohne NHibernate in das Bild aufzunehmen. B) Ich kann diese Verhalten außerhalb von NHibernate verwenden (das Repository ist eine Client-Implementierung, die REST -Dienstaufrufe umschließt.) C) Die Ereignis-Listener von NH können real sein Schmerzen im Arsch;)

36
HackedByChinese

Ich würde Nummer 1 empfehlen, mit einigen Vorbehalten. Nummer 2 scheint am häufigsten zu sein, aber nach meiner Erfahrung führt das Repository zu einem unordentlichen Speicherplatz für Abfragen. Wenn Sie ein generisches Repository (2) verwenden, ist dies nur ein dünner Wrapper um den DBContext. Ein bisschen sinnlos, wenn Sie nicht beabsichtigen, ORMs (schlechte Idee) zu ändern. 

Wenn ich jedoch direkt auf DBContext zugreife, verwende ich lieber ein Pipes and Filters-Muster, damit Sie die allgemeine Logik wiederverwenden können

items = DBContext.Clients
    .ByPhoneNumber('1234%')
    .ByOrganisation(134);

ByPhoneNumber und By Organization sind nur Erweiterungsmethoden.

12
Craig

Hier geht es zum besten Repository-Muster in Asp.Net MVC:  

Das Repository-Muster fügt eine Trennschicht zwischen den Daten- und Domänenschichten einer Anwendung hinzu. Dadurch werden auch die Datenzugriffsteile einer Anwendung besser überprüfbar.

Database Factory (IDatabaseFactory.cs):  

public interface IDatabaseFactory : IDisposable
{
    Database_DBEntities Get();
}

Datenbank-Factory-Implementierungen (DatabaseFactory.cs):  

public class DatabaseFactory : Disposable, IDatabaseFactory
{
    private Database_DBEntities dataContext;
    public Database_DBEntities Get()
    {
        return dataContext ?? (dataContext = new Database_DBEntities());
    }

    protected override void DisposeCore()
    {
        if (dataContext != null)
            dataContext.Dispose();
    }
}

Basisschnittstelle (IRepository.cs):  

public interface IRepository<T> where T : class
{
    void Add(T entity);
    void Update(T entity);
    void Detach(T entity);
    void Delete(T entity);
    T GetById(long Id);
    T GetById(string Id);
    T Get(Expression<Func<T, bool>> where);
    IEnumerable<T> GetAll();
    IEnumerable<T> GetMany(Expression<Func<T, bool>> where);
    void Commit();
}

Abstrakte Klasse (Repository.cs):

public abstract class Repository<T> : IRepository<T> where T : class
{
    private Database_DBEntities dataContext;
    private readonly IDbSet<T> dbset;
    protected Repository(IDatabaseFactory databaseFactory)
    {
        DatabaseFactory = databaseFactory;
        dbset = DataContext.Set<T>();
    }

    /// <summary>
    /// Property for the databasefactory instance
    /// </summary>
    protected IDatabaseFactory DatabaseFactory
    {
        get;
        private set;
    }

    /// <summary>
    /// Property for the datacontext instance
    /// </summary>
    protected Database_DBEntities DataContext
    {
        get { return dataContext ?? (dataContext = DatabaseFactory.Get()); }
    }

    /// <summary>
    /// For adding entity
    /// </summary>
    /// <param name="entity"></param>
    public virtual void Add(T entity)
    {
        try
        {
            dbset.Add(entity);
            //  dbset.Attach(entity);
            dataContext.Entry(entity).State = EntityState.Added;
            int iresult = dataContext.SaveChanges();
        }
        catch (UpdateException ex)
        {
        }
        catch (DbUpdateException ex) //DbContext
        {
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    /// <summary>
    /// For updating entity
    /// </summary>
    /// <param name="entity"></param>
    public virtual void Update(T entity)
    {
        try
        {
            // dbset.Attach(entity);
            dbset.Add(entity);
            dataContext.Entry(entity).State = EntityState.Modified;
            int iresult = dataContext.SaveChanges();
        }
        catch (UpdateException ex)
        {
            throw new ApplicationException(Database_ResourceFile.DuplicateMessage, ex);
        }
        catch (DbUpdateException ex) //DbContext
        {
            throw new ApplicationException(Database_ResourceFile.DuplicateMessage, ex);
        }
        catch (Exception ex) {
            throw ex;
        }
    }

    /// <summary>
    /// for deleting entity with class 
    /// </summary>
    /// <param name="entity"></param>
    public virtual void Delete(T entity)
    {
        dbset.Remove(entity);
        int iresult = dataContext.SaveChanges();
    }

    //To commit save changes
    public void Commit()
    {
        //still needs modification accordingly
        DataContext.SaveChanges();
    }

    /// <summary>
    /// Fetches values as per the int64 id value
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public virtual T GetById(long id)
    {
        return dbset.Find(id);
    }

    /// <summary>
    /// Fetches values as per the string id input
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public virtual T GetById(string id)
    {
        return dbset.Find(id);
    }

    /// <summary>
    /// fetches all the records 
    /// </summary>
    /// <returns></returns>
    public virtual IEnumerable<T> GetAll()
    {
        return dbset.AsNoTracking().ToList();
    }

    /// <summary>
    /// Fetches records as per the predicate condition
    /// </summary>
    /// <param name="where"></param>
    /// <returns></returns>
    public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where)
    {
        return dbset.Where(where).ToList();
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="entity"></param>
    public void Detach(T entity)
    {
        dataContext.Entry(entity).State = EntityState.Detached;
    }

    /// <summary>
    /// fetches single records as per the predicate condition
    /// </summary>
    /// <param name="where"></param>
    /// <returns></returns>
    public T Get(Expression<Func<T, bool>> where)
    {
        return dbset.Where(where).FirstOrDefault<T>();
    }
}

So greifen Sie auf dieses Repository-Muster im Controller zu:

1. Sie haben ein Benutzermodell:

public partial class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

2. Jetzt musst du die Repository-Klasse deines UserModels erstellen

public class UserRepository : Repository<User>, IUserRepository
{
    private Database_DBEntities dataContext;

    protected IDatabaseFactory DatabaseFactory
    {
        get;
        private set;
    }

    public UserRepository(IDatabaseFactory databaseFactory)
        : base(databaseFactory)
    {
        DatabaseFactory = databaseFactory;
    }

    protected Database_DBEntities DataContext
    {
        get { return dataContext ?? (dataContext = DatabaseFactory.Get()); }
    }

    public interface IUserRepository : IRepository<User>
    { 
    }
}

3. Nun müssen Sie UserService Interface (IUserService.cs) mit allen CRUD-Methoden erstellen:

public interface IUserService
{
    #region User Details 
    List<User> GetAllUsers();
    int SaveUserDetails(User Usermodel);
    int UpdateUserDetails(User Usermodel);
    int DeleteUserDetails(int Id);
    #endregion
}

4. Jetzt müssen Sie UserService Interface (UserService.cs) mit allen CRUD-Methoden erstellen:

public class UserService : IUserService
{
    IUserRepository _userRepository;
    public UserService() { }
    public UserService(IUserRepository userRepository)
    {
        this._userRepository = userRepository;
    }

    public List<User> GetAllUsers()
    {
        try
        {
            IEnumerable<User> liUser = _userRepository.GetAll();
            return liUser.ToList();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    /// <summary>
    /// Saves the User details.
    /// </summary>
    /// <param name="User">The deptmodel.</param>
    /// <returns></returns>
    public int SaveUserDetails(User Usermodel)
    {
        try
        {
            if (Usermodel != null)
            {
                _userRepository.Add(Usermodel);
                return 1;
            }
            else
                return 0;
        }
        catch
        {
            throw;
        }
   }

   /// <summary>
   /// Updates the User details.
   /// </summary>
   /// <param name="User">The deptmodel.</param>
   /// <returns></returns>
   public int UpdateUserDetails(User Usermodel)
   {
       try
       {
           if (Usermodel != null)
           {
               _userRepository.Update(Usermodel);
               return 1;
           }
           else
               return 0;
       }
       catch
       {
           throw;
       }
   }

   /// <summary>
   /// Deletes the User details.
   /// </summary>
   /// <param name="Id">The code identifier.</param>
   /// <returns></returns>
   public int DeleteUserDetails(int Id)
   {
       try
       {
           User Usermodel = _userRepository.GetById(Id);
           if (Usermodel != null)
           {
               _userRepository.Delete(Usermodel);
               return 1;
           }
           else
               return 0;
       }
       catch
       {
           throw;
       }
   }
}

5. Nun haben Sie alle für Ihr Repository-Muster festgelegt und können auf alle Daten in User Controller zugreifen:

//Here is the User Controller 
public class UserProfileController : Controller
{
    IUserService _userservice;
    public CustomerProfileController(IUserService userservice)
    {
        this._userservice = userservice;
    }

    [HttpPost]
    public ActionResult GetAllUsers(int id)
    {
        User objUser=new User();

        objUser = _userservice.GetAllUsers().Where(x => x.Id == id).FirstOrDefault();
    }
}
1
Laxman Gite

Es gibt eine gebrauchsfertige Lösung unter URF - Unit of Work & (erweiterbare/generische) Repositories Framework . Das spart viel Zeit ... Sie implementierten ein generisches Repository (es gibt auch ein asynchrones Repository). Für die Erweiterung eines Repositorys haben sie folgende Erweiterungen verwendet:

     public static decimal GetCustomerOrderTotalByYear(this IRepository<Customer> repository, string customerId, int year)
    {
        return repository
            .Queryable()
            .Where(c => c.CustomerID == customerId)
            .SelectMany(c => c.Orders.Where(o => o.OrderDate != null && o.OrderDate.Value.Year == year))
            .SelectMany(c => c.OrderDetails)
            .Select(c => c.Quantity*c.UnitPrice)
            .Sum();
    }

Einige Klassen wie QueryObject können abhängig von Ihrem Projekt überlastet sein, aber insgesamt ist es eine gute Lösung, um Sie beim Einstieg zu unterstützen.

0
Arvand