Mehrere Gruppen verbinden Lambda mit dem Ausdrucksbaum

c# expression-trees lambda

Frage

In unserer Datenbank haben wir eine Anzahl von Tabellen, die entsprechende Übersetzungstabellen mit Sprach- und Regions-IDs (zugeordnet zu anderen Tabellen) haben, wobei Sprache 1 Englisch ist und die Standardregion der Sprache 1 Großbritannien ist. Alle Tabellen, die eine Übersetzungstabelle haben, haben die folgenden Standardspalten (obwohl für die Entitätsrahmenklassen keine Schnittstelle definiert wurde):

<EntityTableName>
EntityTableNameID INT PK
Reference NVARCHAR NULL
[Any other columns]

<EntityTableNameTranslation>
EntityTableNameID INT NOT NULL
LanguageID INT NOT NULL
RegionID INT NULL
Title NVARCHAR NOT NULL
Description NVARCHAR NULL

Die Benennung ist in der gesamten Datenbank konsistent, so dass wir bei Bedarf Schnittstellen hinzufügen konnten, aber im Moment habe ich versucht, dies zu tun, ohne den Aufwand zu sparen.

Die Logik zur Bestimmung des zurückzugebenden Übersetzungstitels und der Beschreibung lautet: 1) Wenn sowohl die Sprache als auch die Region exakt übereinstimmen, gebe sie zurück. 2) Wenn es eine Übereinstimmung für die Sprache, aber nicht für die Region gibt, gebe "default" zurück "Für diese Sprache (wo die RegionID null ist, und es wird immer eine für jede Sprache geben) 3) Wenn es keine Übereinstimmung für die Sprache gibt, gebe einfach die Systemvorgabe zurück (LanguageID = 1, RegionID IS NULL).

Ich weiß, dass das alles komisch klingen mag und jeder hat bessere Möglichkeiten, aber das ist die Aufgabe, mit der ich arbeiten muss. Das ist also die Join-Gruppen-Join-Funktion, die ich erstellt habe und die eine Entity in der Datenbank namens "OrgGroup" verwendet:

public static IEnumerable<TransViewModel> GetUserAreaOrgGroups(TransTestEntities context, int companyID, int languageID, int? regionID)
{
    var transFull = context.OrgGroupTranslations.Where(tr => tr.LanguageID == languageID && tr.RegionID == regionID);
    var transLang = context.OrgGroupTranslations.Where(tr => tr.LanguageID == languageID && !tr.RegionID.HasValue);
    var transDefault = context.OrgGroupTranslations.Where(tr => tr.LanguageID == 1 && !tr.RegionID.HasValue);

    var results = context.OrgGroups.Where(en => en.CompanyID == companyID)
            .GroupJoin(transFull, en => en.OrgGroupID, tr => tr.OrgGroupID,
                        (en, tr) => new TransJoin<OrgGroup, OrgGroupTranslation> { Entity = en, TransFull = tr.DefaultIfEmpty().FirstOrDefault(), TransLang = null, TransDefault = null})
            .GroupJoin(transLang, en => en.Entity.OrgGroupID, tr => tr.OrgGroupID,
                        (en, tr) => new TransJoin<OrgGroup, OrgGroupTranslation> { Entity = en.Entity, TransFull = en.TransFull, TransLang = tr.DefaultIfEmpty().FirstOrDefault(), TransDefault = null })
            .GroupJoin(transDefault, en => en.Entity.OrgGroupID, tr => tr.OrgGroupID,
                        (en, tr) => new TransJoin<OrgGroup, OrgGroupTranslation> { Entity = en.Entity, TransFull = en.TransFull, TransLang = en.TransLang, TransDefault = tr.DefaultIfEmpty().FirstOrDefault() })
            .Select(vm => new TransViewModel
                {
                    EntityID = vm.Entity.OrgGroupID,
                    Title = (vm.TransFull ?? vm.TransLang ?? vm.TransDefault).Title,
                    Description = (vm.TransFull ?? vm.TransLang ?? vm.TransDefault).Description
                });
    return results;
}

Das scheint wie erwartet zu funktionieren, und jetzt versuche ich, dies in eine Funktion umzuwandeln, die die zwei Tabellentypen akzeptiert und Ausdrucksbäume zum Erstellen, Ausführen und Zurückgeben der äquivalenten Abfrage verwendet. Ich bin soweit:

