使用EF Code First本地化实体的最佳实践

10
我正在使用EF Code First开发域模型以持久化数据。我需要添加多语言支持,但不想在域模型中加入位置概念。
我希望数据库中存在一个ProductTranslate表,包含标题和语言字段,但在我的域中,标题属于Product实体。
有人知道如何实现吗?

前端是什么? - Yuliam Chandra
嗨,Yuliam,我不明白你的问题,我的问题是从领域模型的角度而不是从UI的角度。 - xurxodev
如果前端是asp.mvc,有一种方法可以从属性名称推断本地化,而无需添加任何属性,因为本地化在前端中使用,我认为读者需要知道。 - Yuliam Chandra
1个回答

21
这是关于编程的内容。返回翻译后的文本:

这是我使用的,与 Code First 很好地配合。

定义一个基础的 Translation 类:

using System;

public abstract class Translation<T> where T : Translation<T>, new()
{

  public Guid Id { get; set; }

  public string CultureName { get; set; }

  protected Translation()
  {
    Id = Guid.NewGuid();
  }

}

定义一个 TranslationCollection 类:
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;

public class TranslationCollection<T> : Collection<T> where T : Translation<T>, new()
{

  public T this[CultureInfo culture]
  {
    get
    {
      var translation = this.FirstOrDefault(x => x.CultureName == culture.Name);
      if (translation == null)
      {
        translation = new T();
        translation.CultureName = culture.Name;
        Add(translation);
      }

      return translation;
    }
    set
    {
      var translation = this.FirstOrDefault(x => x.CultureName == culture.Name);
      if (translation != null)
      {
        Remove(translation);
      }

      value.CultureName = culture.Name;
      Add(value);
    }
  }

  public T this[string culture]
  {
    get
    {
      var translation = this.FirstOrDefault(x => x.CultureName == culture);
      if (translation == null)
      {
        translation = new T();
        translation.CultureName = culture;
        Add(translation);
      }

      return translation;
    }
    set
    {
      var translation = this.FirstOrDefault(x => x.CultureName == culture);
      if (translation != null)
      {
        Remove(translation);
      }

      value.CultureName = culture;
      Add(value);
    }
  }

  public bool HasCulture(string culture)
  {
    return this.Any(x => x.CultureName == culture);
  }

  public bool HasCulture(CultureInfo culture)
  {
    return this.Any(x => x.CultureName == culture.Name);
  }

}

然后您可以在实体中使用这些类,例如:

using System;
using System.Globalization;

public class HelpTopic
{

  public Guid Id { get; set; }

  public string Name { get; set; }

  public TranslationCollection<HelpTopicTranslation> Translations { get; set; }

  public string Content
  {
    get { return Translations[CultureInfo.CurrentCulture].Content; }
    set { Translations[CultureInfo.CurrentCulture].Content = value; }
  }

  public HelpTopic()
  {
    Id = Guid.NewGuid();
    Translations = new TranslationCollection<HelpTopicTranslation>();
  }

}

HelpTopicTranslation 定义为:

using System;

public class HelpTopicTranslation : Translation<HelpTopicTranslation>
{

  public Guid Id { get; set; }

  public Guid HelpTopicId { get; set; }

  public string Content { get; set; }

  public HelpTopicTranslation()
  {
    Id = Guid.NewGuid();
  }

}

现在,针对代码优先的方面,使用以下配置:

using System.Data.Entity.ModelConfiguration;
using Model;

internal class HelpTopicConfiguration : EntityTypeConfiguration<HelpTopic>
{

  public HelpTopicConfiguration()
  {
    Ignore(x => x.Content); // Ignore HelpTopic.Content since it's a 'computed' field.
    HasMany(x => x.Translations).WithRequired().HasForeignKey(x => x.HelpTopicId);
  }

}

将其添加到您的上下文配置中:
public class TestContext : DbContext
{

  public DbSet<HelpTopic> HelpTopics { get; set; }

  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
    modelBuilder.Configurations.Add(new HelpTopicConfiguration());
  }

}

当所有这些都完成后,将生成以下迁移:

using System.Data.Entity.Migrations;

public partial class AddHelpTopicTable : DbMigration
{

  public override void Up()
  {
    CreateTable(
      "dbo.HelpTopics",
      c => new
      {
        Id = c.Guid(false),
        Name = c.String(),
      })
      .PrimaryKey(t => t.Id);

    CreateTable(
      "dbo.HelpTopicTranslations",
      c => new
      {
        Id = c.Guid(false),
        HelpTopicId = c.Guid(false),
        Content = c.String(),
        CultureName = c.String(),
      })
      .PrimaryKey(t => t.Id)
      .ForeignKey("dbo.HelpTopics", t => t.HelpTopicId, true)
      .Index(t => t.HelpTopicId);
  }

  public override void Down()
  {
    DropForeignKey("dbo.HelpTopicTranslations", "HelpTopicId", "dbo.HelpTopics");
    DropIndex("dbo.HelpTopicTranslations", new[] { "HelpTopicId" });
    DropTable("dbo.HelpTopicTranslations");
    DropTable("dbo.HelpTopics");
  }

}

欢迎提出任何评论和/或改进意见...


2
我制作了一个测试应用程序来尝试这个。运行得很好。非常优雅的解决了一个常见的需求。谢谢Julien。 - Bogac
1
看起来很不错。我还没有尝试过,但我认为可能的改进是将集合和可本地化属性包装在一个复杂类型中,例如LocalizableString。这样添加新的本地化属性每个属性只需要一行代码。 - Francesc Castells
1
这意味着所有的翻译集合将从数据库中获取,并在客户端进行语言代码过滤,对吗? - jayasurya_j
@jayasurya_j 是的,没错。我猜这是一个限制 :-( - Julien Poulin
@jayasura_j 不一定 - 取决于您如何实现本地化,您可以调整查询以在查询内部进行过滤。 您不需要获取全部内容。 这会产生不同的成本。 如果允许用户随意切换语言,则呈现可能变得非常昂贵,因为所有项目都需要发送。 通常情况下,您可能只使用几种语言,因此额外的字符串不会带来太多开销。 - Jon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接