【发布时间】:2014-09-10 17:53:48
【问题描述】:
在我们的数据库中,我们有许多表,它们具有相应的翻译表,语言和地区 ID(映射到其他表),语言 1 是英语,语言 1 的默认地区是英国。所有具有转换表的表都具有以下默认列(尽管实体框架类上没有定义接口):
<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
整个数据库的命名是一致的,所以如果需要我们可以添加接口,但现在我一直在尝试这样做而不是为了节省工作量。
确定返回哪个翻译标题和描述的逻辑是: 1)如果语言和地区都完全匹配,则返回 2) 如果语言匹配,但地区不匹配,则返回该语言的“默认”(RegionID 为空,并且每种语言总是有一个) 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,我似乎无法绑定它,并且我无法将其更改为标准连接,因为我将无法使用在投影中合并,因为不会返回任何结果。
我确定我在做一些非常愚蠢的事情,所以有人可以帮我让我的小组加入工作吗?
【问题讨论】:
标签: c# lambda expression-trees