Frage

Ich frage mich, wie man richtig gespannt Lade Problem für komplexe Objektgraphen zu handhaben, wenn Repository Muster. Dies ist nicht ORM spezifisches Problem, denke ich.

Erster Versuch:

public interface IProductRepository : IRepository<Product>
{
  Product GetById(int id);
  IProductRepository WithCustomers();
}

Das würde funktionieren, aber das würde bedeuten, mir die ganze Zeit zu wiederholen (Schreiben von benutzerdefinierten ‚mit‘ Methoden in Repository-Implementierungen überall).

Als nächster Ansatz:

public interface IRepository<T> where T : IAggregateRoot
{
  ...
  void With(Expression<Func<T, object>> propToExpand);
}

With Methode wird ein Element an privaten Sammlung, die später verwendet werden, um herauszufinden, was geladen eifrig Requisiten sollte, wenn notwendig Person / en abgerufen werden.

Diese Art ein Werk und ist in Ordnung. Aber ich lehne Nutzung:

productRepository.With(x=>x.Customer);
productRepository.With(x=>x.Price);
productRepository.With(x=>x.Manufacturer);
var product = productRepository.GetById(id);

Im Grunde genommen - Problem ist, dass es nicht ist verketten. Ich mag es so sein:

var product = productRepository
  .With(x=>x.Customer)
  .With(x=>x.Price)
  .With(x=>x.Manufacturer)
  .GetById(id);

ich nicht diese erreichen konnte. Selbst wenn ich könnte -. Ich bin nicht sicher, ob diese Lösung elegant sein würde

Dies führt zu Gedanken, die ich grundlegende etwas fehlt bin (Mangel an Beispielen überall). Gibt es verschiedene Möglichkeiten, wie dies zu umgehen? Was sind Best Practices?

War es hilfreich?

Lösung

Interessante Problem, und ich bin sicher, Sie sind nicht der erste Fehler, er mit diesem (I absolutelty haben).

Für mich ist die eigentliche Frage ist: Wo wollen Sie Ihre eifrigen Ladelogik setzen?

Außerhalb des Endlagers im Client-Code

var product = productRepository
.With(x=>x.Customer)
.With(x=>x.Price)
.With(x=>x.Manufacturer)
.GetById(id);

Ich denke nicht, das ist eine gute Software-Design. Sieht es so aus „Tod durch tausend Schnitte“ verursachen könnte, wenn solche Konstrukte durch Ihre ganze app verstreut sind

oder im Repository . Beispiel:

interface IProductRepository {
    Product GetById(int id);
    Product GetByIdWithCustomers(int i);
}

So Ihr Client-Code würde wie folgt aussehen:

var product = productRepository.GetByIdWithCustomers(id);

Normalerweise mache ich eine BaseRepository, die nur die grundlegenden CRUD-Operationen hat definiert:

public class BaseRepository<TEntity, TPrimaryKey> {
    public void Save(TEntity entity) { ... }
    public void Delete(TEntity entity) { ... }
    public TEntity Load(TPrimaryKey id) { ... } // just gets the entity by primary key
}

Dann erweitere ich diese Basisklasse / Schnittstellen, um spezifische Methoden zur Verfügung zu stellen für Domänenobjekte abgerufen werden. Ihr Ansatz scheint in einer etwas similiar Richtung zu gehen.

public class MediaRepository : BaseRepository<Media, int> {
    public long CountMediaWithCategories() { ... }
    public IList<Media> MediaInCategories(IList<Category> categories) { .... }
}

Die gute Sache: alle ORM stuff (eager loading config holen Tiefe usw.) in der Repository-Klasse gekapselt ist, erhält der Client-Code nur die Ergebnismenge.

habe ich versucht, mit sehr allgemeinen Repositories arbeiten, wie Sie versuchen zu tun, aber ich endete meist spezifische Abfragen und Repositories für meine Domain-Objekte Schreiben auf.

Andere Tipps

var product = productRepository
 .With(x=>x.Customer)
 .With(x=>x.Price)
 .With(x=>x.Manufacturer)
 .GetById(id);

kann ich verstehe Ihren Wunsch, wie oben die Abfrage Tiefe des Objektgraphen zu bestimmen, aber ich denke, es könnte ein einfacher Weg, es zu tun. Wie wäre es statt der Auswahl eines Produkt zurückzukehren (mit Kunden, Preis und Herstellern) von ID i einfach das Produkt zurück - und all die anderen Dinge sind faul geladen Eigenschaften von Produkt

