8

我有很多表都有 TextID 列,它指的是翻译表。翻译表还需要 LanguageID 以获取所需语言的翻译文本。我的问题是我的数据库中没有 LanguageID,它是在系统中预定义的,我不知道如何使用 Fluent API 定义它,即这可以是我的模型:

public partial class MyEntity
{    
    public short ID { get; set; }
    public Nullable<int> TextID { get; set; }
    [NotMapped]
    public Nullable<int> LanguageID { get; set; }
    public virtual TEXT_TRANSLATION Translation { get; set; }
}

和翻译表:

public partial class TEXT_TRANSLATION
{
    [Key, Column(Order = 0)]
    public int TextID { get; set; }

    [Key, Column(Order = 1)]
    public int LanguageID { get; set; }

    public string TranslatedText { get; set; } 
}

基本上我需要这样的导航:

myEntity.Translation.TranslatedText

在使用 SQL 时,我会这样做:

Left Join TEXT_TRANSLATION ON 
  MyEntity.TextID = TEXT_TRANSLATION.TextID 
  AND TEXT_TRANSLATION.LanguageID = 1033 

基本上我想使用 TextID 外键并只获得一个翻译 - LanguageID 是静态的并且在上下文中预定义。

我无法更改现有的数据库架构。如果我不需要在我的代码中映射 LanguageID 字段,那将是完美的,只需在映射中使用它,就像系统参数一样。EF甚至可以吗?

4

3 回答 3

5

如果你LanguageID是静态的,你可以尝试使用这个 hack。

定义您的实体,例如:

public class Entity {
    public int Id { get; set; }
    public int TextId { get; set; }
    public Translation Translation { get; set; }
}

// No LanguageId in translation
public class Translation {
    public int TextId { get; set; }
    public string TranslatedText { get; set; }
}

并将这个流畅的映射添加到OnModelCreating您的派生中DbContext

// Define foreign key
modelBuilder.Entity<Entity>()
            .HasRequired(e => e.Translation)
            .WithMany()
            .HasForeignKey(e => e.TextId);

// Trick - EF believes that only TextId is PK. Without this trick you cannot
// make navigation property on your entity
modelBuilder.Entity<Translation>()
            .HasKey(t => t.TextId);

// If you are going to insert translations as well your TextId cannot be 
// handled as autogenerated column
modelBuilder.Entity<Translation>()
            .Property(t => t.TextId)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

// The HACK - conditional mapping. This tells EF to "see" only records
// with LanguageId set to 1033. Without this hack you cannot filter 
// translations for only single language.
modelBuilder.Entity<Translation>()
            .Map(m => {
                        m.Requires("LanguageId").HasValue(1033);
                        m.ToTable("Translations");   
                    });

该 hack 基于用于 TPH 映射的概念,但在这种情况下,您仅使用单个实体类型来仅加载预定义的记录子集LanguageId。即使来自主要实体的 FK 也应该可以工作,因为您不必翻译相同的TextId内容 - 这意味着它们也具有相同的LanguageId内容,这是不可能的,因为TextIdLanguageId形成主键。

我不确定此解决方案中是否存在任何隐藏问题。我只是快速尝试了一下,它奏效了。

于 2013-01-28T19:00:53.803 回答
2

如果你做了这样的事情怎么办:

public partial class MyEntity
{    
    public short ID { get; set; }
    public Nullable<int> TextID { get; set; }
    [NotMapped]
    public Nullable<int> LanguageID { get; set; }
    public virtual ICollection<TEXT_TRANSLATION> Transations {get;set;}

    public IQueryable<TEXT_TRANSLATION> Translation
    {
      get
      {
          return this.Translations.Where( t => t.LanguageID == this.LanguageID );
      }
    }
}

虚拟 ICollection 将存储所有翻译的列表,无论语言如何,Translation 属性将为您执行 .Where()

于 2013-01-28T16:43:44.353 回答
2

即使 T_T 表很大,我也看不出你怎么能以另一种方式做到这一点(好吧,至少需要“在某个地方加载所有内容”)。

public static class Translator {
  private static Dictionary<int, Dictionary<int, string>> translations_;

  static Translator() {
    translations_ = new Dictionary<int, Dictionary<int, string>>();
  } 

  public static void PopulateTranslator(Repository repo) {//or something to go to your db
     translations_ = repo.TEXT_TRANSLATIONs.ToList()
                     .GroupBy(m => m.LanguageId)
                     .ToDictionary(g => g.Key, 
                                   g=> g.ToDictionary(x => x.TextID,
                                                      x=> x.TranslatedText)
                                   );
  }
  public static string GetTranslation(int languageId, int? textId) {
      if (textId == null || !translations.ContainsKey(languageId)
         return something or throw;
      var dic = translations[languageId];
      var id = Convert.ToInt32(textId);
      if (!dic.ContainsKey(id)
          return something or throw;

      return dic[id);

  }

如果用户可以更新翻译,您将Translator.PopulateTranslator(<something to access your db>)在您的应用程序启动时调用,可能会进行重置。

然后在您的实体中,而不是

public virtual TEXT_TRANSLATION Translation { get; set; }

你将会拥有

public string TranslatedText {
    get { return Translator.GetTranslation(LanguageID, TextId);}
}

编辑 另一种方法是使用 resx 文件而不是 db 数据,但我想你还有其他限制。

于 2013-01-28T17:08:59.700 回答