sqlentity-frameworkef-core-2.2ef-model-first

EFCore Model is invalid. Reference another class from it multiple ways seems to be the problem?


I have a workflow database and part of that is a WORKFLOW_INSTANCE that has many WORKFLOW_STEP_INSTANCE. It works fine until I add the line below.

What I am trying to say is there is a one to many but for a single WORKFLOW_INSTANCE I also want to know the specific starting workflow step and ending working flow step . Why the error? I guess I could just use the int and skip the class reference?

Unable to determine the relationship represented by navigation property 'WfInstance.WfInitialStepInstance' of type 'WfStepInstance'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

    public class WfInstance : Entity
{
    public string CreateBy { get; set; }
    public DateTime CreatedOn { get; set; }


    // n-1 relationship
    public int WfStatusTypeId { get; set; }
    public WfStatusType WfStatusType { get; set; }

    public int WfDefinitionId { get; set; }
    public WfDefinition WfDefinition { get; set; }

    public int WfActionTypeId { get; set; }
    public WfActionType WfActionType { get; set; }

    //doesnt work!!!!
    public int WfInitialStepInstanceId { get; set; }
    public WfStepInstance WfInitialStepInstance { get; set; }

    //doesnt work!!!!
    //public int WfCurrentStepInstanceId { get; set; }
    //public WfStepInstance WfCurrentStepInstance { get; set; }

    // 1-n relationship 
    public List<WfStepInstance> WfStepInstances { get; set; } = new List<WfStepInstance>();
}

public class WfStepInstance : Entity
{
    public string Inputs { get; set; }
    public string Outputs { get; set; }
    public string WaitData { get; set; }
    public string CreateBy { get; set; }
    public DateTime CreatedOn { get; set; }

    // 1-1 relationship
    public int? NextStepId { get; set; }
    public virtual WfStepInstance NextStep { get; set; }

    // n-1 relationship
    public int WfInstanceId { get; set; }
    public WfInstance WfInstance { get; set; }

    public int WfStepStatusTypeId { get; set; }
    public WfStepStatusType WfStepStatusType { get; set; }

    public int WfStepTypeId { get; set; }
    public WfStepType WfStepType { get; set; }
}

Solution

  • I believe the issue is that you need to tell EF what FK to use. In the case of a many to 1 (WfInitialStepInstance / WfCurrentStepInstance) EF uses the Type to infer the key name.

    This works because the type is WfActionType so convention points at a key named WfActionTypeId.

    public int WfActionTypeId { get; set; }
    public WfActionType WfActionType { get; set; }
    

    These don't work because convention can't infer the FK name, and you have two properties of type WfStepInstance.

    //doesnt work!!!!
    public int WfInitialStepInstanceId { get; set; }
    public WfStepInstance WfInitialStepInstance { get; set; }
    
    //doesnt work!!!!
    public int WfCurrentStepInstanceId { get; set; }
    public WfStepInstance WfCurrentStepInstance { get; set; }
    

    This should work..

    [ForeignKey("WfInitialStepInstance")]
    public int WfInitialStepInstanceId { get; set; }
    public WfStepInstance WfInitialStepInstance { get; set; }
    
    [ForeignKey("WfCurrentStepInstance")]
    public int WfCurrentStepInstanceId { get; set; }
    public WfStepInstance WfCurrentStepInstance { get; set; }
    

    I'm not a huge fan of convention over configuration as it somewhat breeds complacency where people can appreciate that it "just works" until it "just doesn't" and then are left up a creek without a paddle if they aren't up to speed on configuration. :)

    I recommend reading up on EF configuration via EntityTypeConfiguration<T> (EF6)/IEntityTypeConfiguration<T> (EFCore) and DbContext.OnModelCreating. This will help greatly in understanding how EF relationships are mapped and what you can do when convention doesn't quite cut it. I would also recommend using Shadow Properties (EFCore) or Map+MapKey (EF6) rather than declaring FK properties in entities. Mapped FKs present two sources of truth for a relationship in an entity, and can lead to bugs. With shadow properties your entities only declare the navigation properties. The FKs are hidden.