public static IEnumerable<TransViewModel> GetUserAreaTranslations<TEntity, TTrans>(TransTestEntities context, int companyID, int languageID, int? regionID)
{
    // Get types
    Type entityType = typeof(TEntity);
    Type transType = typeof(TTrans);

    string entityName = entityType.Name;
    string transName = transType.Name;

    // Parameters
    var entityParam = Expression.Parameter(entityType, "en");
    var transParam = Expression.Parameter(transType, "tr");
    var combinedParam = new ParameterExpression[] { entityParam, transParam };

    // Properties
    var CompanyIDProp = Expression.Property(entityParam, "CompanyID");
    var entityIDProp = Expression.Property(entityParam, entityName + "ID");
    var transIDProp = Expression.Property(transParam, entityName + "ID");
    var transLanProp = Expression.Property(transParam, "LanguageID");
    var transRegProp = Expression.Property(transParam, "RegionID");
    var transTitleProp = Expression.Property(transParam, "Title");
    var transDescProp = Expression.Property(transParam, "Description");

    // Tables
    //TODO: Better way of finding pluralised table names
    var entityTable = Expression.PropertyOrField(Expression.Constant(context), entityName + "s");
    var transTable = Expression.PropertyOrField(Expression.Constant(context), transName + "s");

    // Build translation subqueries
    //e.g. context.OrgGroupTranslations.Where(tr => tr.LanguageID == languageID && tr.RegionID == regionID);

    MethodCallExpression fullTranWhereLambda = Expression.Call(typeof(Queryable),
                                    "Where",
                                    new Type[] { transType },
                                    new Expression[]
                                    {
                                        transTable,
                                        Expression.Quote
                                            (
                                                Expression.Lambda
                                                    (
                                                        Expression.AndAlso
                                                            (
                                                                Expression.Equal(transLanProp, Expression.Constant(languageID)),
                                                                Expression.Equal(transRegProp, Expression.Convert(Expression.Constant(languageID), transRegProp.Type))
                                                            ), transParam
                                                    )
                                            )
                                    });

    MethodCallExpression lanTranWhereLambda = Expression.Call(typeof(Queryable),
                                    "Where",
                                    new Type[] { transType },
                                    new Expression[]
                                    {
                                        transTable,
                                        Expression.Quote
                                            (
                                                Expression.Lambda
                                                    (
                                                        Expression.AndAlso
                                                            (
                                                                Expression.Equal(transLanProp, Expression.Constant(languageID)),
                                                                Expression.IsFalse(MemberExpression.Property(transRegProp, "HasValue"))
                                                            ), transParam
                                                    )
                                            )
                                    });

    MethodCallExpression defaultTranWhereLambda = Expression.Call(typeof(Queryable),
                                    "Where",
                                    new Type[] { transType },
                                    new Expression[]
                                    {
                                        transTable,
                                        Expression.Quote
                                            (
                                                Expression.Lambda
                                                    (
                                                        Expression.AndAlso
                                                            (
                                                                Expression.Equal(transLanProp, Expression.Constant(1)),
                                                                Expression.IsFalse(MemberExpression.Property(transRegProp, "HasValue"))
                                                            ), transParam
                                                    )
                                            )
                                    });

    MethodCallExpression entityWhereLambda = Expression.Call(typeof(Queryable),
                                                "Where",
                                                new Type[] { entityType },
                                                new Expression[]
                                                {
                                                    entityTable,
                                                    Expression.Quote(
                                                        Expression.Lambda
                                                        (
                                                            Expression.Equal(CompanyIDProp, Expression.Convert(Expression.Constant(companyID), CompanyIDProp.Type))
                                                            , entityParam
                                                        )
                                                    )
                                                });

    // Create the "left join" call:
    // tr.DefaultIfEmpty().FirstOrDefault()
    var joinType = typeof(TransJoin<TEntity, TTrans>);
    var joinParam = Expression.Parameter(joinType, "tr");
    var leftJoinMethods =
        Expression.Call(
            typeof(Enumerable),
            "FirstOrDefault",
            new Type[] { transType },
            Expression.Call(
                typeof(Enumerable),
                "DefaultIfEmpty",
                new Type[] { transType },
                Expression.Parameter(typeof(IEnumerable<TTrans>), "tr"))
        );

    // Create the return bindings
    var emptyTrans = Expression.Constant(null, typeof(TTrans));
    //var emptyTrans = Expression.Constant(null);
    var fullBindings = new List<MemberBinding>();
    fullBindings.Add(Expression.Bind(joinType.GetProperty("Entity"), entityParam));
    fullBindings.Add(Expression.Bind(joinType.GetProperty("TransFull"), leftJoinMethods));
    fullBindings.Add(Expression.Bind(joinType.GetProperty("TransLang"), emptyTrans));
    fullBindings.Add(Expression.Bind(joinType.GetProperty("TransDefault"), emptyTrans));
    // Create an object initialiser which also sets the properties
    Expression fullInitialiser = Expression.MemberInit(Expression.New(joinType), fullBindings);
    // Create the lambda expression, which represents the complete delegate
    Expression<Func<TEntity, TTrans, TransJoin<TEntity, TTrans>>> fullResultSelector =
        Expression.Lambda <Func<TEntity, TTrans, TransJoin<TEntity, TTrans>>>(fullInitialiser, combinedParam);

    // Create first group join
    var fullJoin = Expression.Call(
        typeof(Queryable),
        "GroupJoin",
        new Type[]
        {
            typeof (TEntity),       // TOuter,
            typeof (TTrans),        // TInner,
            typeof (int),           // TKey,
            typeof (TransJoin<TEntity, TTrans>) // TResult
        },
        new Expression[]
        {
            entityWhereLambda,
            fullTranWhereLambda,
            Expression.Lambda<Func<TEntity, int>>(entityIDProp, entityParam),
            Expression.Lambda<Func<TTrans, int>>(transIDProp, transParam),
            fullResultSelector
        }
    );

Das Problem ist, dass groupjoin erwartet, dass IEnumerable von TTrans zurückgegeben wird, was ich nicht zu binden vermag, und ich kann es nicht zu einem Standardjoin ändern, weil ich nicht in der Lage sein werde, das Coalesce zu verwenden Projektion als kein Ergebnis wird zurückgegeben.

Ich bin mir sicher, dass ich etwas sehr dummes mache, also kann mir jemand helfen, meine Gruppe zum Arbeiten zu bringen?

Beliebte Antwort

Antwort # 2 ... diesmal mit mehr tatsächlicher Antwort: P

Das Problem scheint zu sein, dass die Typen der Lambdas usw., die Sie an die GroupJoin Methode übergeben, falsch sind.

Speziell:

// Create the lambda expression, which represents the complete delegate
Expression<Func<TEntity, TTrans, TransJoin<TEntity, TTrans>>> fullResultSelector =
    Expression.Lambda<Func<TEntity, TTrans, TransJoin<TEntity, TTrans>>>(fullInitialiser, combinedParam);

... obwohl einige der anderen auch ein bisschen zweifelhaft aussehen, aber das könnte nur ich sein.

Der von GroupJoin erwartete GroupJoin ist vom Typ Expression<Func<TEntity, IEnumerable<TTrans>, TransJoin<TEntity, TTrans>>> . Es wird in einer einzelnen TEntity und einer Gruppe von TTrans (als IEnumerable<TTrans> ) übergeben, auch wenn es nur eine Instanz in dieser Gruppe gibt. Ihr Ausdrucksbaum muss mit IEnumerable<TTrans> korrekt IEnumerable<TTrans> , was er zur Zeit nicht tut.

Sind Sie sicher, dass Sie ein GroupJoin und kein Join hier haben wollten?

Ich habe einen Code in LINQPad geschrieben, um das Konzept zu testen. Es ist vorbei an > PasteBin <, wenn Sie es sich ansehen wollen.


Wenn Sie die Dump Erweiterung von LINQPad für einen Ausdruck verwenden, erhalten Sie einen vollständigen Überblick darüber, wie der Ausdruck konstruiert wird. Ordnen Sie einer Variablen eines passenden Expression<Func<....>> ein Lambda zu und rufen Sie dann Dump auf, um zu sehen, wie es aufgebaut ist. Hilft dabei, die Verwendungen herauszufinden und zu zeigen, was Sie tun müssen, um es zu bauen.



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow