Complex relationship mappings in entity framework - c#

I am building a reservation system. I have users in roles('admin', 'client', 'employee', 'student').
Each reservation must be associated with a user of role client, it might be assigned to user of role employee and might also be assigned to user of role student.
So in my reservation class I have properties of type User and I have marked them with [ForeignKey("AnytypeId")] attribute to hint EF for relations.
I have seen code like this at http://blog.stevensanderson.com/2011/01/28/mvcscaffolding-one-to-many-relationships/
public class Reservation
{
public int ReservationID
{
get;
set;
}
[Required(ErrorMessage="Please provide a valid date")]
public DateTime ReservationDate
{
get;
set;
}
public DateTime ReservationEnd { get; set; }
public DateTime EntryDate
{
get;
set;
}
public DateTime UpdatedOn
{
get;
set;
}
public decimal Ammount
{
get;
set;
}
public decimal? Discount { get; set; }
[DataType(DataType.MultilineText)]
public string ServiceDetails { get; set; }
[DataType(DataType.MultilineText)]
public string Remarks { get; set; }
public string VoucherNumber { get; set; }
public int ServiceID
{
get;
set;
}
public Service Service
{
get;
set;
}
public string EmployeeId { get; set; }
[ForeignKey("EmployeeId")]
public User Employee { get; set; }
public string ClientId { get; set; }
[ForeignKey("ClientId")]
public User Client { get; set; }
public string StudentId { get; set; }
[ForeignKey("StudentId")]
public User Student { get; set; }
}
public class User
{
//[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
//public Guid UserId { get; set; }
[Key]
[Required(ErrorMessage = "User Name is required")]
[Display(Name = "User Name")]
[MaxLength(100)]
public string UserName { get; set; }
[Required]
[MaxLength(64)]
public byte[] PasswordHash { get; set; }
[Required]
[MaxLength(128)]
public byte[] PasswordSalt { get; set; }
[Required(ErrorMessage = "Email is required")]
[DataType(DataType.EmailAddress)]
[MaxLength(200)]
public string Email { get; set; }
[MaxLength(200)]
public string Comment { get; set; }
[Display(Name = "Approved?")]
public bool IsApproved { get; set; }
[Display(Name = "Crate Date")]
public DateTime DateCreated { get; set; }
[Display(Name = "Last Login Date")]
public DateTime? DateLastLogin { get; set; }
[Display(Name = "Last Activity Date")]
public DateTime? DateLastActivity { get; set; }
[Display(Name = "Last Password Change Date")]
public DateTime DateLastPasswordChange { get; set; }
public string address { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Phone { get; set; }
public bool? IsActive { get; set; }
public int? ClientTypeID { get; set; }
public virtual ClientType ClientType { get; set; }
public virtual ICollection<Role> Roles { get; set; }
public DateTime? PackageValidity { get; set; }
public virtual ICollection<Reservation> Reservations { get; set; }
}
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
this.HasMany(u => u.Roles)
.WithMany(r => r.Users)
.Map(m =>
{
m.ToTable("RoleMemberships");
m.MapLeftKey("Username");
m.MapRightKey("RoleName");
});
}
}
Now as I run my mvc3 EF code first app database created for me on the fly with following ERD and edmx model.
Now few problems that I am having:
1. When I am listing all the users of role clients their reservation property is showing always 0 even if their are reservations available in database.
2. If I am trying to delete a user of role client who have reservation in database I get following error.
The DELETE statement conflicted with the REFERENCE constraint "Reservation_Client". The conflict occurred in database "CRSDB", table "dbo.Reservations", column 'ClientId'.
The statement has been terminated.
I checked the realtions in ERD and edmx model their is no cascade delete applied to them. How can I instruct EF to delete all the reservations when deleting user of role client but not for users of role employee or student.

