Come faccio a semplificare l'accesso di una relazione has-many con l'entità framework?

c# entity-framework entity-framework-core

Domanda

Ecco cosa voglio fare:

var user = db.User.First(conditions);
user.Book.First();

Ecco come devo farlo.

var user = db.User.Include("Book").First(conditionsForUser);
user.Book.First();

Il motivo per cui voglio semplificare questo è perché non voglio dover specificare cosa è incluso ogni volta che voglio accedere a una relazione. Sembra molto ingombrante.

es: vorrei solo essere in grado di fare quanto segue, dato che ho già recuperato un utente:

user.Book.First()
user.Blog.First()
user.SomeOtherHasManyRelationship.Where(conditions)

Ecco cosa ho finora:

    public object RelationshipFor(string relationship)
    {
        using (var db = User.DbContext())
        {
            var relationshipType = TypeRepresentedBy(relationship); // unused for now, not sure if I need the type of the relationship
            var myTable = ((ICollection)db.Send(RelationshipName)); // RelationshipName is "User" in this instance.
            var meWithRelationship = myTable.Where(i => i.Send(IdColumn) == Id).Include(relationship);  // currently, myTable doesn't know about 'Where' for some reason.
            return meWithRelationship.Send(relationship);
        }
    }

E poi come sarebbe usato sarebbe il seguente:

user.RelationshipFor("Book") // returns a list of books

Ho qualche altra logica nel mio codice che astrae ulteriormente ciò che mi permetterebbe di fare user.Book.First() . Spero di poter ottenere il permesso di open source molto di questo, dato che sto modellando molte delle API dopo Crud in stile ActiveRecord.

Nota che sto usando il set di estensioni che ho creato per aiutare a gestire la dinamica meno dolorosa: https://github.com/NullVoxPopuli/csharp-extensions

AGGIORNAMENTO 1:

    public object RelationshipFor(string relationship)
    {
        using (var db = User.DbContext())
        {
            var myTable = (DbSet<DatabaseModels.User>)db.Send(RelationshipName);
            var myInclude = myTable.Include(i => i.Send(relationship));
            var meWithRelationship = myInclude.First(i => (long)i.Send(IdColumn) == Id);
            return meWithRelationship.Send(relationship);
        }
    }

Per ora, ho codificato il cast dell'utente nel tentativo di ottenere qualcosa lavorando. Il mio errore ora è:

Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN' to type 'System.Linq.Expressions.MemberExpression'.

Risposta popolare

Questo non è un problema banale e non esiste un approccio "taglia unica". Ciò che in realtà sembra essere il caricamento pigro, che non è stato incluso in EF7 per molte ragioni .

Non so cosa il codice che mostri si supponga di fare, ma una opzione sarebbe quella di introdurre un modello di repository, in cui si specificano le "entità da includere" a livello di raccolta:

public class UserRepository
{
    private readonly IQueryable<User> _dataSet;

    public UserRepository(IQueryable<User> userDataSet)
    {
        _dataSet = userDataSet;
    }

    public IQueryable<User> Include()
    {
        return _dataSet.Include(u => u.Book)
                       .Include(u => u.Blog);
    }
}

E puoi spostare molta della logica in una generica classe base, lasciandoti solo il metodo Include() . Puoi ad esempio lavorare con le stringhe mentre mostri (o enumeri, o ...), per selezionare solo entità correlate da includere:

public class GenericRepository
{
    // ...

    public IQueryable<User> Include(string includeGroup = null)
    {
        return IncludeGroup(includeGroup);
    }

    protected virtual IncludeGroup(string includeGroup)
    {
        return _dataSet;
    }
}

E poi in UserRepository :

protected override IQueryable<User> IncludeGroup(string includeGroup)
{
    switch (includeGroup.ToUpperInvariant())
    {
        case "BOOK":
            return _dataSet.Include(u => u.Book)
                           .Include(u => u.Book.Author);
        case "BLOG":
            return _dataSet.Include(u => u.Blog);
        default:
            return base.Include(includeGroup);
    }
}

E quindi usarlo in questo modo:

var userRepo = new UserRepository(db.User);

var userWithBooks = userRepo.Include("Book");

var firstUser = userWithBooks.FirstOrDefault(u => u.Name == "Foo");

var firstUserFirstBook = firstUser.Book.FirstOrDefault();

Un'alternativa sarebbe quella di includere sempre tutte le proprietà di navigazione (in modo ricorsivo), ma sarebbe un approccio orribile in termini di efficienza della query, poiché ogni query sarà un enorme join a tutte le tabelle correlate, che sia necessario o meno.



Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché