Laden...

automatisch aus Properties (auch nested) eine Expression erstellen

Erstellt von serial vor 10 Jahren Letzter Beitrag vor 10 Jahren 2.153 Views
S
serial Themenstarter:in
902 Beiträge seit 2007
vor 10 Jahren
automatisch aus Properties (auch nested) eine Expression erstellen

Hallo,

ich möchte, zur vereinfachung von Linq2SAQL-Includes, eine Methode schreiben, welche alle Properties einer Entity als Expression zurückgibt. Dabei sollen auch auf Navigationseingeschaften zugegriffen werden (für Eager-Loading).

Als Beispiel soll folgender Code dienen (nicht sinnvoll, ich weiss)


public class Person
    {
        public Person()
        {
            Adresses= new List<Adress>();
        }

        public string Name { get; set; }
        public int Age { get; set; }
        public IList<Adress> Adresses { get; set; }
        public IList<string> Names { get; set; }
        public City City { get; set; }
    }

    public class Adress
    {
        public string Street { get; set; }
    }

    public class City
    {
        public string Name { get; set; }
        public IList<string> Plz { get; set; }
    }

Als Ergebnis würde ich mir dann eben Wünschen:

entity => entity.Adresses;
entity => entity.City;
entity => entity.City.Plz;

folgenden Code habe ich:


public class PropertyExtractor
    {
        public IEnumerable<Expression> GetNavigationProperties<T>(T entity, int level)
        {
            var entityType = entity.GetType();
            var result = GetNavigationPropertiesInternal(entityType, level, 0);
            return result;
        }

        public IEnumerable<Expression> GetNavigationPropertiesInternal(Type entityType, int maxDepth, int currentDepth)
        {
            var result = new List<Expression>();
            foreach (var propertyInfo in entityType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                if (propertyInfo.PropertyType.IsPrimitive || propertyInfo.PropertyType == typeof(string))
                    continue;

                //here we have to concat the parent-accessor
                var parameter = Expression.Parameter(entityType, "entity");
                var property = Expression.Property(parameter, propertyInfo);
                var funcType = typeof(Func<,>).MakeGenericType(entityType, propertyInfo.PropertyType);
                var lambda = Expression.Lambda(funcType, property, parameter);
                if (currentDepth <= maxDepth)
                {
                    Type type = ListArgumentOrSelf(propertyInfo.PropertyType);
                    foreach (var info in GetNavigationPropertiesInternal(type, maxDepth, currentDepth++))
                        result.Add(info);
                }
                result.Add(lambda);

            }
            return result;
        }

        public Type ListArgumentOrSelf(Type type)
        {
            if (!type.IsGenericType)
                return type;
            if (type.GetGenericTypeDefinition() != typeof(IEnumerable<>) && !typeof(IEnumerable).IsAssignableFrom(type))
                throw new Exception("Only IEnumerable<T> are allowed");
            return type.GetGenericArguments()[0];
        }

    }

allerdings komme ich da nur auf die erste Ebene, nciht auf die 2te:
entity => entity.City.Plz.

HAt jemand eine Idee für mich?

Danke =)

mfg
Serial

F
10.010 Beiträge seit 2004
vor 10 Jahren

Und was soll das werden?

Manchmal ist ein anderer als der eingeschlagene weg zielführender.

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo serial,

es schaut so aus, als wäre deine Implementierung rekursiv. Du hast bestimmt im Debugger geschaut, warum der rekursive Abstieg nicht erfolgt. Insofern müsstest du dir Frage eigentlich besser beantworten können als wir. Zumindest müsstest du weitere Anhaltspunkte liefern können.

Die Chance - wie FZelle es vorgeschlagen hat - zu schreiben, was du eigentlich erreichen willst, damit vielleicht jemand einen anderen Vorschlag machen kann, wie dieses Ziel einfacher oder besser zu erreichen ist, solltest du dir nicht entgehen lassen.

herbivore

