EF Core 5 TPT and Identity (CRUD)

883 Views Asked by At

I want to use TPT Feature of EF Core and I want to integrate that with Identity but I have some problems with adding to database.

 public class ApplicationUser : IdentityUser<Guid>
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string NationalCode { get; set; }
        public DateTime BirthDate { get; set; }
        public Gender Gender { get; set; }
    }
 public class Student : ApplicationUser
    {
        public string FatherName { get; set; }
        public string PlaceOfBirth { get; set; }
        public string Address { get; set; }
        public string HomePhoneNumber { get; set; }
    }
 public class Teacher : ApplicationUser
    {
        public string FieldOfStudy { get; set; }
        public AcademicDegree AcademicDegree{ get; set; }
        public int YearsOfExperience { get; set; }
    }

and this is my database Context class

 public class SchoolMgmtContext : IdentityDbContext<ApplicationUser,ApplicationRole,Guid>
    {


        public SchoolMgmtContext(DbContextOptions<SchoolMgmtContext> dbContextOptions)
            :base(dbContextOptions)
        {

        }

        public DbSet<Teacher> Teachers { get; set; }
        public DbSet<Student> Students { get; set; }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.Entity<ApplicationUser>().ToTable("Users");
            builder.Entity<Student>().ToTable("Students");
            builder.Entity<Teacher>().ToTable("Teachers");

            builder.Entity<ApplicationRole>().ToTable("Roles");
        }
    }

All things are ok.

enter image description here

but I don't know how to insert a new teacher or a new student.

for example, this is the code for adding a new teacher.

    public IActionResult CreateTeacher()
        {

            ViewBag.Users = new SelectList(_db.Users.Select(u => new { u.Id, FullName = u.FirstName + " " + u.LastName }), "Id", "FullName");
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> CreateTeacher(TeacherCreationDto teacherCreationDto)
        {
            if (ModelState.IsValid)
            {
                var newTeacher = new Teacher()
                {
                    AcademicDegree = teacherCreationDto.AcademicDegree,
                    FieldOfStudy = teacherCreationDto.FieldOfStudy,
                    YearsOfExperience = teacherCreationDto.YearsOfExperience,
                    Id= teacherCreationDto.UserId
                };

                 // _db.Teachers.Update(newTeacher); // here I tested Attach - Add - Update, but none of this work.
                await _db.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            else
            {
                ViewBag.Users = new SelectList(_db.Users.Select(u => new { u.Id, FullName = u.FirstName + " " + u.LastName }), "Id", "FullName");
                return View(teacherCreationDto);
            }
         
        }

how should I add a new student or teacher? thanks,

UPDATE: enter image description here

2

There are 2 best solutions below

2
On

I agree with 'Ivan Stoev': You cannot inherit Student and Teacher from User. if want then your 'TeacherCreationDto' should contain the following fields/payload:

"FieldOfStudy,YearsOfExperience,FirstName,LastName,NationalCode,BirthDate,Gender,Id,UserName,NormalizedUserName,Email,NormalizedEmail,EmailConfirmed,PasswordHash,SecurityStamp,ConcurrencyStamp,PhoneNumber,PhoneNumberConfirmed,TwoFactorEnabled,LockoutEnd,LockoutEnabled,AccessFailedCount" ... The followig coding working for me when I create controller/api by usig auto scaffolder:

    public async Task<IActionResult> Create([Bind("FieldOfStudy,YearsOfExperience,FirstName,LastName,NationalCode,BirthDate,Gender,Id,UserName,NormalizedUserName,Email,NormalizedEmail,EmailConfirmed,PasswordHash,SecurityStamp,ConcurrencyStamp,PhoneNumber,PhoneNumberConfirmed,TwoFactorEnabled,LockoutEnd,LockoutEnabled,AccessFailedCount")] Teacher teacher)
    {
        if (ModelState.IsValid)
        {
            teacher.Id = Guid.NewGuid();
            _context.Add(teacher);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
        return View(teacher);
    }
0
On

There is not enough documentation in Microsoft official docs as the author of the question said.

Contrary to what we expect from TPT, when we want to insert a new record to a derived table we have to provide all properties of the derived type (including Parent type) because under the hood EF Core makes two insert statements, one for parent table and the other for derived table.

So if we provide existing id for making new records, DbUpdateException with message of 'duplicate key' is thrown.

I can say that this approach isn't appropriate when we have overlapping in our inheritance (consider a situation when a User is Both Student and Teacher or even wants to change from User to Student).

As a final word I recommend you to manually create TPT relations. Your models should be like this:

 public class ApplicationUser : IdentityUser<Guid>
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string NationalCode { get; set; }
        public DateTime BirthDate { get; set; }
        public Gender Gender { get; set; }
    }
    public class Student 
    {
        [ForignKey(nameof(User))]
        public Guid Id{ get; set; } // Id and forignKey 
        
        /* Student specific fields */

        public ApplicationUser User{ get; set; }
    }
    public class Teacher
    {
        [ForignKey(nameof(User))]
        public Guid Id{ get; set; } // Id and forignKey 

        /* Teacher specific fields */

        public ApplicationUser User{ get; set; }
    }