实体框架内容国际化 [英] Internationalization of content in Entity Framework
问题描述
我不断遇到i18n的要求,我的数据(不是我的UI)需要国际化。
I keep coming across an i18n requirement where my data (not my UI) needs to be internationalized.
public class FooEntity
{
public long Id { get; set; }
public string Code { get; set; } // Some values might not need i18n
public string Name { get; set } // but e.g. this needs internationalized
public string Description { get; set; } // and this too
}
我可以使用哪些方法?
我尝试过的一些事情: -
1)将资源密钥存储在db 中
public class FooEntity
{
...
public string NameKey { get; set; }
public string DescriptionKey { get; set; }
}
- 优点:无需复杂的查询获得翻译实体。 c系统全球化为您处理回退。
- 缺点:翻译不能轻易被管理员管理(必须部署资源每当我的
Foo
改变时)文件。 - Pros: No need for complicated queries to get a translated entity.
System.Globalization
handles fallbacks for you. - Cons: Translations can't easily be managed by an admin user (have to deploy resource files whenever my
Foo
s change).
2)使用一个 LocalizableString
实体类型
2) Use a LocalizableString
entity type
public class FooEntity
{
...
public int NameId { get; set; }
public virtual LocalizableString Name { get; set; }
public int NameId { get; set; }
public virtual LocalizableString Description { get; set; }
}
public class LocalizableString
{
public int Id { get; set; }
public ICollection<LocalizedString> LocalizedStrings { get; set; }
}
public class LocalizedString
{
public int Id { get; set; }
public int ParentId { get; set; }
public virtual LocalizableString Parent { get; set; }
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public string Value { get; set; }
}
public class Language
{
public int Id { get; set; }
public string Name { get; set; }
public string CultureCode { get; set; }
}
- 优点:所有本地化的字符串在同一个表。验证可以按字符串执行。
- 缺点:查询可怕。必须包含LocalizedStrings表一次为父实体上的每个本地化字符串。后援很难,涉及广泛的加入。没有找到一种方法来避免N + 1,当检索例如使用一个包含所有本地化属性的所有不变属性和子实体的父实体,其中包含一个表的数据。
public class FooEntity
{
...
public ICollection<FooTranslation> Translations { get; set; }
}
public class FooTranslation
{
public long Id { get; set; }
public int ParentId { get; set; }
public virtual FooEntity Parent { get; set; }
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public string Name { get; set }
public string Description { get; set; }
}
public class Language
{
public int Id { get; set; }
public string Name { get; set; }
public string CultureCode { get; set; }
}
- 优点:太难了!)将实体完整翻译成内存。
- 缺点:双重实体数量。不能处理实体的部分翻译 - 特别是在名称来自
es
但说明来自es-AR
。 - Pros: Not as hard (but still too hard!) to get a full translation of an entity into memory.
- Cons: Double the number of entities. Can't handle partial translations of an entity - especially the case where, say, Name is coming from
es
but Description is coming fromes-AR
. -
用户可以在运行时编辑实体,语言和翻译
Users can edit entities, languages, and translations at runtime
我有三个解决方案要求
根据System.Globalization提供缺少字符串来自后备的部分翻译
Users can supply partial translations with missing strings coming from a fallback as per System.Globalization
实体可以被带入内存,而不会进入例如N + 1个问题
Entities can be brought into memory without running into e.g. N+1 issues
推荐答案
为什么不把最好的两个世界?
有一个CustomResourceManager可以处理资源的加载和挑选正确的文化,并使用一个CustomResourceReader,它使用你喜欢的任何后备存储。基本的实现可能看起来像这样,依靠Resourceky的约定是Typename_PropertyName_PropertyValue。如果由于某种原因,后台存储(csv / excel / mssql / table结构)的结构需要更改,则只需更改ResourceReader的实现。
Why don't you take the best of both worlds? Have a CustomResourceManager that handles the loading of resources and picking the right culture and use a CustomResourceReader that uses whatever backing store you like. A basic implementation could look like this, relying on convention of the Resourceky being Typename_PropertyName_PropertyValue. If for some reason the structure of the backingstore(csv/excel/mssql/table structure) need to change you only have the change the implementation of the ResourceReader.
额外的奖金我也得到了真正/透明的代理。
As an added bonus I also got the real/transparent proxy going.
ResourceManager
class MyRM:ResourceManager
{
readonly Dictionary<CultureInfo, ResourceSet> sets = new Dictionary<CultureInfo, ResourceSet>();
public void UnCache(CultureInfo ci)
{
sets.Remove(ci):
}
protected override ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
{
ResourceSet set;
if (!sets.TryGetValue(culture, out set))
{
IResourceReader rdr = new MyRR(culture);
set = new ResourceSet(rdr);
sets.Add(culture,set);
}
return set;
}
// sets Localized values on properties
public T GetEntity<T>(T obj)
{
var entityType = typeof(T);
foreach (var prop in entityType.GetProperties(
BindingFlags.Instance
| BindingFlags.Public)
.Where(p => p.PropertyType == typeof(string)
&& p.CanWrite
&& p.CanRead))
{
// FooEntity_Name_(content of Name field)
var key = String.Format("{0}_{1}_{2}",
entityType.Name,
prop.Name,
prop.GetValue(obj,null));
var val = GetString(key);
// only set if a value was found
if (!String.IsNullOrEmpty(val))
{
prop.SetValue(obj, val, null);
}
}
return obj;
}
}
ResourceReader
class MyRR:IResourceReader
{
private readonly Dictionary<string, string> _dict;
public MyRR(CultureInfo ci)
{
_dict = new Dictionary<string, string>();
// get from some storage (here a hardcoded Dictionary)
// You have to be able to deliver a IDictionaryEnumerator
switch (ci.Name)
{
case "nl-NL":
_dict.Add("FooEntity_Name_Dutch", "nederlands");
_dict.Add("FooEntity_Name_German", "duits");
break;
case "en-US":
_dict.Add("FooEntity_Name_Dutch", "The Netherlands");
break;
case "en":
_dict.Add("FooEntity_Name_Dutch", "undutchables");
_dict.Add("FooEntity_Name_German", "german");
break;
case "": // invariant
_dict.Add("FooEntity_Name_Dutch", "dutch");
_dict.Add("FooEntity_Name_German", "german?");
break;
default:
Trace.WriteLine(ci.Name+" has no resources");
break;
}
}
public System.Collections.IDictionaryEnumerator GetEnumerator()
{
return _dict.GetEnumerator();
}
// left out not implemented interface members
}
用法
var rm = new MyRM();
var f = new FooEntity();
f.Name = "Dutch";
var fl = rm.GetEntity(f);
Console.WriteLine(f.Name);
Thread.CurrentThread.CurrentUICulture = new CultureInfo("nl-NL");
f.Name = "Dutch";
var dl = rm.GetEntity(f);
Console.WriteLine(f.Name);
RealProxy
public class Localizer<T>: RealProxy
{
MyRM rm = new MyRM();
private T obj;
public Localizer(T o)
: base(typeof(T))
{
obj = o;
}
public override IMessage Invoke(IMessage msg)
{
var meth = msg.Properties["__MethodName"].ToString();
var bf = BindingFlags.Public | BindingFlags.Instance ;
if (meth.StartsWith("set_"))
{
meth = meth.Substring(4);
bf |= BindingFlags.SetProperty;
}
if (meth.StartsWith("get_"))
{
// get the value...
meth = meth.Substring(4);
var key = String.Format("{0}_{1}_{2}",
typeof (T).Name,
meth,
typeof (T).GetProperty(meth, BindingFlags.Public | BindingFlags.Instance
|BindingFlags.GetProperty).
GetValue(obj, null));
// but use it for a localized lookup (rm is the ResourceManager)
var val = rm.GetString(key);
// return the localized value
return new ReturnMessage(val, null, 0, null, null);
}
var args = new object[0];
if (msg.Properties["__Args"] != null)
{
args = (object[]) msg.Properties["__Args"];
}
var res = typeof (T).InvokeMember(meth,
bf
, null, obj, args);
return new ReturnMessage(res, null, 0, null, null);
}
}
实体/透明代理使用
var f = new FooEntity();
f.Name = "Dutch";
var l = new Localizer<FooEntity>(f);
var fp = (FooEntity) l.GetTransparentProxy();
fp.Name = "Dutch"; // notice you can use the proxy as is,
// it updates the actual FooEntity
var localizedValue = fp.Name;
这篇关于实体框架内容国际化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!