Entity Framework - Gibt es eine Möglichkeit, untergeordnete Entitäten ohne Include () automatisch zu laden?

74

Gibt es eine Möglichkeit, Ihre POCO-Klassen so zu dekorieren, dass untergeordnete Entitäten automatisch geladen werden, ohne sie Include()jedes Mal verwenden zu müssen, wenn Sie sie laden?

Angenommen, ich habe ein Klassenauto mit komplexen Eigenschaften für Räder, Türen, Motor, Stoßstange, Fenster, Auspuff usw. Und in meiner App muss ich mein Auto von meinem DbContext an 20 verschiedenen Orten mit unterschiedlichen Abfragen usw. laden Ich möchte nicht angeben müssen, dass ich jedes Mal, wenn ich mein Auto laden möchte, alle Eigenschaften einbeziehen möchte.

ich möchte sagen

List<Car> cars = db.Car
                .Where(x => c.Make == "Ford").ToList();

//NOT .Include(x => x.Wheels).Include(x => x.Doors).Include(x => x.Engine).Include(x => x.Bumper).Include(x => x.Windows)


foreach(Car car in cars)
{

//I don't want a null reference here.

String myString = car.**Bumper**.Title;
}

Kann ich meine POCO-Klasse oder in meiner irgendwie dekorieren OnModelCreating()oder eine Konfiguration in EF festlegen, die besagt, dass beim Laden meines Autos nur alle Teile meines Autos geladen werden sollen? Ich möchte dies eifrig tun, daher verstehe ich, dass es nicht möglich ist, meine Navigationseigenschaften virtuell zu machen. Ich weiß, dass NHibernate ähnliche Funktionen unterstützt.

Ich frage mich nur, ob mir etwas fehlt. Danke im Voraus!

Prost,

Nathan

Ich mag die folgende Lösung, frage mich aber , ob ich die Aufrufe an die Erweiterungsmethoden verschachteln kann. Angenommen, ich habe eine ähnliche Situation mit Engine, in der es viele Teile gibt, die ich nicht überall einbeziehen möchte. Kann ich so etwas machen? (Ich habe noch keinen Weg gefunden, dies zu funktionieren). Auf diese Weise kann ich, wenn ich später herausfinde, dass Engine FuelInjectors benötigt, diese nur in der BuildEngine hinzufügen und muss sie nicht auch in BuildCar hinzufügen. Wie kann ich einen Anruf an eine Sammlung verschachteln, wenn ich die Anrufe verschachteln kann? Möchten Sie BuildWheel () für jedes meiner Räder in meinem BuildCar () aufrufen?

public static IQueryable<Car> BuildCar(this IQueryable<Car> query) {
     return query.Include(x => x.Wheels).BuildWheel()
                 .Include(x => x.Doors)
                 .Include(x => x.Engine).BuildEngine()
                 .Include(x => x.Bumper)
                 .Include(x => x.Windows);
}

public static IQueryable<Engine> BuildEngine(this IQueryable<Engine> query) {
     return query.Include(x => x.Pistons)
                 .Include(x => x.Cylendars);
}

//Or to handle a collection e.g.
 public static IQueryable<Wheel> BuildWheel(this IQueryable<Wheel> query) {
     return query.Include(x => x.Rim)
                 .Include(x => x.Tire);
}

Hier ist ein weiterer sehr ähnlicher Thread für den Fall, dass er für andere in dieser Situation hilfreich ist, aber immer noch nicht in der Lage ist, die Erweiterungsmethoden als nächstes aufzurufen.

Entity Framework linq query Include () mehrere untergeordnete Entitäten

