实体框架 - 与假外键的关系(数据库中没有外键)

时间:2013-01-28 15:52:56

标签: entity-framework ef-code-first entity-relationship

我有很多具有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外键并获得 ONLY ONE 翻译 - LanguageID是静态的,并在上下文中预定义。

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

3 个答案:

答案 0 :(得分:5)

如果LanguageID是静态的,您可以尝试使用此黑客。

定义您的实体,如:

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,这是不可能的,因为TextId和{{ 1}}表格主键。

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

答案 1 :(得分: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将存储所有翻译的列表,无论语言如何,翻译属性都会为您执行.Where()

答案 2 :(得分: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数据,但我想你有其他约束。