This code does the trick
public class Reservation
{
public int ReservationID
{
get;
set;
}
[Required(ErrorMessage="Please provide a valid date")]
public DateTime ReservationDate
{
get;
set;
}
public DateTime ReservationEnd { get; set; }
public DateTime EntryDate
{
get;
set;
}
public DateTime UpdatedOn
{
get;
set;
}
public decimal Ammount
{
get;
set;
}
public decimal? Discount { get; set; }
[DataType(DataType.MultilineText)]
public string ServiceDetails { get; set; }
[DataType(DataType.MultilineText)]
public string Remarks { get; set; }
public String PaymentMethod { get; set; }
public string VoucherNumber { get; set; }
public int ServiceID
{
get;
set;
}
public virtual Service Service
{
get;
set;
}
public string EmployeeID { get; set; }
[ForeignKey("EmployeeID")]
public virtual User Employee { get; set; }
public string ClientID { get; set; }
[ForeignKey("ClientID")]
public virtual User Client { get; set; }
public string StudentID { get; set; }
[ForeignKey("StudentID")]
public virtual User Student { get; set; }
}
public class ReservationMap : EntityTypeConfiguration<Reservation>
{
public ReservationMap()
{
this.HasOptional(r => r.Client).WithMany().WillCascadeOnDelete(true);
this.HasOptional(r => r.Employee).WithMany().WillCascadeOnDelete(false);
this.HasOptional(r=>r.Student).WithMany().WillCascadeOnDelete(false);
}
}

Related

How to avoid cycles when introducing FOREIGN KEY constraint in a table?

