c# - 多组将 lambda 连接到表达式树

标签 c# lambda expression-trees

在我们的数据库中,我们有许多具有相应翻译表的表,具有语言和区域 ID(映射到其他表),其中语言 1 为英语,语言 1 的默认区域为英国。所有具有转换表的表都有以下默认列(尽管在 Entity Framework 类上没有定义接口(interface)):

<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

命名在整个数据库中是一致的,因此我们可以根据需要添加接口(interface),但现在我一直在尝试这样做而不省力。

确定返回哪个翻译标题和描述的逻辑是: 1)如果语言和地区都完全匹配,则返回 2)如果语言匹配,但区域不匹配,则返回该语言的“默认值”(这是 RegionID 为 null 的地方,每种语言总会有一个) 3)如果没有匹配到语言,就返回系统默认值(LanguageID = 1, RegionID IS NULL)。

我知道这听起来很奇怪,每个人都有更好的方法,但这是我必须处理的简报。所以这是我创建的 lambda 组连接函数,它使用数据库中名为“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;
}

这似乎按预期工作,现在我正在尝试将其转换为一个函数,该函数将接受两种表类型并使用表达式树来创建、执行和返回等效查询。我已经做到了:

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

问题是 groupjoin 期望返回 TTrans 的 IEnumerable,我似乎无法绑定(bind)它,而且我无法将其更改为标准连接,因为我将无法使用在投影中合并,因为不会返回任何结果。

我确定我正在做一些非常愚蠢的事情,所以有人可以帮助我让我的小组加入工作吗?

最佳答案

您要查找的表达式节点是MemberInitExpression ,这是编译包含 new { } 语句的 lambda 的结果。

假设我们有一个像这样的简单键值对类:

public class KV
{
    public int Key;
    public string Value;
}

我可以为此构建一个 new 表达式来加载一些常量,如下所示:

Type tKV = typeof(KV);
MemberInfo miKey = tKV.GetMember("Key")[0];
MemberInfo miValue = tKV.GetMember("Value")[0];

Expression meminit = 
    Expression.MemberInit(
        Expression.New(tKV),
        Expression.Bind(miKey, Expression.Constant(1)),
        Expression.Bind(miValue, Expression.Constant("Some Value"))
    );

或者对于更完整的版本,构造一个完全初始化变量的 lambda 表达式:

public Expression<Func<int, string, KV>> InitKV()
{
    var pK = Expression.Parameter(typeof(int), "k");
    var pV = Expression.Parameter(typeof(string), "v");

    Type tKV = typeof(KV);
    MemberInfo miKey = tKV.GetMember("Key")[0];
    MemberInfo miValue = tKV.GetMember("Value")[0];

    Expression meminit = 
        Expression.MemberInit(
            Expression.New(tKV),
            Expression.Bind(miKey, pK),
            Expression.Bind(miValue, pV)
        );

    return (Expression<Func<int, string, KV>>)Expression.Lambda(meminit, pK, pV);       
}

在你的情况下,那里会有更多的 Bind 表达式。

关于c# - 多组将 lambda 连接到表达式树,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24844553/

相关文章:

c# - 有没有办法使用自定义 OrderBy 或 Min 而无需实现 IComparer<T> ?

Python/Tkinter - 运行具有多个参数的函数的按钮不起作用

c# - AWS Lambda C# 上传对象到 S3

c# - 使用 EF Core 编写动态 LINQ 查询以进行排序和投影

c# - 从 linq 表达式检索信息时是否使用反射?

c# - 无法掌握 Freeze/Inject/Register 之间的区别

c# - 使用 Json.Net 反序列化 Json 对象

c# - 从其事件处理程序访问控制

c++ - 在三元运算符中初始化捕获 lambda

c++ - 递归创建树