Plusieurs groupes joignent lambda à l'arbre d'expression

c# expression-trees lambda

Question

Dans notre base de données, nous avons un certain nombre de tables qui ont des tables de traduction correspondantes, avec des ID de langue et de région (mappés sur d'autres tables), la langue 1 étant l'anglais et la région par défaut de la langue 1 étant le Royaume-Uni. Toutes les tables comportant une table de traduction ont les colonnes par défaut suivantes (bien qu'aucune interface n'ait été définie sur les classes du cadre d'entité):

<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

La dénomination est cohérente dans toute la base de données, nous pouvons donc ajouter des interfaces si nécessaire, mais pour l'instant, j'essaie de le faire sans épargner l'effort.

La logique permettant de déterminer le titre et la description de la traduction à renvoyer est la suivante: 1) S'il existe une correspondance exacte pour la langue et la région, renvoyez-la 2) S'il existe une correspondance pour la langue, mais pas pour la région, renvoyez la valeur par défaut. "pour cette langue (où RegionID est nul et qu'il y en aura toujours une pour chaque langue) 3) S'il n'y a pas de correspondance pour la langue, il suffit de renvoyer la valeur par défaut du système (LanguageID = 1, RegionID IS NULL).

Je sais que cela peut paraître étrange et que tout le monde a de meilleures façons de le faire, mais c'est le mandat avec lequel je dois travailler. Il s’agit donc de la fonction de jointure de groupe lambda que j’ai créée et qui utilise une entité de la base de données appelée "OrgGroup":

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;
}

Ce qui semble fonctionner comme prévu, et j'essaye maintenant de convertir cela en une fonction qui acceptera les deux types de table et utilisera des arbres d'expression pour créer, exécuter et renvoyer la requête équivalente. J'ai aussi loin que:

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
        }
    );

Le problème est que groupjoin s'attend à renvoyer un IEnumerable de TTrans, que je ne semble pas pouvoir lier, et je ne peux pas le remplacer par une jointure standard car je ne pourrai pas utiliser la fusion dans le projection car aucun résultat ne sera retourné.

Je suis sûr que je fais quelque chose de très stupide, alors quelqu'un peut-il m'aider à faire travailler les membres de mon groupe s'il vous plaît?

Réponse populaire

Réponse n ° 2 ... cette fois avec une réponse plus concrète: P

Le problème semble être que les types de lambdas et autres que vous passez à la méthode GroupJoin sont incorrects.

Plus précisément:

// 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);

... Bien que certains des autres semblent aussi un peu douteux, mais cela pourrait juste être moi.

L'expression de sélecteur attendue par GroupJoin est de type Expression<Func<TEntity, IEnumerable<TTrans>, TransJoin<TEntity, TTrans>>> . Il va passer dans une seule TEntity et un groupe de TTrans (comme IEnumerable<TTrans> ) même s'il n'y a qu'une seule instance dans ce groupe. Votre arborescence d'expression doit gérer correctement ce IEnumerable<TTrans> , ce qu'elle ne fait actuellement pas.

Etes-vous sûr de vouloir un GroupJoin et pas un Join ici?

J'ai écrit du code dans LINQPad pour tester le concept. C'est fini à > PasteBin < si vous voulez vous renseigner .


Incidemment, l'utilisation de l'extension Dump de LINQPad sur une expression vous donnera une explication complète de la façon dont l'expression est construite. Affectez un lambda à une variable du type Expression<Func<....>> approprié, puis appelez Dump pour voir comment il est construit. Aide à découvrir les usages et à montrer ce que vous devez faire pour le construire.



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow