这是这样的想法:
我们的应用程序有一个产品表,其中包含可翻译的名称。因为支持的语言数量可以扩展,所以每个产品都有一个 ProductTranslation 集合,其中包含一种文化(例如“en-US”)和每个可翻译属性。
域如下所示:
/// <summary>
/// Marks a class as translatable, i.e. there are properties that need to be different per language, such as a name or description.
/// </summary>
/// <typeparam name="TTranslation"></typeparam>
public interface ITranslatable<TTranslation> where TTranslation: ITranslation
{
/// <summary>
/// Gets or sets the translations
/// </summary>
TranslationCollection<TTranslation> Translations { get; set; }
}
/// <summary>
/// Marks this class as a translation of another class.
/// </summary>
public interface ITranslation
{
/// <summary>
/// Gets or sets the culture
/// </summary>
string Culture { get; set; }
}
public class Product : ITranslatable<ProductTranslation>
{
private TranslationCollection<ProductTranslation> _translations;
/// <summary>
/// Gets or sets the code.
/// </summary>
public virtual string Code { get; set; }
/// <summary>
/// Gets or sets the price.
/// </summary>
public virtual decimal? Price { get; set; }
public virtual TranslationCollection<ProductTranslation> Translations
{
get { return _translations ?? (_translations = new TranslationCollection<ProductTranslation>()); }
set { _translations = value; }
}
}
/// <summary>
/// Contains the translatable properties for <see cref="Product"/>
/// </summary>
public class ProductTranslation: ITranslation
{
/// <summary>
/// Gets or sets the culture of this translation
/// </summary>
public string Culture { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public virtual string Name { get; set; }
}
您可能已经注意到,我使用自定义集合类进行翻译。(TranslationCollection 而不是默认的 ICollection)
该类扩展了 Collection,但添加了一个实用属性“Current”,该属性返回与当前 UI 文化匹配的翻译:
/// <summary>
/// Contains specific methods to work with translations
/// </summary>
/// <typeparam name="TTranslation"></typeparam>
public class TranslationCollection<TTranslation>: Collection<TTranslation> where TTranslation: ITranslation
{
/// <summary>
/// Initializes an empty <see cref="TranslationCollection{TTranslation}"/>
/// </summary>
public TranslationCollection()
{
}
/// <summary>
/// Initializes a new <see cref="TranslationCollection{TTranslation}"/> with the given <paramref name="list"/> as its contents
/// </summary>
/// <param name="list"></param>
public TranslationCollection(IList<TTranslation> list) : base(list)
{
}
/// <summary>
/// Returns the translation that has the same culture as the current UI culture.
/// </summary>
public TTranslation Current
{
get
{
return this.SingleOrDefault(t => t.Culture == CultureInfo.CurrentUICulture.Name);
}
}
}
如您所见,这里几乎没有发生什么,但我喜欢为此创建一个自定义集合类的想法,因为稍后当我们想要制作一些用于显示和表单的自定义 HTML 组件时,它可能会派上用场。
现在的问题:
当我们查询 products 表时,按其名称进行搜索将如下所示:
var products = dbContext.Products.Where(p => p.Translations.Where(t => t.Culture == CultureInfo.CurrentUICulture).Any(t => t.Name.ToLower().Contains("abc")))
但是,鉴于将来会有很多可翻译的表格(这是一个相当大的应用程序),如果我们可以这样写会很有趣:
var products = dbContext.Products.Where(p => p.Translations.Current.Name.ToLower().Contains("abc"))
当然,'Current' 属性是未映射的,当您运行此代码时,Entity Framework 将引发异常。但是,是否可以使用 ExpressionVisitor (或其他任何东西)自动将“当前”调用转换为其他东西
我做了第一次尝试,但有点卡住:
public class CurrentTranslationVisitor: ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
if(node.Member.MemberType != MemberTypes.Property)
return base.VisitMember(node);
var propertyInfo = node.Member as PropertyInfo;
if (propertyInfo == null)
return base.VisitMember(node);
if (!typeof (ITranslation).IsAssignableFrom(propertyInfo.PropertyType))
return base.VisitMember(node);
if (!string.Equals(propertyInfo.Name, "Current"))
return base.VisitMember(node);
// at this point we can be confident that the property is [someTranslatableObject].Translations.Current
}
}
此时如何访问针对 Current 属性编写的代码?
例如,当表达式是
p => p.Translations.Current.Name.ToLower().Contains("abc")
我怎样才能访问
.Name.ToLower().Contains("abc")
建议和帮助将不胜感激!