S
serial Themenstarter:in
902 Beiträge seit 2007
vor 10 Jahren

HI,

danke schonmal. Also der rekursive aufruf erfolgt, allerdings immer auf das übergebene kind element. (logischerweise).

Was will ich erreichen?

Ich möchte eine MEthode anbieten, welche es erlaubt für eine Linq2Sql-Abfrage alle Includes implizit per Ebenen anzugeben. Also das alle Properties bis zu einer gewünschten Tiefe gleich geladen, und nicht nachgeladen werden. Ich hoffe das habe ich verständlich ausgedrückt.

mfg
Serial

16.835 Beiträge seit 2008
vor 10 Jahren

So ein Vorgehen ist nicht mal eben mit einem 3-Zeiler abzudecken.
Spätestens wenn Du sich selbst navgierende Entities hast, oder Bidirektionale-Parent-Child-Properties kommst Du ohne Prüfung in eine Endlosschleife.

Reden wir wirklich von Linq2SQL oder vom Entity Framework?
Zweiteres bietet mittlerweile Include via Lambda an. Ausgelagert in einen Repository-Pattern hat man die richtige Stelle für das Ablegen der Includes gefunden.
Bei Linq2SQL gibts immerhin noch LoadWith, was man ebenfalls in das Repository auslagern kann.

S
serial Themenstarter:in
902 Beiträge seit 2007
vor 10 Jahren

HI,

@Abt: ja sorry ich spreche vom EF, meine Gedanken waren in einem anderen Kontext.

Das es kein 3 Zeiler wird ist mir klar, allerdings kenne ich mich zu wenig mit Expressions aus, um das ohne einen Anstoß hinzubekommen. Und genau das Include via Lambda will ich ja nutzen, dafür möchte ich mir ja für jede property welche es nachzuladen gilt, automatisch ein Lambda erzeugen um dies dann EF zu übergeben.

mfg
serial

16.835 Beiträge seit 2008
vor 10 Jahren

Ehrlich gesagt: das so dynamisch über Reflection im Repository abzudecken hat für mich keinen Sinn bzw gehört das es einfach nicht hin.

Bei Eager Loading weiß der Entwickler nie so richtig, was _wirklich _geladen wird. Das resultiert dann in unperformante und fehleranfällig Queries.
Bei Explizit Loading hat er die freie Entscheidung und bei Lazy Loading sinkt wenigstens die Gefahr unnötig alles zu laden und kann mit Hilfe der Materialisierung wenigstens ein wenig gesteuert werden.

Irgendwo stand mal, dass Include einen Query pro NavigationProperty um den Faktor 5-10 von der Dauer her verlängert.
Bei einer kurzen Suche nach "eager loading performance" kam ich auf

Zitat von: Lazy vs eager loading performance on Entity Framework
Eager loading: 106ms

Lazy loading: 11ms + 5ms + 5ms

Lazy loading wins, end of story.

Eager Loading hat also meiner Meinung nach keine Daseinsberechtigung in produktiven Anwendungen.
Willst Du das wirklich alles aufs Spiel setzen, oder war Dir das Ausmaß nicht klar?

S
serial Themenstarter:in
902 Beiträge seit 2007
vor 10 Jahren

Hi,

also das Ausmaß war mir nciht klar. Aber der Hintergrund:

Ich arbeite in einem Projekt, wo für Logeinträge die Entities mit ihren Werten geloggt werden (im Falle eines Fehlers). Nun führt dies oft zu Problemen mit Lazyloading, sodass sich die Entwickler hier entschlossen haben, vor dem Loggen einfach die Entity nochmal zu laden, mit allen Informationen die für das Logging gebraucht werden. Dies führte oft dazu, dass neue Entwickler einfach vergessen haben die nötigen Includes anzugeben etc. Nun war mein erster naiver Gedanke, dies zu automatisieren. Das es kein best practise ist, ist mir klar 😕

mfg
serial