I'm using code-first approach with EF Core 6 to construct the database and its tables. The problem is that when I update the database, I get an error regarding cycles or multiple cascade paths. I searched the same questions but none of them could solve my problem.
My entity classes are:
public class User
{
[Key]
public int UserId { get; set; }
[Required]
[DataType(DataType.Text)]
public string Username { get; set; }
[Required]
[DataType(DataType.Text)]
public string Password { get; set; }
[Required]
[DataType(DataType.Text)]
public string UserRole { get; set; }
[Required]
public bool IsActive { get; set; }
[Required]
public bool CanAccessNotifications { get; set; }
[Required]
public bool CanAccessMessages { get; set; }
[Required]
public bool CanAccessFiles { get; set; }
[Required]
public bool CanAccessPmDatabase { get; set; }
[Required]
public bool CanMakeChangesToPmDatabase { get; set; }
[Required]
public bool IsLocked { get; set; }
[Required]
public bool CanLocked { get; set; }
//Navigation properties
public virtual CostCenter CostCenter { get; set; }
public virtual List<PmSchedule> PmSchedules { get; set; }
}
public class ServiceType
{
[Key]
public int ServiceTypeId { get; set; }
[Required]
[DataType(DataType.Text)]
public string Title { get; set; }
[Required]
public bool IsActive { get; set; } = true;
//Navigation properties
public virtual List<PmDataSet> PmDataSets { get; set; }
public virtual List<PmSchedule> PmSchedules { get; set; }
}
public class PmSchedule
{
[Key]
public long PmScheduleId { get; set; }
[Required]
public long PmNumber { get; set; }
[Required]
[DataType(DataType.DateTime)]
public DateTime ScheduledStartDate { get; set; }
[Required]
[DataType(DataType.DateTime)]
public DateTime ScheduledEndDate { get; set; }
[Required]
[DataType(DataType.Text)]
public string MainFileName { get; set; }
[DataType(DataType.Text)]
public string? UploadedFileName { get; set; }
[Required]
public int MainUploader { get; set; }
public int? Uploader { get; set; }
[DataType(DataType.DateTime)]
public DateTime? CompletionDate { get; set; }
[Required]
[DataType(DataType.Text)]
public string? Status { get; set; }
//Navigation properties
public virtual CostCenter CostCenter { get; set; }
public virtual ServiceType ServiceType { get; set; }
}
public class PmDataSet
{
[Key]
public long PmDataSetId { get; set; }
[Required]
public long PmNumber { get; set; }
[Required]
[DataType(DataType.DateTime)]
public DateTime ScheduledStartDate { get; set; }
[Required]
[DataType(DataType.DateTime)]
public DateTime ScheduledEndDate { get; set; }
[Required]
[DataType(DataType.Text)]
public string WorkOrderNumber { get; set; }
[Required]
[DataType(DataType.Text)]
public string Priority { get; set; }
[Required]
[DataType(DataType.Text)]
public string Activity { get; set; }
[DataType(DataType.DateTime)]
public DateTime? StartTime { get; set; }
[DataType(DataType.DateTime)]
public DateTime? EndTime { get; set; }
[Required]
public int ActualDuration { get; set; }
[Required]
public int StandardDuration { get; set; }
[Required]
[DataType(DataType.Text)]
public string Executor { get; set; }
[DataType(DataType.Text)]
public string? Expaination { get; set; }
//Navigation properties
public virtual ServiceType ServiceType { get; set; }
public virtual CostCenter CostCenter { get; set; }
public virtual Equipment Equipment { get; set; }
}
public class Equipment
{
[Key]
public long EquipmentId { get; set; }
[Required]
[DataType(DataType.Text)]
public string EquipmentCode { get; set; }
[Required]
[DataType(DataType.Text)]
public string EquipmentTitle { get; set; }
//Navigation properties
public virtual CostCenter CostCenter { get; set; }
public virtual List<PmDataSet> PmDataSets { get; set; }
}
public class CostCenter
{
[Key]
public int CostCenterId { get; set; }
[Required]
[DataType(DataType.Text)]
public string Title { get; set; }
[Required]
public bool IsActive { get; set; } = true;
//Navigation properties
public virtual List<PmDataSet>? PmDataSets { get; set; }
public virtual List<PmSchedule>? PmSchedules { get; set; }
public virtual List<Equipment>? Equipments { get; set; }
public virtual List<User>? Users { get; set; }
}
My context class:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
I get the following error when update the database:
Introducing FOREIGN KEY constraint
'FK_PmDataSets_Equipments_EquipmentId' on table 'PmDataSets' may cause
cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON
UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not
create constraint or index. See previous errors.
you need to add some extra config like this :
protected override void OnModelCreating(ModelBuilder mb)
{
var pmConfig = mb.Entity<PmDataSet>();
pmConfig.HasOne(n => n.Equipment).WithMany(n => n.PmDataSets).OnDelete(DeleteBehavior.NoAction);
}
the problem is your Equipment model or/and some others already has at least one cascade delete relation,
so if you add another cascade delete relation there will be a chain deletion if one of these entities got deleted.
so you need to tell ef that some/none of these relations are not cascading while deleting.
(A) <-- cascade --> (B)
(B) <-- cascade --> (C)
so there will be a logical cascade between A and C, that is not arbitrary.
with a little playing with that config you will solve your problem ;)
-------- update
to answer the commented question u can go by ef convictions:
public User MainUploader { get; set; }
[Required]
public int MainUploaderId { get; set; }
public User Uploader { get; set; }
public int? UploaderId { get; set; }
you also can use [ForeignKey("fkName")] annotation if you want to use another way of naming your properties,
or use fluent API ForeignKey(a=> a.something) method or its overlods.

How to effectively implement 1 to many relationship?

