Entity framework Fluent API does not consider base class properties
Asked Answered
S

1

1

EF 6.1 :

We just started a project that has a lot pf inheritance. The selected inheritance db mapping type is the table per hierarchy. The problem is that when trying to generate the migration using the add-migration, the following error is thrown :

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.

Here are the classes & the configuration classes used :

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

I know we can use the [ForeignKey] attribute to tell that the navigation property on a derived class should use the column defined in the parent abstract class. We would like to avoid using DataAnnotations. I just don't get why it throw this error. The "Version" navigation property is defined in the AbstractR configuration and not in the SER configuration (which should also work since SER inherits from AbstractR), am I right ?

Secondly, when removing the Version property & mapping, the same problem appears with the "ChildId" and "ParentId" property used in the SER mapping. Is this a know problem ? Am I doing something wrong ?

PS : The ParentId mapping has been removed for simplicity since it seems to be the same problem as the ChildId mapping.

Has anyone any idea why this kind of problem is happening ?


UPDATE

After some more research, it appeared that Fluent API cannot use base class properties for the mapping. Is that right ? Is this a wanted behavior ? Why are the DataAnnotations able to use base class properties and not Fluent API ? Aren't all the base class properties inserted inside every classes or is it read with some kind of decorator pattern ?

Slumber answered 2/9, 2014 at 8:52 Comment(5)
I suggest that you write the answer that you found to your original question as an answer, mark it as the answer, and start a new question for these new questions.Scottscotti
I haven't found any answers.Slumber
Which data annotations that are able to use base class properties?Licence
[ForeignKey("VersionId")], [ForeignKey("ChildId")] works well, but using the FluentAPI it is not working at all.Slumber
@Whoami, not sure how you define it on which type / property, but that would probably be something differentLicence
L
3

You can use base class properties as foreign key associations as long as the principal also uses navigation properties of type base class.

The Principal (Version) has declared ListOfSER as navigation property of type SER as Dependent

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

, but the configuration uses base class property (VersionId) as foreign key association

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

Which is not allowed when configuring ForeignKeyConstraintConfiguration, take a look at the code excerpt

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

The solution 1 would be changing the ListOfSER type from SER into AbstractR.

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

This will make more sense, the VersionId is defined on base class, any derived type should be able to use this foreign key association, right? But unfortunately the Version type only allows SER to be associated with Version.

You also have inconsistency when defining fluent api configuration.

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

Even more, you don't have to configure on both side. Just put it either on VersionConfiguration or AbstractRConfiguration would be sufficient.

The solution 2 would be moving the Version and VersionId from base class to SER, if you want Version only to be associated with 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; }
}

And configuring either on Version configuration or SER configuration.

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);
    }
}
Licence answered 17/9, 2014 at 10:33 Comment(3)
Thanks for your reply but: The solution 1 is what I would like to avoid. I don't want an ICollection<AbstractR> in the Version, but instead I would like to have ICollection<SER>, ICollection<FR> etc (FR inherits from AbstractR). That's why I used the WithMany() without parameters in the AbstractR object. Isn't that why it's written : "Configures the relation to be many:req without a navigation property on other side" ? But I aggree I missed the WithRequired(mdv => mdv.Version) in the VersionConfig. The sol 2 is not possible because we have 8classes under AbstractRSlumber
@Whoami, if you use WithMany with empty argument, Version and base class will still have relationship, but it's an independent association. ListOfSER will not connect to VersionId. Why do you want to avoid solution 1 and if you don't want to define each VersionId on derived class, then WithMany() is what you're looking for, but remove the LISTOfSER and the HasMany(mdv => mdv.ListOfSER)... configuration, let it be independent association from VersionLicence
Ok, then I have no other choice that putting an ICollection<AbstractR> in the Version. Thanks for your answer. Marking your answer as accepted one.Slumber

© 2022 - 2024 — McMap. All rights reserved.