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.
Related
I'm creating a freelance task management app. Here is the logic. These are the 3 POCOs involved
User (LoggedIn User)
A user can create any number of clients
A user can create any number of projects
A user is created, updated by another user
Client (Freelance Client)
A client can have any number of projects
A client is created, updated by a user
Project
A project should have only one client
A project is created, updated by a user
These are my Entities. I'm using Code-First approach and uses SQLServer. I removed many properties for posting here
User Entity
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User UpdatedBy { get; set; }
[ForeignKey("UserId")]
public int? CreatedById { get; set; }
[ForeignKey("UserId")]
public int? UpdatedById { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public virtual ICollection<Client> Clients { get; set; }
}
Client Entity
public class Client
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
public string Company { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User UpdatedBy { get; set; }
[ForeignKey("UserId")]
public int? CreatedById { get; set; }
[ForeignKey("UserId")]
public int? UpdatedById { get; set; }
public ICollection<Project> Projects { get; set; }
}
Project Entity
public class Project
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string ProjectLogo { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User UpdatedBy { get; set; }
[ForeignKey("UserId")]
public int? CreatedById { get; set; }
[ForeignKey("UserId")]
public int? UpdatedById { get; set; }
public virtual Client Client { get; set; }
}
I'm now facing so many issues with ForginKeys and mappings. What is wrong with the entity mappings here?
I can't insert 2 rows in clients table with same CreatedById, EF is constraining it with unique values.
This is my fluent mappings
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//User
modelBuilder.Entity<User>().HasOne(a => a.CreatedBy).WithOne().OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<User>().HasOne(a => a.UpdatedBy).WithOne().OnDelete(DeleteBehavior.NoAction);
//Project
modelBuilder.Entity<Project>().HasOne(a => a.CreatedBy).WithOne().OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Project>().HasOne(a => a.UpdatedBy).WithOne().OnDelete(DeleteBehavior.NoAction);
//Client
modelBuilder.Entity<Client>().HasOne(a => a.CreatedBy).WithOne().OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Client>().HasOne(a => a.UpdatedBy).WithOne().OnDelete(DeleteBehavior.NoAction);
}
How can I implement this requirement?
This was a relationship issue with my entities. I made my properties look like
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User UpdatedBy { get; set; }
public int? CreatedById { get; set; }
public int? UpdatedById { get; set; }
And added fluent mapping
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//User
modelBuilder.Entity<User>().HasOne(a => a.CreatedBy).WithMany().OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<User>().HasOne(a => a.UpdatedBy).WithMany().OnDelete(DeleteBehavior.SetNull);
//Project
modelBuilder.Entity<Project>().HasOne(a => a.CreatedBy).WithMany().OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<Project>().HasOne(a => a.UpdatedBy).WithMany().OnDelete(DeleteBehavior.SetNull);
//Client
modelBuilder.Entity<Client>().HasOne(a => a.CreatedBy).WithMany().OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<Client>().HasOne(a => a.UpdatedBy).WithMany().OnDelete(DeleteBehavior.SetNull);
}
Adding One-Many relationship resolved my issue. Thanks to #David
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;}
}
I'm trying to create a Friendship mapping table that has 2 FK's that originate from the same class (User and SportsFacility)
Here are my model classes.
public partial class User
{
[Key]
public int Id { get; set; }
[Required]
public int AcountId { get; set; }
[Required]
[MaxLength(20)]
//[Column(TypeName = "varchar")]
public string Login { get; set; }
[Required]
[MaxLength(20)]
public string Name { get; set; }
[Required]
public string Password { get; set; }
public virtual ICollection<CheckQueue> ChecksQueue { get; set; }
public virtual ICollection<BookedEvent> BookedEvents { get; set; }
}
public partial class SportsFacility
{
public SportsFacility()
{
ChecksQueue = new List<CheckQueue>();
BookedEvent = new List<BookedEvent>();
}
[Key]
public int Id { get; set; }
[Required]
[MaxLength(30)]
public string Name { get; set; }
[Required]
public int PnH_Id { get; set; }
[Required]
[MaxLength(70)]
public string Address { get; set; }
public int City_Id { get; set; }
[ForeignKey("City_Id")]
public virtual City City { get; set; }
public virtual ICollection<CheckQueue> ChecksQueue { get; set; }
public virtual ICollection<BookedEvent> BookedEvent { get; set; }
}
public partial class CheckQueue
{
[Key]
public int Id { get; set; }
public DateTime StartCheckTime { get; set; }
[Required]
public string CheckParams { get; set; }
public string Error { get; set; }
public bool IsDeleted { get; set; }
public int User_Id { get; set; }
[ForeignKey("User_Id")]
public virtual User User { get; set; }
public int SportsFacility_Id { get; set; }
[ForeignKey("SportsFacility_Id")]
public virtual SportsFacility SportsFacility { get; set; }
[Index]
public DateTime? LastCheck { get; set; }
}
public partial class BookedEvent
{
[Key]
public int Id { get; set; }
public DateTime BookDate { get; set; }
[Required]
public string CheckParams { get; set; }
public string Error { get; set; }
public bool IsDeleted { get; set; }
public int User_Id { get; set; }
[ForeignKey("User_Id")]
public virtual User User { get; set; }
public int SportsFacility_Id { get; set; }
[ForeignKey("SportsFacility_Id")]
public virtual SportsFacility SportsFacility { get; set; }
}
I tried override the OnModelCreating method in the DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<City>();
modelBuilder.Entity<SportsFacility>();
modelBuilder.Entity<User>();
modelBuilder.Entity<CheckQueue>();
modelBuilder.Entity<BookedEvent>();
base.OnModelCreating(modelBuilder);
}
This causes a new error message to be displayed.
Additional information: The entity types 'BookedEvent' and 'CheckQueue' cannot share table 'CheckQueues' because they are not in the same type hierarchy or do not have a valid one to one foreign key relationship with matching primary keys between them.
Any help would be greatly appreciated.
In simple relational terms, I want each entry of ContractDetails to be assigned to either a Site OR a Company, not both at the same time, and one of them must be selected or there is no link at all. I'm not quite sure how to represent this in entity framework. My Model at Present:
Company Model:
public class Company
{
public int ID { get; set; }
public string Company_Name { get; set; }
public string Company_Prefix { get; set; }
public virtual ICollection<Site> Sites { get; set; }
}
Contract Details Model:
public class ContractDetails
{
public int ContractDetailsID { get; set; }
public int ContractTypeID { get; set; }
public int ContractRenewalPeriodID { get; set; }
public int? CompanyID { get; set; }
public int? SiteID { get; set; }
[Required, StringLength(10)]
public string Reference { get; set; }
[Display(Name = "Contract Start Date"), DataType(DataType.Date)]
public DateTime? Contract_Start_Date { get; set; }
[Display(Name = "Contract End Date"), DataType(DataType.Date)]
public DateTime? Contract_End_Date { get; set; }
[Column(TypeName = "text")]
public string Notes { get; set; }
public string Direct_Debit_Reference { get; set; }
public virtual Company Company { get; set; }
public virtual Site Site { get; set; }
public virtual ContractType ContractType { get; set; }
public virtual ContractRenewalPeriod ContractRenewalPeriod { get; set; }
}
Site Model:
public class Site
{
public int ID { get; set; }
public string Site_Name { get; set; }
public string Site_TelephoneNumber { get; set; }
public string Site_City { get; set; }
public int CompanyID { get; set; }
public virtual Company Company { get; set; }
}
Just implement the IValidatableObject on ContractDetails class. On Validate method put the validation logic. If the object is not valid you must return a collection of ValidationResult. when saving the object, EF will execute the Validate method and verify that your ContractDetails object is coherent.
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);
}
}