Base solution for your next web application
Open Closed

Entity Framework Relationship 1 to 1 and child to many #5318


User avatar
0
drblakl created

Hello all!

I've posted this on stackoverflow related to entityframework but haven't gotten many responses, so I'm hoping one of you can enlighten me. Perhaps my strategy is all wrong or thinking is not quite in the right place. Might just be my lack of understanding of EntityFramework and how it works. I'm okay with using straight data annotations, or OnModelCreating(). I think I'm just missing a few pieces to ensure the chain is inserted properly, and goes from there.

For this example, we have a Student Entity which has a one to one relationship with ImmunizationRecord. ImmunizationRecord has a one to Many relationship to ShotRecord. A student can only have one ImmunizationRecord, but every ImmunizationRecord can have many ShotRecords.

Where I'm at now I currently get the following error on insert of Student.

abp InvalidOperationException: A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'Id'.

Here are my entity snippets:

Student

[Table("tblStudent")]   
    public class Student : FullAuditedEntity, IMustHaveTenant
    {
        public Student()
        {
            ImmunizationRecord = new ImmunizationRecord();
        }

        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public override int Id { get => base.Id; set => base.Id = value; }

        public int TenantId { get; set; }
 
        //...
        [ForeignKey(nameof(Id))]
        public virtual ImmunizationRecord ImmunizationRecord { get; set; }      
    }

ImmunizationRecord - Only one per student allowed

[Table("tblImmunizationRecord")]
    public class ImmunizationRecord : FullAuditedEntity, IMustHaveTenant
    {
        public ImmunizationRecord()
        {
            ShotRecords = new HashSet<ShotRecord>();
        }

        [Key, ForeignKey(nameof(ShotRecords))]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public override int Id { get => base.Id; set => base.Id = value; }

        public int TenantId { get; set; }

        public int StudentId { get; set; }

       //...

        public virtual Student Student { get; set; }
        public virtual ICollection<ShotRecord> ShotRecords { get; set; }
    }

ShotRecord - there can be many shot records for each ImmunizationRecord

[Table("tblShotRecord")]
    public class ShotRecord : FullAuditedEntity, IMustHaveTenant
    {
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public override int Id { get => base.Id; set => base.Id = value; }

        public int ImmunizationRecordId { get; set; }

        public int TenantId { get; set; }

        //...

       public virtual ImmunizationRecord ImmunizationRecord  {get; set; }
    }

My DbContext

modelBuilder.Entity<Student>()
                        .HasOptional(s => s.ImmunizationRecord)
                        .WithRequired(z => z.Student);

            modelBuilder.Entity<ShotRecord>().HasRequired(r => r.ImmunizationRecord).WithMany(i => i.ShotRecord).HasForeignKey(l => l.ImmunizationRecordId).WillCascadeOnDelete(false);

6 Answer(s)
  • User Avatar
    0
    hitaspdotnet created

    I have 2 questions,

    1. Why you used override for entity identity? Only for getting member's name? I think 80% possible it's your mistake in your case.
    2. If you config your entities by fluent api why you need DataAnnotation attributes? [https://www.learnentityframeworkcore.com/configuration/data-annotation-attributes])

    I can suggest you to read more about Entity Framework if is your target ORM

    [https://docs.microsoft.com/en-us/ef/core/]) [https://www.learnentityframeworkcore.com/])

  • User Avatar
    0
    hitaspdotnet created

    In fact, I think you're using ZERO MVC 5 + EF 6 version, If is true use this:

    [https://msdn.microsoft.com/en-us/library/aa937723(v=vs.113).aspx])

    [Table("tblStudent")]   
        public class Student : FullAuditedEntity<int>, IMustHaveTenant //by default identity type is int you can use without <int>
        {
            //...
            public virtual ImmunizationRecord ImmunizationRecord { get; set; }      
        }
    
    [Table("tblImmunizationRecord")]
        public class ImmunizationRecord : FullAuditedEntity<int>, IMustHaveTenant
        {
           //...
            [Required]
            public virtual Student Student { get; set; }
            public virtual ICollection<ShotRecord> ShotRecords { get; set; }
        }
    
    [Table("tblShotRecord")]
        public class ShotRecord : FullAuditedEntity<int>, IMustHaveTenant
        {
            //...
            [Required]
            public virtual ImmunizationRecord ImmunizationRecord  {get; set; }
            public int ImmunizationRecordId { get; set; }
        }
    
    modelBuilder.Entity<Student>()
                            .HasOptional(s => s.ImmunizationRecord)
                            .WithRequired(z => z.Student);
    
                modelBuilder.Entity<ShotRecord>().HasRequired(r => r.ImmunizationRecord).WithMany(i => i.ShotRecord).HasForeignKey(l => l.ImmunizationRecordId).WillCascadeOnDelete(false);
    
  • User Avatar
    0
    drblakl created

    Thank you so much! Worked like a charm!

    Through messing with my entities the database and entities were not in sync. Just removed them from dbcontext, and added them back as mentioned above and it works great! Thanks again!

    Is there a way to determine if the database is out of sync with the entities, for example if the database has been manually modified outside of the code?

  • User Avatar
    0
    alper created
    Support Team

    Thanks @csbeginner for the complete solution!

    Hi @drblakl, row-level security is an enhanced feature that you need to work away. there's no built-in function for that in AspNet Boilerplate. But basically you can calculate a hash with a salt including all fields and write it to a new field in the entity like SecurityStamp. When you read the record from database recalculate the hash and check if it equals to the SecurityStamp. If not equals, it means it's modified by other sources, throw exception or send notification emails to the admin. You may need to use database interceptors to calculate and validate hash. But again, this is an advanced topic which is out of the framework's business.

  • User Avatar
    0
    drblakl created

    Thanks for the response! I was actually referring more toward the database schema rather than the data if that makes sense. For example, if someone goes in and changes a column, or relationship on the database side, is there a way to detect that within entityframework codefirst to know that the database schema is out of sync with the code base?

  • User Avatar
    0
    ismcagdas created
    Support Team

    I haven't tried but this might help <a class="postlink" href="https://stackoverflow.com/questions/13089448/how-to-check-if-database-schema-matches-entity-framework-schema">https://stackoverflow.com/questions/130 ... ork-schema</a>