2015-04-02 74 views
1

我试图在我的实体框架模型中添加对多语言分类字符串的支持。这是我有:使用ExpressionVisitor修改表达式以实现自动翻译

实体:

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

包含属性的名称本地化的字符串在主实体和翻译实体中总是相同的(0在这种情况下为)。

使用这种模式,我可以做这样的事情:

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

的问题是我不喜欢写这种代码包含任何意译的实体每个查询,所以我正在考虑使用QueryInterceptorExpressionVisitor这将做一些魔术和允许我用这样的替换查询:

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 
    

但我不是那种熟悉表情的游客和树木,所以我在这里有点迷路。有人能让我走上正轨吗?

+0

https://msdn.microsoft.com/en-us/library/bb882521%28v=vs.90%29.aspx – 2015-04-02 12:02:56

+0

我m使用.NET 4.5,所以ExpressionVisitor已经在那里实现了,我需要覆盖它以满足我的需求。无论如何,我已经想出了一个解决方案,稍后会发布一个答案。 – Donatas 2015-04-03 07:14:32

回答

3

好吧,看起来我已经想出了一个工作解决方案。

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

+0

Upvoted用于自我解决;) – 2015-04-03 08:09:01