2012-06-18 28 views
16

Tôi tiếp tục yêu cầu i18n khi dữ liệu của tôi (không phải giao diện người dùng của tôi) cần được quốc tế hóa.Quốc tế hóa nội dung trong Entity Framework

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 
} 

Tôi có thể sử dụng một số phương pháp nào?

Một số điều tôi đã cố gắng: -

1) Cất giữ một phím nguồn trong db

public class FooEntity 
{ 
    ... 
    public string NameKey { get; set; } 
    public string DescriptionKey { get; set; } 
} 
  • Ưu điểm: Không cần phải cho các truy vấn phức tạp để có được một dịch thực thể. System.Globalization xử lý các khoản hoàn trả cho bạn.
  • Nhược điểm: Người dùng quản trị không thể dễ dàng quản lý bản dịch (phải triển khai tệp tài nguyên bất cứ khi nào thay đổi của tôi Foo s).

2) Sử dụng một loại thực thể LocalizableString

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; } 
} 
  • Ưu điểm: Tất cả các dây cục bộ trong cùng một bảng. Xác nhận có thể được thực hiện cho mỗi chuỗi.
  • Nhược điểm: Truy vấn rất đáng sợ. Phải bao gồm bảng LocalizedStrings một lần cho mỗi chuỗi có thể bản địa hóa trên thực thể cha. Sự sụp đổ là khó khăn và liên quan đến việc tham gia rộng rãi. Đã không tìm thấy cách tránh N + 1 khi truy xuất ví dụ: dữ liệu cho một bảng.

3) Sử dụng một công ty mẹ với tất cả các thuộc tính bất biến và các tổ chức con chứa tất cả các thuộc tính cục bộ

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; } 
} 
  • Ưu điểm: Không như cứng (nhưng vẫn còn quá cứng) để có được! bản dịch đầy đủ của một thực thể vào bộ nhớ.
  • Nhược điểm: Tăng gấp đôi số lượng thực thể. Không thể xử lý bản dịch từng phần của một thực thể - đặc biệt là trường hợp, như vậy, Tên là đến từ es nhưng Mô tả đến từ es-AR.

tôi có ba yêu cầu cho một giải pháp

  • Người dùng có thể chỉnh sửa các đối tượng, ngôn ngữ và bản dịch trong thời gian chạy

  • Người dùng có thể cung cấp dịch từng phần với chuỗi mất tích đến từ một dự phòng theo System.Globalization

  • Thực thể có thể được đưa vào bộ nhớ mà không cần chạy trong ví dụ: Các vấn đề về N + 1

+0

Vẫn chưa được trả lời, tôi cũng quan tâm. – polkduran

+0

Nó không rõ ràng những gì bạn sẽ xem xét một câu trả lời chấp nhận được. Nếu ai đó có một tùy chọn 4 nó có khả năng có ưu/khuyết quá. – explunit

+0

Câu hỏi đã được làm rõ. Tôi không mong đợi có một giải pháp hoàn hảo, nhưng hy vọng có một giải pháp tốt hơn tôi đã nghĩ ra cho đến nay. –

Trả lời

1

Tại sao bạn không tận dụng tối đa cả hai thế giới? Có một CustomResourceManager xử lý việc nạp tài nguyên và chọn đúng nền văn hóa và sử dụng CustomResourceReader sử dụng bất kỳ cửa hàng sao lưu nào bạn thích. Một triển khai cơ bản có thể trông như thế này, dựa trên quy ước của Resourceky là Typename_PropertyName_PropertyValue. Nếu vì lý do nào đó cấu trúc của hệ thống sao lưu (csv/excel/mssql/table structure) cần phải thay đổi bạn chỉ có sự thay đổi việc thực thi ResourceReader.

Là phần thưởng gia tăng, tôi cũng nhận được proxy thực/trong suốt.

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 
    } 

Cách sử dụng

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

Bất động/Transparent sử dụng Proxy

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; 
+0

Tôi lo lắng về các đặc điểm truy vấn của giải pháp này. Nếu tôi không nhầm, tôi cần phải tải tất cả các chuỗi được bản địa hóa trong ứng dụng vào bộ nhớ (với tất cả các vấn đề đi kèm với đó), hoặc tôi cần gọi MyRM.GetEntity trên từng thực thể riêng lẻ - điều này sẽ gây ra vấn đề nghiêm trọng N + 1 khi tôi muốn hiển thị một bảng các thực thể. –

+0

Nó phụ thuộc. Nếu áp lực bộ nhớ là mối quan tâm của bạn, bạn có thể thực hiện một giải pháp bộ nhớ đệm thông minh loại bỏ resourceSets khỏi bộ nhớ sau thời gian x. Hoặc có nhiều vấn đề hơn tôi không giám sát bây giờ? Và bạn đúng là bạn phải gọi GetEntity trên mỗi thực thể. Nhưng đó là một trong hai hoặc có một truy vấn phức tạp.Một điều tôi đã thử nhưng không thể làm việc được hoặc là một động lực hoặc minh bạchpro exposes địa phương hóa kết quả từ tài sản của bạn. Điều đó tích hợp thực thể được bản địa hóa trong bất kỳ mã hiện tại nào liền mạch. Và sau đó bạn có thể tập trung vào việc triển khai kỹ thuật đáp ứng các yêu cầu của bạn. – rene

+0

Vấn đề bức xúc hơn, tôi nghĩ, là sạch sẽ đảm bảo rằng bộ nhớ cache này bị vô hiệu khi lưu trữ đọc từ xa được cập nhật. –

1

Đầu tiên một là xứng đáng nếu bạn có nội dung tĩnh trong cơ sở dữ liệu. Ví dụ: nếu bạn có danh mục mà tương đối sẽ không bị người dùng thay đổi. Bạn có thể thay đổi chúng khi triển khai tiếp theo. Tôi không tự mình giải pháp này. Tôi không coi đây là một giải pháp tốt đẹp. Đây chỉ là một lối thoát của vấn đề.

Thứ hai là tốt nhất nhưng có thể gây ra sự cố khi bạn có hai hoặc nhiều trường có thể bản địa hóa trong một thực thể. Bạn có thể đơn giản hóa một chút và cứng mã ngôn ngữ trên nó như thế này

public class LocalizedString 
{ 
    public int Id { get; set; } 

    public string EnglishText { get; set; } 
    public string ItalianText { get; set; } 
    public string ArmenianText { get; set; } 
} 

thứ ba một không phải là một trong những tốt không. Từ cấu trúc này tôi không thể chắc chắn rằng tất cả các nút (chữ, dòng, chuỗi, vv) được dịch trong văn hóa cụ thể.

Không khái quát quá nhiều. Mỗi vấn đề là loại chuyên ngành và nó cũng cần giải pháp chuyên ngành. Quá nhiều khái quát hóa làm cho các vấn đề không được điều chỉnh.

+0

Tôi không ưa thích việc khử chuẩn hóa thực thể LocalizedString vì điều này có nghĩa là người dùng quản trị sẽ không thể thêm mới ngôn ngữ. Tôi không nghĩ đây là vấn đề "khái quát quá nhiều". Đây là vấn đề kinh doanh chính hãng - người dùng của tôi thường cần có khả năng chỉnh sửa thực thể, ngôn ngữ, bản dịch mà không cần sự can thiệp của nhà phát triển. –

+0

Rất tiếc, tôi không hiểu chính xác bạn. Tôi không biết rằng bạn muốn có thể thêm ngôn ngữ nữa. – TIKSN

Các vấn đề liên quan