如何在实体框架中映射类型为“字典”的属性?

时间:2018-10-14 21:25:15

标签: c# entity-framework dictionary orm entity

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”,因为它是   类型为“字典”,而不是   支持的原始类型或有效的实体类型。要么明确映射   此属性,或使用'[NotMapped]'属性或通过忽略此属性   在'OnModelCreating'中使用'EntityTypeBuilder.Ignore'。

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

3 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

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

public List<VmWordLocalisation> {get;set;}

然后在子表上,您将像这样定义外键

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

您还需要在子表上设置一个复合主键,以标识ID和语言

希望这会有所帮助!

答案 2 :(得分:0)

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

在实体框架中,DbSet<...>代表关系数据库中的表。关系数据库不知道Dictionary的概念。

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

A,您忘了告诉我们您要使用哪个字符串作为VmWordLocalizations的快速查找。是En吗?或Pl?还是一个全新的钥匙?

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

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总体上是唯一的,请考虑将其用作主键。

注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

现在,如果您有一个VmWord == 10的Id,则可以添加一个VmWordLocation == 10和VmWordId的{​​{1}} ==“你好”。但是,如果您尝试使用这些值添加第二个Key,则会出现异常,就像将两个具有相同键的对象添加到字典中一样。

现在可以在字典中获取所请求的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。此后,您将无法再执行IQueryable LINQ语句