【问题标题】:How do I map property with of type 'Dictionary' with Entity Framework?如何使用实体框架映射类型为“字典”的属性?
【发布时间】:2018-10-14 21:25:15
【问题描述】:
using Microsoft.EntityFrameworkCore;

namespace NameSpaceName
{
    public class WordContext : DbContext
    {
        public DbSet<VmWord> Words { get; set; }
        public DbSet<VmWordLocalization> WordLocalization { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite("Data Source=NameSpaceName.db");
        }
    }
}

namespace NameSpaceName
{
    public class VmWord
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Dictionary<string, VmWordLocalization> Localization { get; set; }
    }
}

namespace NameSpaceName
{
    public class VmWordLocalization
    {
        public int Id { get; set; }
        public string En { get; set; }
        public string Pl { get; set; }
    }
}

我有 VmWord 表,我需要将 VmWordLocalization 添加为字典,以便能够按键获取本地化变量值;

当我尝试执行时:

dotnet ef 迁移添加 Add_Localization_Table --context WordContext

我明白了:

无法映射属性“VmWord.Localization”,因为它是 'Dictionary' 类型的,它不是 支持的原始类型或有效的实体类型。要么显式映射 此属性,或使用 '[NotMapped]' 属性或通过 在“OnModelCreating”中使用“EntityTypeBuilder.Ignore”。

如何映射“字典”类型的属性?

【问题讨论】:

  • 请解释一下您希望实体框架如何在表格中布置字典(键值集合)

标签: c# entity-framework dictionary orm entity


【解决方案1】:

您想要一个具有Dictionary 属性的对象的原因是因为您想要快速查找:Give me the VmWord with the Localization with this Key。换句话说:给定一个键,您希望快速查找包含该键的 VnWordLocalization 对象。

在实体框架中,DbSet&lt;...&gt; 表示关系数据库中的表。关系型数据库不知道Dictionary这个概念。

幸运的是,数据库知道使用键进行快速查找的概念。通常这是主键,但您可以添加其他可以用作键的索引。

唉,你忘了告诉我们你想用哪个字符串来快速查找你的VmWordLocalizations。是En吗?或Pl?还是全新的钥匙?

如果它是一个全新的 Key,那么显然每个 VmWordLocalication 都有一个属性,我们将其命名为 Key,它与外键 VmWordId 组合是唯一的。如果是EnPl,则将其用作Key。

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

    // every VmWord has zero or more Localizations (one-to-many)
    public virtual ICollection<VmWordLocalization> VmWordLocalizations { get; set; }

    ... // other properties
}

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

    // every VmWordLocalization belongs to exactly one VmWord using foreign key
    public int VmWordId {get; set;}
    public virtual VmWord VmWord {get; set;}

    // every VmWordLocalization has a property Key, which is unique in combination
    // with VmWordId
    public string Key {get; set;}
}

注意:如果 Key 是整体唯一的,请考虑将其用作主 Key。

注意2:在实体框架中,非虚拟属性代表表中的列,表之间的关系由虚拟属性表示

注意 3:考虑坚持使用entity framework code-first conventions. 它消除了对属性或流畅 API 的需求。只有在有充分理由的情况下才偏离这些约定。必须输入更少的字符不是一个好理由。

如果Key 不能成为您的主键,您将不得不添加一个额外的快速查找:索引。这是在DbContext.OnModelCreating

protected overrid void OnModelCreating(...)
{
     // Every VmWordLocalization has a property Key, which is unique per VmWordId
     // create an index with (VmWordId, Key) as index key
     var vmWordLocalizationEntity = modelBuilder.Entity<VmWordLocalization>();

     // first index annotation: VmWordId:
     vmWordLocalizationEntity.Property(entity => entity.VmWordId)
         .IsRequired()
         .HasColumnAnnotatin(IndexAnnotation.AnnotationName, 
              new IndexAnnotation(new IndexAttribute("index_VmWordId", 0)));

     // 2nd index annotation: Key:
     vmWordLocalizationEntity.Property(entity => entity.Key)
         .IsRequired()
         .HasColumnAnnotatin(IndexAnnotation.AnnotationName, 
              new IndexAnnotation(new IndexAttribute("index_Key", 1)));
}