I have an EF model to is connected 1 to 1 for many tables but I am having trouble connecting a 1 to many table. Currently using EF6 on .Net 4.8. The error I am recieving is "Multiplicity is not valid in Role '' in relationship 'Login_Users'. Because the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be '1'"
The main table is
Users
public partial class Users
{
public Users()
{
this.Login = new HashSet<Login>();
this.Phone = new HashSet<Phone>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public System.DateTime BirthDate { get; set; }
public int LastFourSSN { get; set; }
public Nullable<int> AddressId { get; set; }
public virtual Address Address { get; set; }
public virtual ICollection<Login> Login { get; set; }
public virtual ICollection<Phone> Phone { get; set; }
}
One to one tables
public partial class Login
{
public Login()
{
this.Login_Track = new HashSet<Login_Track>();
this.Policy = new HashSet<Policy>();
}
public string Username { get; set; }
public string Password { get; set; }
[Key, ForeignKey("Users")]
public Nullable<int> UserId { get; set; }
public System.DateTime CreationDate { get; set; }
public Nullable<System.DateTime> LastLoginDate { get; set; }
public virtual Users Users { get; set; }
}
Address
public partial class Address
{
public Address()
{
this.Users = new HashSet<Users>();
}
[Key, ForeignKey("Users")]
public int Id { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string County { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public virtual ICollection<Users> Users { get; set; }
}
1 to many table
public partial class Phone
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Number { get; set; }
public string Type { get; set; }
[ForeignKey("Users")]
public Nullable<int> UserId { get; set; }
public virtual Users Users { get; set; }
}

How to use identity aspnetusers

I asked a question earlier but I don't think I asked it correctly.
in my asp.net mvc application I am using aspnetusers for my login and registration.
If I have another model for Employees containing their information, How do I connect these two models so when a user logs in, it grabs their information from the employee table to be used. I created the Employees Model with sql server and used ado.net entity data model to use it in visual studio.
Employee Model:
public partial class Employee
{
public int UserID { get; set; }
[Key]
public int EmployeeID { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
public System.DateTime StartDate { get; set; }
public int RoleID { get; set; }
public int ShiftID { get; set; }
public int AreaID { get; set; }
public int DisciplineID { get; set; }
public int SiteID { get; set; }
public int ALCategory { get; set; }
public int HoursTaken { get; set; }
public Nullable<int> AwardedLeave { get; set; }
public Nullable<int> TotalHoursThisYear { get; set; }
public int HoursCarriedForward { get; set; }
public Nullable<int> EntitlementRemainingThisYear { get; set; }
public string Comments { get; set; }
public int SickLeaveTaken { get; set; }
public Nullable<int> SickLeaveEntitlement { get; set; }
public Nullable<int> SickLeaveEntitlementRemaining { get; set; }
public int StudyLeaveEntitlement { get; set; }
public int StudyLeaveTaken { get; set; }
public Nullable<int> StudyLeaveRemaining { get; set; }
public int ExamLeaveTaken { get; set; }
public int ForceMajeure { get; set; }
public int BereavementLeaveTaken { get; set; }
public int MaternityLeaveTaken { get; set; }
public int ParentalLeaveTaken { get; set; }
public int AdoptionLeaveTaken { get; set; }
public string ManagerEmail { get; set; }
public string AreaManagerEmail { get; set; }
public virtual Area Area { get; set; }
public virtual Discipline Discipline { get; set; }
public virtual Shift Shift { get; set; }
public virtual Site Site { get; set; }
public virtual Employee Employee1 { get; set; }
public virtual Employee Employee2 { get; set; }
}
And the log-in model:
public class LoginViewModel
{
[Required]
[Display(Name = "Email")]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
So can I connect these two?
Does it matter that I use database first model?
Could I use a Foreign Key to connect by email?
I want to use it for example to filter a table of employees to only show the employees who have the same siteID for the user logged in.
So when a user logs in, I want them to only see employees with the same site as themselves on the employee HTML table.
After successful login, you will get Id of AspNetUser. Take foreign key reference of column Id from AspNetUser table in your Employee table. By doing this, you can achieve expected result. Simply one more thing you need to add in your Employee model as mentioned below:
public virtual ApplicationUser ApplicationUser { get; set; }

How to get data from 3 related entities using LINQ?

I have a student class, program class and session class. I want to select a student on the basis of his rollno, sessionName, programName and password.
program and session classes have one to many relationship with student class.
This is Session Class.
public class Session
{
[Key]
public int SessionID { get; set; }
[Required]
[MaxLength(30)]
public string SessionName { get; set; }
//Relationship ------- Navigational Properties -------------------------------
public virtual List<Student> Students { get; set; }
public virtual List<Teacher> Teachers { get; set; }
public virtual List<Subject> Subjects { get; set; }
public virtual List<Program> Programs { get; set; }
}
This is program class.
public class Program
{
[Key]
public int ProgramID { get; set; }
[Required]
[MaxLength(30)]
public string ProgramName { get; set; }
//Relationship ------- Navigational Properties -------------------------------
public virtual List<Student> Students { get; set; }
public virtual List<Teacher> Teachers { get; set; }
public virtual List<Subject> Subjects { get; set; }
}
And this is Student Class.
public class Student
{
[Key]
[Column(Order = 0)]
public int StudentID { get; set; }
[Required]
[MaxLength(30)]
public string FirstName { get; set; }
[Required]
[MaxLength(30)]
public string LastName { get; set; }
[Required]
[MaxLength(30)]
public string UserName { get; set; }
[Required]
[MaxLength(35)]
public string Email { get; set; }
[Required]
[MaxLength(30)]
public string Password { get; set; }
[Required]
[MaxLength(30)]
public string FatherName { get; set; }
[Required]
public DateTime DOB { get; set; }
[Required]
[MaxLength(15)]
public string CNIC { get; set; }
[Required]
public int RollNo { get; set; }
public bool Active { get; set; }
public bool Graduated { get; set; }
public bool Expelled { get; set; }
//Relationship ------- Navigational Properties -------------------------------
public virtual List<Teacher> Teachers { get; set; }
public virtual List<Subject> Subjects { get; set; }
public virtual List<StudentMessage> Messages { get; set; }
public Session Session { get; set; }
public Program Program { get; set; }
public Grade Grade { get; set; }
public Attendance Attendance { get; set; }
public StudentContact StudentContact { get; set; }
public StudentMessage StudentMessage { get; set; }
}
Now how do I select student's username who has given specific rollno, sessionName, programName, password using LINQ Query Syntax and using LINQ Method syntax?
In database, Student Table contains SessionID and ProgramID.
I know "Join" is used to extract data from multiple tables but i don't know how to use it in LINQ Syntax.
You can use following code:
var list = dbContext.Students
.Where(e=>e.RollNo == 10)
.Where(e=>e.Password == "password")
.Where(e=>e.Session.SessionName == "SessionName")
.Where(e=>e.Session.ProgramName == "ProgramName")
.Select(e=> e.UserName)
.ToList();

Unable to determine the principal end of an association - EF Code First

I have a class "User" on which I have added the following fields:
[Required]
public int CreatedBy_Id { get; set; }
[ForeignKey("CreatedBy_Id")]
public User CreatedBy { get; set; }
public int? UpdatedBy_Id { get; set; }
[ForeignKey("UpdatedBy_Id")]
public User UpdatedBy { get; set; }
When I try to push these fields into database with this command :
Add-Migration -Configuration DataModel.DevMigrations.Configuration TestAlterUser
I get this error:
Unable to determine the principal end of an association between the
types 'DataModel.Entities.User' and
'DataModel.Entities.User'.
The principal end of this association must be explicitly configured
using either the relationship fluent API or data annotations.
If I remove the UpdatedBy property (or CreatedBy), it works perfectly.
The complete user's class :
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Login { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
[MaxLength(50)]
public string FirstName { get; set; }
[Required]
public int CreatedBy_Id { get; set; }
[ForeignKey("CreatedBy_Id")]
public User CreatedBy { get; set; }
public int? UpdatedBy_Id { get; set; }
[ForeignKey("UpdatedBy_Id")]
public User UpdatedBy { get; set; }
}
Try to use InverseProperty attribute:
public class User
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Login { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
[MaxLength(50)]
public string FirstName { get; set; }
[Required]
public int CreatedBy_Id { get; set; }
[ForeignKey("CreatedBy_Id")]
public virtual User CreatedBy { get; set; }
public int? UpdatedBy_Id { get; set; }
[ForeignKey("UpdatedBy_Id")]
public virtual User UpdatedBy { get; set; }
//add these properties
[InverseProperty("CreatedBy")]
public virtual ICollection<User> WereCreated {get; set;}
[InverseProperty("UpdatedBy")]
public virtual ICollection<User> WereUpdated {get; set;}
}

Categories

Resources