I am trying to make a "One to one" association using the Fluent API. Here are my classes :
public class Person
{
public Guid Id { get; set; }
public Guid ProfilId { get; set; }
public DateTime InsertDate { get; set; }
public DateTime UpdateDate { get; set; }
public virtual Profil Profil { get; set; }
}
public class Profil
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public String Email { get; set; }
public virtual Person Person { get; set; }
}
public class PersonMap : EntityTypeConfiguration<Person>
{
public PersonMap()
{
...
ToTable("Person");
HasRequired(t => t.Profil)
.WithOptional(c => c.Person)
.Map(m => m.MapKey("ProfilId"));
}
}
This implementation throws an exception Invalid column name 'ProfilId'.
Would somebody tell me how to set up a mapping with 1-1 relationship using these classes?
Thank you
When configuring one-to-one relationships,Entity Framework requires that the primary key of the dependent also be the foreign key, so you can map the relationship using Data Annotations as follow:
public class Person
{
[Key]
[ForeignKey("Profil")]
public Guid ProfilId { get; set; }
public DateTime InsertDate { get; set; }
public DateTime UpdateDate { get; set; }
public virtual Profil Profil { get; set; }
}
Or using Fluent Api:
HasKey(t=>t.ProfilId);
HasRequired(t => t.Profil).WithOptional(c => c.Person);
Edit 1:
Well, EF lets you create an one-to-one relationship between two entities with their own PKs, but you can't use a FK property, so, remove ProfileId in Person entity and configure the relationship this way:
HasRequired(t => t.Profil).WithOptional(c => c.Person);
The MapKey method is used to change the foreign key name in the database, but you can't have a property with the same name in your entity, otherwise, an exception will be thrown.
Related
I have the below domain classes:
public class Product
{
public Guid? Id { get; set; }
public string? Name { get; set; }
}
public class Order
{
public Guid? Id { get; set; }
public string? Address{ get; set; }
public Guid? ProductId{ get; set; }
}
I am not using navigation properties for setting the foreign key but am doing it as below:
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.HasOne<Product>()
.WithMany()
.HasForeignKey(c => c.ProductId);
}
My problem when I do the update-database, it says that the field ProductId does not exist. This is because it named as Id in table product but in table order it is named as ProductId. Is there a way to map the foreign key with a different name?
Modify the Product class
public class Product
{
public Guid? Id { get; set; }
public string? Name { get; set; }
public ICollection<Order> Orders {get; set;}
}
The relationship will work properly.
Reference: Relationships
The user(AskedUser) can have many questions asked by other users(Asker).
Users(Asker) can ask questions to other users(AskedUser).
So the QuestionModel should have foreign key to asked user id and foreign key to user who asked the question.
Do I constructed my models to what I want to achieve? How to configure this using fluent api cause this is not achievable using data annotations only.
public class ApplicationUser : IdentityUser<long>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string Country { get; set; }
public DateTime BirthDate { get; set; }
public ICollection<QuestionModel> AskedQuestions { get; set; }
}
public class QuestionModel
{
public int Id { get; set; }
public string Content { get; set; }
public bool IsAnswered { get; set; }
public long AskerId { get; set; }
public ApplicationUser Asker { get; set; }
public long AskedUserId { get; set; }
public ApplicationUser AskedUser { get; set; }
}
This is I've tried so far
builder.Entity<ApplicationUser>()
.HasMany(user => user.AskedQuestions)
.WithOne(q => q.AskedUser)
.HasForeignKey(user => user.AskedUserId)
.HasConstraintName("ForeignKey_User_AskedQuestion")
.HasForeignKey(user => user.AskerId)
.HasConstraintName("ForeignKey_Asker_QuestionAsked")
.IsRequired(true);
You can do it on QuestionModel
//Asker relation
builder.Entity<QuestionModel>()
.HasOne(q=> q.Asker)
.Withmany(u => u.AskedQuestions)
.HasForeignKey(q=> q.AskerId)
.HasConstraintName("ForeignKey_Asker_QuestionAsked")
.IsRequired(true);
//Asked relation
builder.Entity<QuestionModel>()
.HasOne(q=> q.AskedUser)
.Withmany()
.HasForeignKey(q=> q.AskeduserId)
.HasConstraintName("ForeignKey_User_AskedQuestion")
.IsRequired(true);
I use Fluent API on dependant model instead of the root element.
I have the following:
public class Event : IEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("date")]
public DateTimeOffset Date { get; set; }
[JsonProperty("distance")]
public int Distance { get; set; }
[JsonProperty("verticalAscend")]
public int VerticalAscend { get; set; }
[ForeignKey("User")]
[JsonProperty("userId")]
public Guid UserId { get; set; }
//attending
public DateTimeOffset DateCreated { get; set; }
public DateTimeOffset DateModified { get; set; }
[JsonProperty("user")]
public virtual User User { get; set; }
[JsonProperty("comments")]
public virtual ICollection<Comment> Comments { get; set; }
[JsonProperty("attending")]
public virtual ICollection<User> AttendingList { get; set; }
}
And:
public class User : IEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("profilePicUrl")]
public string ProfilePicUrl { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("surname")]
public string LastName { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
public DateTimeOffset DateCreated { get; set; }
public DateTimeOffset DateModified { get; set; }
public virtual ICollection<Event> AttendingEvents { get; set; }
[InverseProperty("User")]
public virtual ICollection<Event> Events { get; set; }
}
Relationships:
Event:
Many Users attending (AttendingList)
User:
Can attend many events (AttendingEvents)
Can create multiple events (Events)
There exists a many-many relationship between the Event.AttendingList and User.AttendingEvents.
There exists 0-many relationship between Event.User and User.Events, with ForeignKey as UserId.
I am trying to configure these with Fluent API, and using the InverseProperty to configure the other side of the relationship, mapping back to Event.User, but getting the following error:
Introducing FOREIGN KEY constraint 'FK_dbo.UserEvents_dbo.Events_Event_Id' on table 'UserEvents' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I am unsure on how to solve this relationship on one table. What am I doing wrong?
In the DbContext configure your models as follows:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Event>().HasRequired(e => e.User)
.WithMany(u => u.Events)
.HasForeignKey(e => e.UserId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<User>()
.HasMany<Event>(s => s.AttendingEvents)
.WithMany(c => c.AttendingList)
.Map(cs =>
{
cs.MapLeftKey("UserId");
cs.MapRightKey("EventId");
cs.ToTable("UserEvents");
});
}
In my regular .NET Framework application, I was using EF 6.x and was also using some Inheritance, specifically:
PurchaseOrder.cs and SaleOrder.cs both inherit from Order.cs
And in the OnModelCreating() on my context class inheriting from IdentityDbContext, I was doing:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
This used to work, but now I am moving my application to .NET Core 2.0 and I am using EF Core. What achieves the same thing in EF Core? Because right now I am getting the error:
System.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN KEY constraint 'FK_Order_Business_CustomerId' on table 'Order' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
UPDATE
Here's the code after Ahmar's answer. In my context class, I have:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.HasDefaultSchema("PD");
builder.Entity<Customer>()
.HasMany(c => c.SaleOrders)
.WithOne(e => e.Customer)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<Supplier>()
.HasMany(po => po.PurchaseOrders)
.WithOne(e => e.Supplier)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<PurchaseOrder>()
.HasMany(li => li.LineItems)
.WithOne(po => po.PurchaseOrder)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<SaleOrder>()
.HasMany(li => li.LineItems)
.WithOne(po => po.SaleOrder)
.OnDelete(DeleteBehavior.SetNull);
}
And as far the Entities, they are:
public abstract class Business : IEntity
{
protected Business()
{
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public string Name { get; set; }
public string TaxNumber { get; set; }
public string Description { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
public string Email { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public ICollection<Address> Addresses { get; set; } = new List<Address>();
public ICollection<Contact> Contacts { get; set; } = new List<Contact>();
}
[Table("Customers")]
public class Customer : Business
{
public decimal AllowedCredit { get; set; }
public decimal CreditUsed { get; set; }
public int NumberOfDaysAllowedToBeOnMaxedOutCredit { get; set; }
public ICollection<SaleOrder> SaleOrders { get; set; }
}
[Table("Suppliers")]
public class Supplier : Business
{
public ICollection<PurchaseOrder> PurchaseOrders { get; set; }
}
public abstract class Order : IEntity
{
protected Order()
{
Date = DateTime.UtcNow;
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public DateTime Date { get; set; }
public decimal ShippingCost { get; set; }
public Currency ShippingCurrency { get; set; }
public decimal ShippingConversionRate { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public ICollection<Invoice> Invoices { get; set; }
public ICollection<Note> Notes { get; set; }
}
[Table("PurchaseOrders")]
public class PurchaseOrder : Order
{
public int SupplierOrderNumber { get; set; }
public PurchaseOrderStatus Status { get; set; }
public decimal Vat { get; set; }
public decimal ImportDuty { get; set; }
public int SupplierId { get; set; }
public Supplier Supplier { get; set; }
public ICollection<PurchaseOrderLineItem> LineItems { get; set; }
}
[Table("SaleOrders")]
public class SaleOrder : Order
{
public decimal AmountToBePaidOnCredit { get; set; }
public SaleOrderStatus Status { get; set; }
public ICollection<SaleOrderLineItem> LineItems { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
So after doing what Ahmar suggested, I still get the same error when I do update-database.
You need to configure cascade delete behavior on each entity in .Net Core EF.
The Entity Framework Core Fluent API OnDelete method is used to specify the action which should take place on a dependent entity in a relationship when the principal is deleted.
The OnDelete method takes a DeleteBehavior enum as a parameter:
Cascade - dependents should be deleted
Restrict - dependents are
unaffected
SetNull - the foreign key values in dependent rows should
update to NULL
Example:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Employee> Employees { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int? CompanyId { get; set; }
public Company Company { get; set; }
}
protected override void OnModelCreating(Modelbuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.HasMany(c => c.Employees)
.WithOne(e => e.Company).
.OnDelete(DeleteBehavior.SetNull);
}
When deleting the Company, it will set CompanyId property in Employee table to null.
Get more detail at Configuring One To Many Relationships
PS. Please make sure your all referencing properties should be null able so, EF Core can set them null on delete. like CompanyId in about example.
This is asked many times, I know where the exact issue is but I am trying to avoid it.
Simplified POCO:
public class TaskEntity
{
public int TaskId { get; set; }
public int? AssignedToId { get; set; }
public virtual UserEntity AssignedTo { get; set; }
public int CreatedById { get; set; }
public virtual UserEntity CreatedBy { get; set; }
public int? ClosedById { get; set; }
public virtual UserEntity ClosedBy { get; set; }
}
public class UserEntity
{
public List<TaskEntity> TaskId { get; set; }
public int UserId { get; set; }
public string Name { get; set; }
}
Mappings:
public class TaskMap : EntityTypeConfiguration<TaskEntity>
{
public TaskMap()
{
ToTable("tTasks");
HasKey(x => x.TaskId);
HasRequired(x => x.CreatedBy).WithMany(x => x.TaskId).HasForeignKey(x => x.CreatedById).WillCascadeOnDelete(false);
HasOptional(x => x.ClosedBy).WithMany(x => x.TaskId).HasForeignKey(x => x.ClosedById).WillCascadeOnDelete(false);
HasOptional(x => x.AssignedTo).WithMany(x => x.TaskId).HasForeignKey(x => x.AssignedToId).WillCascadeOnDelete(false);
}
}
I have read that I should separate UserEntity in 3 different classes and make them inherit from TaskEntity, but this doesn't sounds right as It will be exactly the same user object for all these cases.
I am expecting to have table structure as follows:
tTasks
TaskId | [FK]AssignedToId | [FK]CreatedById | [FK]ClosedById
tUsers
UserId | Name
Could someone point what am I doing wrong here. Do I need to adjust my mapping somehow in order to get my table created as I expect
The answer is yes. You should adjust your mapping.
What you're doing wrong is in this line:
public List<TaskEntity> TaskId { get; set; }.
In EF you cannot get in the same navigation property all Tasks related to the UserEntity with different foreign keys. That means you need a navigation property in UserEntity to be mapped against every navigation property in TaskEntity. And as you have 3 navigation properties in every class you will need to specify which is against which.
You'll get this:
public class TaskEntity
{
public int TaskId { get; set; }
public int? AssignedToId { get; set; }
[InverseProperty("AssignedTasks")]
public virtual UserEntity AssignedTo { get; set; }
public int CreatedById { get; set; }
[InverseProperty("CreatedTasks")]
public virtual UserEntity CreatedBy { get; set; }
public int? ClosedById { get; set; }
[InverseProperty("ClosedTasks")]
public virtual UserEntity ClosedBy { get; set; }
}
public class UserEntity
{
public int UserId { get; set; }
public string Name { get; set; }
[InverseProperty("AssignedTo")]
public virtual ICollection<TaskEntity> AssignedTasks {get; set; }
[InverseProperty("CreatedBy")]
public virtual ICollection<TaskEntity> CreatedTasks {get; set; }
[InverseProperty("ClosedBy")]
public virtual ICollection<TaskEntity> ClosedTasks {get; set; }
}
With this all the mapping is done with the annotations and you can remove the TaskMap class.
You can add a List<TaskEntity> Tasks to your UserEntity that aggregates the results from the 3 previous navigation properties, but the aggregation will be done after the data is loaded and you cannot use it in Linq queries.