标识符index_VmWordindex_Key 可以是任何东西。它们只是命名索引列的标识符。

数字 0 和 1 给出了索引的排序顺序:首先是index_VmWordId,然后是index_Key

现在,如果您有一个带有 Id == 10 的 VmWord,则可以添加带有 VmWordId == 10 和 Key == "Hello" 的 VmWordLocation。但是,如果您尝试使用这些值添加第二个VmWordLocation,您将得到一个异常,就像您将两个具有相同键的对象添加到字典中一样。

现在获取请求的VmWords 及其在字典中的本地化:

var vmWordsWithLocalizations = myDbContext.VmWords.Select(vmWord => new
{
      // select only the properties you plan to use:
      Id = vmWord.Id,
      Name = vmWord.Name,

      Localizations.VmWord.VmwordLocalizations
          .Where(vmWordLocalization => ...) // only if you do't want all vmWordLocalizations
          .ToDictionary(vmWordLocalization => vmWordLocalization.Key,  // Dictionary Key
              new                                                      // Dictionary value
              {   // again: select only the properties you plan to use
                  Id = vmWordLocalization.Id,
                  En = vmWordLocalization.Em,
                  Pl = vmWordLocalization.Pl,

                  // no need: you know it equals vmWord.Id
                  // VmWordId = vmWordLocalization.VmWordId

              }),

});

如果您必须经常这样做,请考虑创建一个扩展函数:

IEnumerable<VmWordWithDictionary> ToVmWordWithDictionary(this IQueryable<VmWord> vmWords)
{
     return vmWords.Select(vmWord => new VmWordEx()
     {
         Id = vmWord.Id,
         Name = vmWord.Name,
         Localizations = vmWord.VmWordLocalizatons
             .Where(vmWordLocalization => ...)
             .ToDictionary(
                 vmWordLocalization => vmWordLocalization.Key        // Key
                 vmWordLocalization => new VmWordLocalizationEx()    // Value
                 {
                      Id = vmWordLocalization.Id,
                      En = vmWordLocalization.Em,
                      Pl = vmWordLocalization.Pl,
                 }),
});

注意:出于效率原因,我不想传输比需要更多的数据,所以我创建了两个额外的类,只包含我需要的数据。

注意:由于 Dictionary 的原因,返回值必须是 IEnumerable。在此之后您不能再执行 IQueryable LINQ 语句了

【讨论】:

  • 感谢您的回答。我考虑过通过键 En、Pl 获取值,有点像 lang = "en" word.Localization[lang];,因为我的项目的问题是有太多类似常量的调用“en”、“pl”和 word.localization_en 等字段, word.localization_pl 使用。添加新的本地化变得困难
【解决方案2】:

在 EF 中不可能有 Dictionary 属性。解决方法是将Dictionary替换为ICollection

namespace NameSpaceName
{
    public class VmWord
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public ICollection<VmWordLocalization> Localizations { get; set; }
    }

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

        public string Key { get; set; }  // key from your previous dictionary

        public string En { get; set; }

        public string Pl { get; set; }
    }
}

【讨论】:

  • 不要忘记一对多中的virtual。在实体框架中,表列用非虚属性表示,表之间的关系用虚属性表示
【解决方案3】:

问题是,什么是字典,您如何看待此映射到物理数据库表(您是否希望有一个包含单词列表的表,然后是另一个包含每个翻译重复的单词列表的表) ?我在这里“吐口水”,因为我不确定您要达到的目标是什么,但是您可能希望拥有一个带有单词 id 和语言 id 的表而不是带有翻译链接的基本词它构成了一个复合主键,那么您只需应用 where wordid = x 和 language = y 即可获得该单词的正确版本。或者,如果您设置在您的结构上,那么您将需要使用外键实现一个子表,并在 VmWord 类上定义它:

public List<VmWordLocalisation> {get;set;}

然后在子表上你会像这样定义外键

[ForeignKey("Id")]
public VmWord Parent {get;set;}

您还需要在子表上设置复合主键以识别 Id 和语言

希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 2015-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-15
    • 2021-12-20
    • 2010-09-24
    • 1970-01-01
    相关资源
    最近更新 更多