Dies erreiche ich ‚vollständige Graph Zugänglichkeit‘ durch ‚Verkettung‘ von POCO-Objektmodell in meiner Datenzugriffsschicht. Auf diese Weise brauche ich nicht zu wissen, wie viel eifrig geladene Daten zu einem beliebigen Zeitpunkt zu ziehen, ich frage nur für das, was ich von dem Objektgraph muß, und das Modell weiß, was geladen ist und was zusätzlich von der DAL muss erholt. Werfen Sie einen Blick auf diese drei Antworten - ich versuche es meinen Ansatz zu erklären. Wenn Sie mehr benötigen Klärung lassen Sie mich wissen, und ich werde diese Antwort bearbeiten.

Es ist eine alte Frage, aber vielleicht kann es jemand helfen. Ich habe irgendwann verbrachte eine gute aproach zu finden, hier ist das, was ich in C # gefunden:

IRepository.cs:

public interface IRepository<TEntity> where TEntity : class
{
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> where
                              , params Expression<Func<TEntity, object>>[] properties);
}

Repository.cs

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{    
    private readonly DbSet<TEntity> _dbset;

    public Repository(DbSet<TEntity> dbset)
    {
        _dbset = dbset;
    }

    public virtual IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> where
                              , Expression<Func<TEntity, object>>[] properties)
    {
        if (where == null) 
            throw new ArgumentNullException(nameof(where));    
        if (properties == null) 
            throw new ArgumentNullException(nameof(properties));

        var query = _dbset as IQueryable<TEntity>; // _dbSet = dbContext.Set<TEntity>()

        query = properties
                   .Aggregate(query, (current, property) => current.Include(property));

        return query.AsNoTracking().Where(where).ToList();
    }
}

Wie verwenden:

var repository = new Repository<User>();
var users = repository.GetAll(p => p.Id == 1, d => d.Address, d => d.Carts);

Ref: Link-

Ich kann schätzen, was Sie zu tun versuchen, aber Sie sind etwas über die grundlegenden Repository-Muster.

Eine minimale Repository-Schnittstelle können Methoden für:

  • GetById
  • Hinzufügen
  • Entfernen

Wenn Sie oben auf, dass weitere Methoden hinzufügen, starten Sie in Situationen ausgeführt wird, wo die Schnittstelle nicht notwendigerweise Sinn für alle Ihre Gesamt Wurzeln machen.

Manchmal ist es einfach nicht möglich, eine ganz schöne API zu haben. Wenn das, was Sie Werke „gut genug“ für Sie haben, würde ich mit ihm gehen. Wenn Sie weg von den Repository-Mustern zu erhalten brauchen eine bessere API bereitzustellen, gegen zu programmieren, es zu tun!

Das Repository-Muster ist nicht be-alle / end-all-Lösung. Manchmal braucht man eine andere Lösung.

Wenn Sie möchten, dass alle die, um anzuzeigen, enthält, die Sie außerhalb Ihres Repository benötigen, können Sie optional params Liste (C #) für jede generische Methode:

TEntity Find(Func<TEntity, bool> expression, params string[] eagerLoads);

Dann auf Ihrer Client-Schicht:

IProductRepository.Find(x => x.Id == id, "Customer", "Price")

Wenn Sie Typ sicher sein wollen, aufzuzählen Ihre Entitäten:

public enum BusinessEntities { Customer, Price, Manufacturer }

IProductRepository.Find(x => x.Id == id, BusinessEntities.Customer.ToString(), BusinessEntities.Price.ToString())

Ich denke, dass es der Kunde ist, Verantwortung zu fragen speziell für das, was es will. Generisches Repository sollte nur grundlegende CRUD behandeln.

In der BaseRepository.cs können Sie diese Methode erstellen:

public async Task<IEnumerable<T>> GetWithChild(string child)
{
    return await _entities.Include(child).ToListAsync();
}

In meinem API habe ich implementiert auch eine Service-Schicht, sondern von der API ich einfach diese Methode aufrufen und den Namen der Variable übergeben zu laden.

Natürlich in Ihrer Situation, werden Sie ein paar Strings müssen umfassen.

Ich gab eine Antwort früher, aber ich war immer noch nicht zufrieden mit der Lösung. Also hier ist eine bessere Lösung.

in der BaseRepository.cs

public async Task<IEnumerable<T>> GetAll(params Expression<Func<T, object>>[] properties)
{
      IQueryable<T> query = _entities;

      query = properties.Aggregate(query, (current, property) => current.Include(property));

      return await query.AsNoTracking().ToListAsync();
}

und Sie können einfach die Methode wie folgt verwenden

await _service.GetAll(x => x.Customer, x => x.Price, x => x.Manufacturer); 
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top