net - repository pattern c# beispiel




Bestes Repository-Muster für ASP.NET MVC (3)

Es gibt eine gebrauchsfertige Lösung bei URF - Unit of Work & (erweiterbar / generisch) Repositories Framework . Es wird Ihnen viel Zeit sparen. Sie haben ein generisches Repository implementiert (es gibt auch ein asynchrones Repository). Um ein Repository zu erweitern, haben sie Erweiterungen wie folgt 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 eine Überarbeitung darstellen, sind aber in der Regel eine gute Lösung, um Ihnen den Einstieg zu erleichtern.

Ich habe kürzlich ASP.NET MVC gelernt (ich liebe es). Ich arbeite mit einem Unternehmen zusammen, das Dependency Injection verwendet, um eine Repository-Instanz in jede Anfrage zu laden, und ich bin mit der Verwendung dieses Repositorys vertraut.

Aber jetzt schreibe ich ein paar eigene MVC-Anwendungen. Ich verstehe das Wie und Warum des von meiner Firma verwendeten Repositorys nicht vollständig und versuche, den besten Ansatz für die Implementierung des Datenzugriffs zu finden.

Ich verwende C # und Entity Framework (mit den neuesten Versionen).

Ich sehe drei allgemeine Ansätze zur Handhabung des Datenzugriffs.

  1. Regulärer DB-Kontext innerhalb einer using-Anweisung bei jedem Zugriff auf Daten. Das ist einfach und es funktioniert gut. Wenn jedoch zwei Standorte die gleichen Daten innerhalb einer Anforderung lesen müssen, müssen die Daten zweimal gelesen werden. (Bei einem einzigen Repository pro Anfrage würde an beiden Stellen die gleiche Instanz verwendet werden, und ich verstehe, dass der zweite Lesevorgang einfach die Daten aus dem ersten Lesevorgang zurückgibt.)

  2. Ein typisches Repository-Muster . Aus Gründen, die ich nicht verstehe, beinhaltet dieses typische Muster die Erstellung 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 viel mehr Sinn für mich. Aber macht es für andere Sinn? Ist der Link über dem besten Ansatz?

Ich würde gerne etwas Input von anderen zu diesem Thema bekommen. Schreiben Sie Ihr eigenes Repository, verwenden Sie eines der oben genannten, oder machen Sie etwas ganz anderes. Bitte teilen.


Ich habe eine Mischung von # 2 und # 3 verwendet, aber ich bevorzuge ein strenges generisches Repository, wenn möglich (strenger als sogar in dem Link für # 3 vorgeschlagen). # 1 ist nicht gut, weil es beim Komponententesten schlecht spielt.

Wenn Sie eine kleinere Domäne haben oder einschränken müssen, welche Entitäten Ihre Domäne abfragen darf, nehme ich an, dass # 2- oder # 3, die Entitäts-spezifische Repository-Schnittstellen definieren, die selbst ein generisches Repository implementieren, sinnvoll sind. Ich finde es jedoch anstrengend und unnötig, eine Schnittstelle und eine konkrete Implementierung für jede Entität zu schreiben, die ich abfragen möchte. Was public interface IFooRepository : IRepository<Foo> (wiederum, es sei denn, ich muss Entwickler auf eine Menge erlaubter aggregierter Roots beschränken)?

Ich definiere einfach meine generische Repository-Schnittstelle mit GetDeferred , GetDeferred , GetDeferred , GetDeferred , GetDeferred und Find Methoden (Find gibt eine IQueryable Schnittstelle zurück, die LINQ erlaubt), erstelle eine konkrete generische Implementierung und nenne sie einen Tag. Ich verlasse mich stark auf Find und somit auf LINQ. Wenn ich eine bestimmte Abfrage mehr als einmal verwenden muss, verwende ich Erweiterungsmethoden und schreibe die Abfrage mit LINQ.

Dies deckt 95% meiner Persistenz Bedürfnisse. Wenn ich eine Persistenzaktion ausführen muss, die nicht generisch durchgeführt werden kann, verwende ich eine ICommand API, die aus dem eigenen Haus stammt. Angenommen, ich arbeite mit NHibernate und muss eine komplexe Abfrage als Teil meiner Domäne ausführen, oder vielleicht muss ich 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, was auch immer, verwenden und es bei meinem Service Locator registrieren.

Nun kann ich in meiner Geschäftslogik so etwas machen:

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

var overdueAccounts = query.Execute();

Sie können auch ein Spezifikationsmuster mit IQuery , um aussagekräftige, benutzereingabegesteuerte Abfragen zu erstellen, anstatt eine Schnittstelle mit Millionen verwirrenden Eigenschaften zu haben, aber davon IQuery , dass Sie das Spezifikationsmuster nicht für sich selbst verwirrend finden;).

Ein letzter Teil des Puzzles ist, wenn Ihr Repository spezifische Pre- und Post-Repository-Operationen durchführen muss. Jetzt können Sie ganz einfach eine Implementierung Ihres generischen Repositorys für eine bestimmte Entität erstellen, dann die relevante (n) Methode (n) außer Kraft setzen 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 umständlich 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 der Spitze meines Kopfes)

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);
}

Nun, diese Verhaltensweisen können alles sein. Auditing, Sicherheitsprüfung, Soft-Delete, Erzwingen von Domäneneinschränkungen, Validierung usw. Ich erstelle ein Verhalten, registriere es beim IoC oder Service Locator und verändere mein generisches Repository, um eine Sammlung registrierter IRepositoryBehavior s aufzunehmen und jedes Verhalten zu überprüfen gegen den aktuellen Repository-Typ und umbrechen Sie die Operation in den Pre / Post-Handler für jedes anwendbare Verhalten.

Hier sehen Sie ein Beispiel für ein Verhalten zum Löschen von Daten (Soft-Delete bedeutet, dass jemand, der eine Entität löschen möchte, diese als gelöscht markiert, so dass sie nicht erneut zurückgegeben werden kann, aber nie 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 deshalb gefällt es mir. A) Ich kann Einheiten testen, ohne NHibernate in das Bild zu bringen B) Ich kann diese Verhaltensweisen außerhalb von NHibernate verwenden (sagen wir, das Repository ist eine Client-Implementierung, die REST-Serviceaufrufe umschließt) C) Die Event-Listener von NH können sehr schmerzhaft sein ;)


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. Es macht auch die Datenzugriffsteile einer Anwendung besser testbar.

Datenbankfabrik (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. Nun müssen Sie die Repository-Klasse Ihres 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. Nun 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. Jetzt haben Sie alle für Ihr Repository-Muster festgelegt und Sie können auf alle Daten im Benutzer-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();
    }
}




repository-pattern