我试图在实体框架模型中添加对多语言分类字符串的支持。这就是我所拥有的:
实体:
public partial class ServiceState : ITranslatableEntity<ServiceStateTranslation>
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ServiceStateTranslation> Translations { get; set; }
}ITranslatableEntity接口:
public interface ITranslatableEntity<T>
{
ICollection<T> Translations { get; set; }
}然后,包含翻译的实体:
public partial class ServiceStateTranslation
{
public int Id { get; set; }
[Index("IX_ClassificationTranslation", 1, IsUnique = true)]
public int MainEntityId { get; set; }
public ServiceState MainEntity { get; set; }
[Index("IX_ClassificationTranslation", 2, IsUnique = true)]
public string LanguageCode { get; set; }
public string Name { get; set; }
}包含本地化字符串的属性的名称在主实体和转换实体中总是相同的(本例中为Name)。
使用这样的模型,我可以这样做:
var result = query.Select(x => new
{
Name = x.Name,
StateName =
currentLanguageCode == DEFAULTLANGUAGECODE
? x.ServiceState.Name
: x.ServiceState.Translations.Where(i => i.LanguageCode == currentLanguageCode)
.Select(i => i.Name)
.FirstOrDefault() ?? x.ServiceState.Name
}).ToList();问题是,我不喜欢为每个包含可转换实体的查询编写这种代码,所以我正在考虑使用QueryInterceptor和一个ExpressionVisitor,这会产生一些神奇的效果,并允许我用这样的方法来替换这个查询:
var result = query.Select(x => new
{
Name = x.Name,
StateName = x.ServiceState.Name
}).ToLocalizedList(currentLanguageCode, DEFAULTLANGUAGECODE);我认为可以创建一个ExpressionVisitor,它将:
Select块内实现ITranslatableEntity<>接口的导航属性中的表达式x.ServiceState.Name更改为
x.ServiceState.Translations.Where(i => i.LanguageCode == currentLanguageCode) .Select(i => i.Name) .FirstOrDefault()?x.ServiceState.Name但我不太熟悉表情的访客和树木,所以我有点迷失在这里。谁能让我走上正轨吗?
发布于 2015-04-03 07:28:25
好吧,看来我想出了一个可行的解决方案。
public class ClassificationTranslationVisitor : ExpressionVisitor
{
private string langCode = "en";
private string defaultLangCode = "en";
private string memberName = null;
private Expression originalNode = null;
public ClassificationTranslationVisitor(string langCode, string defaultLanguageCode)
{
this.langCode = langCode;
this.defaultLangCode = defaultLanguageCode;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (langCode == defaultLangCode)
{
return base.VisitParameter(node);
}
if (!node.Type.GetCustomAttributes(typeof(TranslatableAttribute), false).Any() && originalNode == null)
{
return base.VisitParameter(node);
}
if (IsGenericInterface(node.Type, typeof(ITranslatableEntity<>)))
{
return AddTranslation(node);
}
return base.VisitParameter(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node == null || node.Member == null || node.Member.DeclaringType == null)
{
return base.VisitMember(node);
}
if (langCode == defaultLangCode)
{
return base.VisitMember(node);
}
if (!node.Member.GetCustomAttributes(typeof(TranslatableAttribute), false).Any() && originalNode == null)
{
return base.VisitMember(node);
}
if (IsGenericInterface(node.Member.DeclaringType, typeof(ITranslatableEntity<>)))
{
memberName = node.Member.Name;
originalNode = node;
return Visit(node.Expression);
}
if (IsGenericInterface(node.Type, typeof(ITranslatableEntity<>)))
{
return AddTranslation(node);
}
return base.VisitMember(node);
}
private Expression AddTranslation(Expression node)
{
var expression = Expression.Property(node, "Translations");
var resultWhere = CreateWhereExpression(expression);
var resultSelect = CreateSelectExpression(resultWhere);
var resultIsNull = Expression.Equal(resultSelect, Expression.Constant(null));
var testResult = Expression.Condition(resultIsNull, originalNode, resultSelect);
memberName = null;
originalNode = null;
return testResult;
}
private Expression CreateWhereExpression(Expression ex)
{
var type = ex.Type.GetGenericArguments().First();
var test = CreateExpression(t => t.LanguageCode == langCode, type);
if (test == null)
return null;
return Expression.Call(typeof(Enumerable), "Where", new[] { type }, ex, test);
}
private Expression CreateSelectExpression(Expression ex)
{
var type = ex.Type.GetGenericArguments().First();
ParameterExpression itemParam = Expression.Parameter(type, "lang");
Expression selector = Expression.Property(itemParam, memberName);
var columnLambda = Expression.Lambda(selector, itemParam);
var result = Expression.Call(typeof(Enumerable), "Select", new[] { type, typeof(string) }, ex, columnLambda);
var stringResult = Expression.Call(typeof(Enumerable), "FirstOrDefault", new[] { typeof(string) }, result);
return stringResult;
}
/// <summary>
/// Adapt a QueryConditional to the member we're currently visiting.
/// </summary>
/// <param name="condition">The condition to adapt</param>
/// <param name="type">The type of the current member (=Navigation property)</param>
/// <returns>The adapted QueryConditional</returns>
private LambdaExpression CreateExpression(Expression<Func<ITranslation, bool>> condition, Type type)
{
var lambda = (LambdaExpression)condition;
var conditionType = condition.GetType().GetGenericArguments().First().GetGenericArguments().First();
// Only continue when the condition is applicable to the Type of the member
if (conditionType == null)
return null;
if (!conditionType.IsAssignableFrom(type))
return null;
var newParams = new[] { Expression.Parameter(type, "bo") };
var paramMap = lambda.Parameters.Select((original, i) => new { original, replacement = newParams[i] }).ToDictionary(p => p.original, p => p.replacement);
var fixedBody = ParameterRebinder.ReplaceParameters(paramMap, lambda.Body);
lambda = Expression.Lambda(fixedBody, newParams);
return lambda;
}
private bool IsGenericInterface(Type type, Type interfaceType)
{
return type.GetInterfaces().Any(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == interfaceType);
}
}
public class ParameterRebinder : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, ParameterExpression> map;
public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{
this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
{
return new ParameterRebinder(map).Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression node)
{
ParameterExpression replacement;
if (map.TryGetValue(node, out replacement))
node = replacement;
return base.VisitParameter(node);
}
}我还添加了TranslatableAttribute,它是所有即将被转换的属性所必需的。
代码当然缺少一些检查,但是它已经在我的环境中工作了。我也没有检查替换的表达式是否在Select块中,但是看起来,对于TranslatableAttribute来说,这并不是必要的。
我使用了ParameterRebinder和这个答案ExpressionVisitor soft delete中的一些其他代码
https://stackoverflow.com/questions/29410428
复制相似问题