【问题标题】:Entity framework Fluent API does not consider base class properties实体框架 Fluent API 不考虑基类属性
【发布时间】:2014-09-02 08:52:36
【问题描述】:

EF 6.1:

我们刚刚开始了一个有很多 pf 继承的项目。选定的继承数据库映射类型是每个层次结构的表。问题是当尝试使用 add-migration 生成迁移时,会抛出以下错误:

The foreign key component 'VersionId' is not a declared property on type 'SER'. Verify that it has not been explicitly excluded from the model and that it is a valid primitive property.

这里是使用的类和配置类:

public class Version : BaseObject
{
    public virtual ICollection<SER> ListOfSER { get; set; }
}

public abstract class AbsractR : BaseObject
{
    public int ParentId { get; set; }
    public int ChildId { get; set; }

    public int VersionId { get; set; }
    public virtual Version Version { get; set; }
}

public class SER : AbstractR
{
    public int SEDId
    {
        get
        {
            return base.ChildId;
        }
        set
        {
            base.ChildId = value;
        }
    }
    public virtual SED SED { get; set; }
}

public abstract class AbstractD : BaseObject
{
}

public class SED : AbstractD
{
    public virtual ICollection<SER> ListOfSER { get; set; }
}


public class SDContext : BaseContext
{
    public DbSet<Version> Versions { get; set; }
    public DbSet<AbstractD> Ds { get; set; }
    public DbSet<AbstractR> Rs { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Configurations.Add(new VersionConfiguration());

        #region Refs
        modelBuilder.Configurations.Add(new AbstractRConfiguration());
        modelBuilder.Configurations.Add(new SERConfiguration());
        #endregion

        #region Defs
        modelBuilder.Configurations.Add(new AbstractDConfiguration());
        modelBuilder.Configurations.Add(new SEDConfiguration());
        #endregion
    }
}

public class BaseObjectConfiguration<T> : EntityTypeConfiguration<T> where T : BaseObject
{
    public BaseObjectConfiguration()
    {
        #region Key
        this.HasKey(bo => bo.Id);
        #endregion

        #region Properties
        this.Property(bo => bo.Id).IsRequired();
        this.Property(bo => bo.IsDeleted).IsRequired();
        this.Property(bo => bo.LastModificationDate).IsOptional();
        this.Property(bo => bo.OptimisticVersion).IsConcurrencyToken().IsRequired().IsRowVersion();
        this.Property(bo => bo.CreationDate).IsRequired();
        this.Property(bo => bo.DeletionDate).IsOptional();
        #endregion
    }
}

public class VersionConfiguration : BaseObjectConfiguration<Version>
{
    public VersionConfiguration() : base()
    {
        #region Properties
        #endregion

        #region Objects
        this.HasMany(mdv => mdv.ListOfSER).WithRequired().HasForeignKey(ser => ser.VersionId).WillCascadeOnDelete(false);
        #endregion

        #region Table
        this.ToTable("Versions");
        #endregion
    }
}

public class AbstractRConfiguration : BaseObjectConfiguration<AbstractR>
{
    public AbstractRConfiguration()
        : base()
    {
        #region Properties
        this.Property(ser => ser.VersionId).IsRequired();
        #endregion

        #region Objects
        this.HasRequired(ar => ar.Version).WithMany().HasForeignKey(ar => ar.VersionId).WillCascadeOnDelete(false);
        #endregion

        #region Table
        this.ToTable("Refs");
        #endregion
    }
}

public class SERConfiguration : BaseObjectConfiguration<SER>
{
    public SERConfiguration()
        : base()
    {
        #region Properties
        this.Ignore(ser => ser.SEDId);
        #endregion

        #region Objects
        this.HasRequired(ser => ser.SED).WithMany(sed => sed.ListOfSER).HasForeignKey(ser => ser.ChildId).WillCascadeOnDelete(false);
        #endregion

        #region Table
        this.ToTable("Refs");
        #endregion
    }
}

public class AbstractDConfiguration : BaseObjectConfiguration<AbstractD>
{
    public AbstractDConfiguration() : base()
    {
        this.ToTable("Defs");
    }
}

public class SEDConfiguration : BaseObjectConfiguration<SED>
{
    public SEDConfiguration()
        : base()
    {
        #region Properties
        #endregion

        #region Objects
        this.HasMany(sed => sed.ListOfSER).WithRequired(sed => sed.SED).HasForeignKey(sed => sed.ChildId).WillCascadeOnDelete(false);
        #endregion

        #region Table
        this.ToTable("Defs");
        #endregion
    }
}

我知道我们可以使用 [ForeignKey] 属性来告诉派生类的导航属性应该使用父抽象类中定义的列。我们希望避免使用 DataAnnotations。我只是不明白为什么它会抛出这个错误。 “版本”导航属性是在 AbstractR 配置中定义的,而不是在 SER 配置中定义的(由于 SER 继承自 AbstractR,这也应该可以工作),对吗?

其次,删除 Version 属性和映射时,SER 映射中使用的“ChildId”和“ParentId”属性也会出现同样的问题。这是一个已知问题吗?我是不是做错了什么?

PS : 为简单起见,已删除 ParentId 映射,因为它似乎与 ChildId 映射存在相同的问题。

有人知道为什么会出现这种问题吗?


更新