Nathan Geffers
quelle
Nathan, sehr interessante Herausforderung. Bitte erläutern Sie, warum Sie die Verwendung aktiver Ladetechniken wie include () vermeiden möchten / müssen.
Dave Alperovich
Nein, können Sie nicht, aber nichts hindert Sie daran, eine Eigenschaft wie DbQuery<Car> CompleteCars { get { return ...(Ihre lange IncludeKette) im DbContext oder in einer Repository-Klasse zu erstellen. Wenn Sie könnten, würde das nicht die Möglichkeit ausschließen, "nackte" Autos aus der Datenbank zu holen?
Gert Arnold
Ich möchte dies vermeiden, weil ich ein komplexes Modell habe, aber nur sehr wenige Datensätze. Daher möchte ich, dass die Daten eifrig geladen werden, aber keine Einschlüsse von Enkelkindern irgendwo in der App verpassen.
Nathan Geffers
Verwandte Frage / Duplikat: stackoverflow.com/questions/5001311/…
Stefan

Antworten:

65

Nein, das können Sie beim Mapping nicht tun . Eine typische Problemumgehung ist die einfache Erweiterungsmethode:

public static IQueryable<Car> BuildCar(this IQueryable<Car> query) {
     return query.Include(x => x.Wheels)
                 .Include(x => x.Doors)
                 .Include(x => x.Engine)
                 .Include(x => x.Bumper)
                 .Include(x => x.Windows);
}

Jetzt Cartun Sie jedes Mal, wenn Sie alle Beziehungen abfragen möchten, Folgendes :

var query = from car in db.Cars.BuildCar()
            where car.Make == "Ford"
            select car;

Bearbeiten:

Auf diese Weise können Sie Anrufe nicht verschachteln. Schließen Sie Arbeiten an der Kernentität ein, mit der Sie arbeiten. Diese Entität definiert die Form der Abfrage. Nach dem Aufruf arbeiten Include(x => Wheels)Sie also noch mit IQueryable<Car>und können die Erweiterungsmethode für nicht aufrufen IQueryable<Engine>. Sie müssen erneut beginnen mit Car:

public static IQueryable<Car> BuildCarWheels(this IQuerable<Car> query) {
    // This also answers how to eager load nested collections 
    // Btw. only Select is supported - you cannot use Where, OrderBy or anything else
    return query.Include(x => x.Wheels.Select(y => y.Rim))
                .Include(x => x.Wheels.Select(y => y.Tire));
}

und Sie werden diese Methode folgendermaßen anwenden:

public static IQueryable<Car> BuildCar(this IQueryable<Car> query) {
     return query.BuildCarWheels()
                 .Include(x => x.Doors)
                 .Include(x => x.Engine)
                 .Include(x => x.Bumper)
                 .Include(x => x.Windows);
}

Die Verwendung wird nicht aufgerufen, Include(x => x.Wheels)da sie automatisch hinzugefügt werden sollte, wenn Sie das eifrige Laden der verschachtelten Entitäten anfordern.

Hüten Sie sich vor komplexen Abfragen, die von solch komplexen eifrigen Ladestrukturen erzeugt werden. Dies kann zu einer sehr schlechten Leistung und zu vielen doppelten Daten führen, die aus der Datenbank übertragen werden.

Ladislav Mrnka
quelle
Genau das brauche ich! Vielen Dank!
Nathan Geffers
Könnte ich Aufrufe dieser Erweiterungsmethoden für untergeordnete Entitäten verschachteln? (Siehe aktualisierte Frage)
Nathan Geffers
1
Wenn ich ein [Include] -Attribut habe, bekomme ich meine Stimme! (siehe Link in der Antwort)
Nathan Geffers
Das bedeutet also, dass ich immer noch eine separate Methode zum Laden von Felgen und Reifen schreiben muss, wenn ich Busse, Autos, Lastwagen usw. laden muss. Ich werde sie nur in meine LoadBus-, LoadTruck- und LoadCar-Methoden aufnehmen, aber ich habe versucht, dies zu vermeiden Mit diesen Methoden muss man etwas über die Kinder von Tyrus wissen. Schade, aber zumindest habe ich meine Antwort. Vielen Dank!
Nathan Geffers
die für die Leistung gut ist , lazy loadingoder eager loadingich kann aus sehen hier seine lazy loadingi, eigentlich bin versucht , Produkte zu suchen , die Beziehung zu anderen Tabellen hat, was Sie denken?
Shaijut
8

Hatte das gleiche Problem und sah den anderen Link, der ein IncludeAttribut erwähnte . Bei meiner Lösung wird davon ausgegangen, dass Sie ein Attribut mit dem Namen erstellt haben IncludeAttribute. Mit der folgenden Erweiterungsmethode und Dienstprogrammmethode :

    public static IQueryable<T> LoadRelated<T>(this IQueryable<T> originalQuery)
    {
        Func<IQueryable<T>, IQueryable<T>> includeFunc = f => f;
        foreach (var prop in typeof(T).GetProperties()
            .Where(p => Attribute.IsDefined(p, typeof(IncludeAttribute))))
        {
            Func<IQueryable<T>, IQueryable<T>> chainedIncludeFunc = f => f.Include(prop.Name);
            includeFunc = Compose(includeFunc, chainedIncludeFunc);
        }
        return includeFunc(originalQuery);
    }

    private static Func<T, T> Compose<T>(Func<T, T> innerFunc, Func<T, T> outerFunc)
    {
        return arg => outerFunc(innerFunc(arg));
    }
Lunyx
quelle
0

Wenn Sie WIRKLICH alle Navigationseigenschaften einbeziehen müssen (Sie könnten Leistungsprobleme haben), können Sie diesen Code verwenden.
Wenn Sie auch Sammlungen eifrig laden müssen (selbst eine schlechteste Idee), können Sie die continue-Anweisung im Code löschen.
Der Kontext ist Ihr Kontext (dies, wenn Sie diesen Code in Ihren Kontext einfügen)

    public IQueryable<T> IncludeAllNavigationProperties<T>(IQueryable<T> queryable)
    {
        if (queryable == null)
            throw new ArgumentNullException("queryable");

        ObjectContext objectContext = ((IObjectContextAdapter)Context).ObjectContext;
        var metadataWorkspace = ((EntityConnection)objectContext.Connection).GetMetadataWorkspace();

        EntitySetMapping[] entitySetMappingCollection = metadataWorkspace.GetItems<EntityContainerMapping>(DataSpace.CSSpace).Single().EntitySetMappings.ToArray();

        var entitySetMappings = entitySetMappingCollection.First(o => o.EntityTypeMappings.Select(e => e.EntityType.Name).Contains(typeof(T).Name));

        var entityTypeMapping = entitySetMappings.EntityTypeMappings[0];

        foreach (var navigationProperty in entityTypeMapping.EntityType.NavigationProperties)
        {
            PropertyInfo propertyInfo = typeof(T).GetProperty(navigationProperty.Name);
            if (propertyInfo == null)
                throw new InvalidOperationException("propertyInfo == null");
            if (typeof(System.Collections.IEnumerable).IsAssignableFrom(propertyInfo.PropertyType))
                continue;

            queryable = queryable.Include(navigationProperty.Name);
        }

        return queryable;
    }
Bubi
quelle
0

Als ich weiter an Lunyx 'Antwort arbeitete, fügte ich eine Überladung hinzu, damit sie mit einer Sammlung von Zeichenfolgen funktioniert (wobei eine Zeichenfolge ein Eigenschaftsname wie "propertyA" oder "propertyA.propertyOfAA" ist):

  public static IQueryable<T> LoadRelated<T>(this IQueryable<T> originalQuery, ICollection<string> includes)
    {
        if (includes == null || !includes.Any()) return originalQuery;

        Func<IQueryable<T>, IQueryable<T>> includeFunc = f => f;
        foreach (var include in includes)
        {
            IQueryable<T> ChainedFunc(IQueryable<T> f) => f.Include(include);
            includeFunc = Compose(includeFunc, ChainedFunc);
        }

        return includeFunc(originalQuery);
    }
EyeSirvived
quelle