经过更多研究,Fluent API 似乎无法使用基类属性进行映射。那正确吗 ?这是一种通缉行为吗?为什么 DataAnnotations 能够使用基类属性而不是 Fluent API?不是所有的基类属性都插入到每个类中,还是使用某种装饰器模式读取?

【问题讨论】:

  • 我建议您将找到的答案写为对原始问题的答案,将其标记为答案,然后针对这些新问题开始一个新问题。
  • 我没有找到任何答案。
  • 哪些数据注解可以使用基类属性?
  • [ForeignKey("VersionId")], [ForeignKey("ChildId")] 效果很好,但是使用 FluentAPI 根本就不行。
  • @Whoami,不确定你是如何定义它的类型/属性的,但这可能会有所不同

标签: c# entity-framework inheritance table-per-hierarchy ef-fluent-api


【解决方案1】:

只要主体还使用基类类型的导航属性,您就可以将基类属性用作外键关联。

主体 (Version) 已将 ListOfSER 声明为 SER 类型的导航属性为依赖

public class Version : BaseObject
{
    public virtual ICollection<SER> ListOfSER { get; set; }
}

,但配置使用基类属性(VersionId)作为外键关联

public class VersionConfiguration : BaseObjectConfiguration<Version>
{
    public VersionConfiguration()
        : base()
    {
        HasMany(mdv => mdv.ListOfSER)
            .WithRequired()
            .HasForeignKey(ser => ser.VersionId) // -> belongs to base class
            .WillCascadeOnDelete(false);
    }
}

配置ForeignKeyConstraintConfiguration的时候是不允许的,看代码摘录

foreach (var dependentProperty in dependentPropertyInfos)
{
    var property
        = dependentEnd.GetEntityType() // -> SER
            .GetDeclaredPrimitiveProperty(dependentProperty); // -> VersionId

    if (property == null) // VersionId is not part of SER metamodel
    {
        throw Error.ForeignKeyPropertyNotFound(
            dependentProperty.Name, dependentEnd.GetEntityType().Name);
    }

    dependentProperties.Add(property);
}

解决方案 1ListOfSER 类型从 SER 更改为 AbstractR

public class Version : BaseObject
{
    public virtual ICollection<AbstractR> ListOfSER { get; set; }
}

这会更有意义,VersionId 是在基类上定义的,任何派生类型都应该能够使用这个外键关联,对吧?但不幸的是Version 类型只允许SERVersion 关联。

定义fluent api配置时也有不一致的地方。

public class VersionConfiguration : BaseObjectConfiguration<Version>
{
    public VersionConfiguration()
        : base()
    {
        HasMany(mdv => mdv.ListOfSER)
            .WithRequired() // -> this should be WithRequired(x => x.Version)
            .HasForeignKey(ser => ser.VersionId)
            .WillCascadeOnDelete(false);
    }
}
public class AbstractRConfiguration : BaseObjectConfiguration<AbstractR>
{
    public AbstractRConfiguration()
        : base()
    {
        HasRequired(ar => ar.Version)
            .WithMany() // -> this should be WithMany(x => x.ListOfSER)
            .HasForeignKey(ar => ar.VersionId)
            .WillCascadeOnDelete(false);
    }
}

更重要的是,您不必在双方都进行配置。只需将其放在VersionConfigurationAbstractRConfiguration 上就足够了。

解决方案 2VersionVersionId 从基类移动到 SER,如果您希望 Version 仅与 SER 关联。

public class Version : BaseObject
{
    public virtual ICollection<SER> ListOfSER { get; set; }
}
public abstract class AbstractR : BaseObject
{
}
public class SER : AbstractR
{
    public int VersionId { get; set; }
    public virtual Version Version { get; set; }
}

并在Version 配置或SER 配置上进行配置。

public class VersionConfiguration : BaseObjectConfiguration<Version>
{
    public VersionConfiguration()
        : base()
    {
        HasMany(mdv => mdv.ListOfSER)
            .WithRequired(x => x.Version)
            .HasForeignKey(ser => ser.VersionId)
            .WillCascadeOnDelete(false);
    }
}
public class SERConfiguration : BaseObjectConfiguration<SER>
{
    public SERConfiguration()
        : base()
    {
        HasRequired(ar => ar.Version)
            .WithMany(x => x.ListOfSER)
            .HasForeignKey(ar => ar.VersionId)
            .WillCascadeOnDelete(false);
    }
}

【讨论】:

  • 感谢您的回复,但是:解决方案 1 是我想避免的。我不想要版本中的ICollection&lt;AbstractR&gt;,而是我想要ICollection&lt;SER&gt;ICollection&lt;FR&gt; 等(FR 继承自 AbstractR)。这就是我在 AbstractR 对象中使用不带参数的 WithMany() 的原因。这不是为什么要写:“将关系配置为 many:req 而另一侧没有导航属性”?但我同意我错过了 VersionConfig 中的WithRequired(mdv =&gt; mdv.Version)。 sol 2 是不可能的,因为我们在 AbstractR 下有 8 个类
  • @Whoami,如果你使用WithMany和空参数,版本和基类仍然有关系,但它是一个独立的关联。 ListOfSER 不会连接到 VersionId。为什么要避免解决方案 1,如果您不想在派生类上定义每个 VersionId,那么 WithMany() 就是您要查找的内容,但删除 LISTOfSERHasMany(mdv =&gt; mdv.ListOfSER)... 配置,让它独立于Version
  • 好的,那么我别无选择,只能将 ICollection 放入版本中。感谢您的回答。将您的答案标记为已接受。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多