I have this class
public class Client
{
public int Id { get; set; }
public ICollection<Recipe> Recipes { get; set; }
public ICollection<Recipe> Favorites { get; set; }
}
that has 2 1 to many relationship with the recipe class
public class Recipe
{
public int Id { get; set; }
public int ClientId { get; set; }
public Client Client { get; set; }
}
How can I describe these two relationships? do I need an extra class (favorite)?
Thanks for the help.
EDIT:
I should've been more clear. Client.Recipes is the recipes the client actually owns:
modelBuilder.Entity<Client>()
.HasMany(c => c.Recipes)
.WithRequired(r => r.Client)
.HasForeignKey(r => r.ClientId);
the problem with Client.Favorites is that it doesn't own them thus Recipe.ClientId is invalid for this particular relationship. I need a relational table for this, ut do I need to express it in a class or can it be expressed in Fluent Api? If yes, how?
Sorry if I wasn't explicit at first.
You should be able to define them as a standard 1 to N relationship. Should be something similar to this
class MyContext : DbContext
{
public DbSet<Client> Clients { get; set; }
public DbSet<Recipe> Recipes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Client>()
.HasOne(p => p.Clients )
.WithMany(b => b.Recipes ) .HasForeignKey(x => x.ClientId );
modelBuilder.Entity<Client>()
.HasOne(p => p.Clients )
.WithMany(b => b.Favorites ) .HasForeignKey(x => x.ClientId );
}
}
Related
I am using ef core 3.0 code-first database. I have a table, Status, and I need to create a relationship to itself to list the possible "next status" List<Status> SubsequentStatuses. This is of course to systematically control the workflow of the object.
Using this at face value, it creates a one-to-many relationship and a new StatusId column in the table; however, I need to be able to set a status to be a "SubsequentStatus" to more than one Status.
For example, if there are 4 statuses:
New
In Work
Complete
Cancelled
I want to have the following
New
Subsequent Statuses
In Work
Cancelled
In Work
Subsequent Statuses
Complete
Cancelled
Complete
None
Cancelled
None
Notice that "Cancelled" is related to both "New" and "In Work"
Here are the classes and config that I have at this point:
public class EstimateStatus
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<EstimateStatusRel> SubsequentStatuses { get; set; }
}
public class EstimateStatusRel
{
public int EstimateStatusId { get; set; }
public EstimateStatus EstimateStatus { get; set; }
public int SubsequentStatusId { get; set; }
public EstimateStatus SubsequentStatus { get; set; }
}
public class SapphireContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EstimateStatusRel>().HasKey(x => new { x.EstimateStatusId, x.SubsequentStatusId });
modelBuilder.Entity<StatusRel>()
.HasOne(pt => pt.Status)
.WithMany(p => p.SubsequentStatuses)
.HasForeignKey(pt => pt.StatusId);
}
}
The issue this is creating, is that when Entity Framework is building the migration, it errors out about the multiple cascading delete action, but when I add the NoAction modifier to the modelBuilder fluent API, it still does not clear the error
It ended up being because I didn't specify an OnDelete action
This is my final config:
modelBuilder.Entity<EstimateStatusRel>()
.HasOne(pt => pt.Status)
.WithMany(p => p.SubsequentStatus)
.HasForeignKey(pt => pt.EstimateStatusId)
.OnDelete(DeleteBehavior.NoAction);
For self-reference in one-to-many relationships, you could try the below code:
public class EstimateStatus
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public EstimateStatus ParentStatuses { get; set; }
public virtual ICollection<EstimateStatus> SubsequentStatuses { get; set; }
}
public class TestDbContext:DbContext
{
public TestDbContext (DbContextOptions<TestDbContext> options):base(options)
{ }
public DbSet<EstimateStatus> EstimateStatuse { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EstimateStatus>()
.HasMany(e => e.SubsequentStatuses)
.WithOne(s => s.ParentStatuses)
.HasForeignKey(e => e.ParentId);
}
}
I have been getting error when I try to run add-migration script.
The error is:
Domain.DataAccessLayer.AllRoutines_Product: : Multiplicity conflicts with the referential constraint in Role 'AllRoutines_Product_Target' in relationship 'AllRoutines_Product'. Because all of the properties in the Dependent Role are non-nullable, multiplicity of the Principal Role must be '1'.
And I cannot figure what I am doing wrong. I have AllRoutines and Product entities. AllRoutines can have 0 or 1 Products. Here is my AllRoutines class (some code has been omitted for clarity):
public class AllRoutines
{
[Key]
public long Id { get; set; }
public string Name { get; set; }
public string Tags { get; set; }
public int? RoutineLevelId { get; set; }
public RoutineLevel RoutineLevel { get; set; }
public Guid? ProductId { get; set; }
public virtual Product Product { get; set; }
}
Here is FluetnApi mapping (again some code is omitted):
public void MapRoutine(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<RoutineLevel>().HasKey(r => r.RoutineLevelId);
modelBuilder.Entity<AllRoutines>()
.HasKey(r => r.Id)
.Property(r => r.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<AllRoutines>()
.HasOptional(r => r.RoutineLevel)
.WithMany()
.HasForeignKey(r => r.RoutineLevelId);
modelBuilder.Entity<AllRoutines>().HasOptional(c => c.Product)
.WithMany()
.HasForeignKey(c => c.ProductId)
.WillCascadeOnDelete(false);
}
Also I am not sure if this is important or not, but there is also class CustomRoutine which inherits AllRoutines and looks like this:
public class CustomRoutine : AllRoutines
{
public DateTime DateCreated { get; set; }
public string UserId { get; set; }
public User UserWhoCreatedRoutine { get; set; }
}
The inheritance approach was Table per Hierarchy.
I've tried to add to mapping configuration this:
modelBuilder.Entity<CustomRoutine>().HasOptional(c => c.Product)
.WithMany()
.HasForeignKey(c => c.ProductId)
.WillCascadeOnDelete(false);
But the error was same. I am not sure why this is happening, because, as you can see in the code same mapping was already done (without any problems) for RoutineLevel, also I have same mapping for Product and the other class, again with no problems.
EDIT
Here is also Product class:
public class Product
{
public Guid Id { get; protected set; }
public string Code { get; set; }
public string Name { get; set; }
public bool IsFree { get; set; }
public ICollection<SubscriptionProduct> SubscriptionProducts { get; set; }
}
And FluentAPI mapping:
modelBuilder.Entity<Product>()
.HasKey(p => p.Id);
modelBuilder.Entity<Product>()
.Property(p => p.Code)
.IsRequired()
.HasMaxLength(10);
modelBuilder.Entity<Product>()
.Property(p => p.Name)
.IsRequired()
.HasMaxLength(100);
modelBuilder.Entity<Product>()
.HasMany(p => p.SubscriptionProducts)
.WithRequired()
.HasForeignKey(p => p.ProductId)
.WillCascadeOnDelete(true);
Is there a way to map two entities to have one to one relationship optional on both sides using fluent API in Entity Framework 6?
Code example:
// Subscription (has FK OrderId)
this.HasOptional(t => t.Order)
.WithOptionalDependent(t => t.Subscription)
.HasForeignKey(d => d.OrderId); // does not compile
Context: why would I do this? I work in an existing system where there are payment orders to buy subscriptions. When a order get paid a subscription is created and associated whit it, meaning subscription is optional to order. Also, there are other ways to create subscriptions, meaning order is optional to subscription.
Usually in an one-to-one (or zero) relationship both entities shares the same PK and, in the dependent one, the PK is also specified as FK. Check this link for more info about this. But if you entities not share the same PK, then you can't add a FK property in the dependent entity. If you do that, EF will throw an exception related with the multiplicity saying that it must be *.
About the relationship's configuration, there is only one way to configure an one-to-one relationship with both sides as optional, which it is what you currently have using Fluent Api. This way you can also use the Map method to rename the FK column that EF create by convention in the dependent table by the name that you already have in the Subscription table in your DB.
Update
If you were not tied to an existing database, you could do something like this:
public class Subscription
{
public int SubscriptionId { get; set; }
public int? OrderId { get; set; }
public virtual Order Order { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public int SubscriptionId { get; set; }
public virtual Subscription Subscription { get; set; }
}
And the configuration would be this:
modelBuilder.Entity<Subscription>()
.HasOptional(s => s.Order)
.WithMany()
.HasForeignKey(s=>s.OrderId);
modelBuilder.Entity<>(Order)
.HasOptional(s => s.Subscription)
.WithMany()
.HasForeignKey(s=>s.SubscriptionId);
This way you can work with the OrderIdFK (and SubscriptionId too) like it was a one-to-one relationship. The problem here is you have to set and save both associations separately.
Kindly try this code in the database context class
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Subscription>()
.HasOptional(x => x.Order)
.WithOptionalPrincipal()
.Map(x => x.MapKey("SubscriptionId"));
modelBuilder.Entity<Order>()
.HasOptional(x => x.Subscription)
.WithOptionalPrincipal()
.Map(x => x.MapKey("OrderId"));
}
My test models are as follows
public class Order
{
[Key]
public int OrderId { get; set; }
public string Description { get; set; }
public virtual Subscription Subscription { get; set; }
}
public class Subscription
{
[Key]
public int SubscriptionId { get; set; }
public virtual Order Order { get; set; }
}
Edit:
I did a reverse engineering to database trying to reach the required code structure by using double 1 to many relation to work like you want. The generated code is like the following. However, It is bad idea to do so.
public partial class Order
{
public Order()
{
this.Subscriptions = new List<Subscription>();
}
public int OrderId { get; set; }
public string Description { get; set; }
public Nullable<int> SubscriptionId { get; set; }
public virtual Subscription Subscription { get; set; }
public virtual ICollection<Subscription> Subscriptions { get; set; }
}
public partial class Subscription
{
public Subscription()
{
this.Orders = new List<Order>();
}
public int SubscriptionId { get; set; }
public Nullable<int> OrderId { get; set; }
public virtual ICollection<Order> Orders { get; set; }
public virtual Order Order { get; set; }
}
public class OrderMap : EntityTypeConfiguration<Order>
{
public OrderMap()
{
// Primary Key
this.HasKey(t => t.OrderId);
// Properties
// Table & Column Mappings
this.ToTable("Orders");
this.Property(t => t.OrderId).HasColumnName("OrderId");
this.Property(t => t.Description).HasColumnName("Description");
this.Property(t => t.SubscriptionId).HasColumnName("SubscriptionId");
// Relationships
this.HasOptional(t => t.Subscription)
.WithMany(t => t.Orders)
.HasForeignKey(d => d.SubscriptionId);
}
}
public class SubscriptionMap : EntityTypeConfiguration<Subscription>
{
public SubscriptionMap()
{
// Primary Key
this.HasKey(t => t.SubscriptionId);
// Properties
// Table & Column Mappings
this.ToTable("Subscriptions");
this.Property(t => t.SubscriptionId).HasColumnName("SubscriptionId");
this.Property(t => t.OrderId).HasColumnName("OrderId");
// Relationships
this.HasOptional(t => t.Order)
.WithMany(t => t.Subscriptions)
.HasForeignKey(d => d.OrderId);
}
}
public partial class EFOrdersContextContext : DbContext
{
static EFOrdersContextContext()
{
Database.SetInitializer<EFOrdersContextContext>(null);
}
public EFOrdersContextContext()
: base("Name=EFOrdersContextContext")
{
}
public DbSet<Order> Orders { get; set; }
public DbSet<Subscription> Subscriptions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new OrderMap());
modelBuilder.Configurations.Add(new SubscriptionMap());
}
}
Let's say I have the following entities:
Box
Id
Crank crank // has one required relationship
Crank
Id // does not care about the box
What is the proper way to define BoxMap? Is this sufficient? Or do I need WithRequiredPrincipal (which I have no idea what that does):
HasKey(t => t.Id);
ToTable("Boxes")
Property(t=>t.Id).HasColumnName("Id")
Property(t=>t.CrankId).HasColumnName("Crank_Id")
HasRequired(t=>t.Crank)
NOTE: Any good resources on learning fluent api are welcome. Thanks.
public class Context : DbContext
{
public DbSet<Box> Boxes { get; set; }
public DbSet<Crank> Cranks { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Box>()
.HasRequired(m => m.Crank)
.WithOptional()
.Map(m => m.MapKey("Crank_Id"));
base.OnModelCreating(modelBuilder);
}
}
public class Box
{
public int Id { get; set; }
public Crank Crank { get; set; } // has one required relationship
}
public class Crank
{
public int Id { get; set; }
}
You don't need to specify this:
HasKey(t => t.Id);
ToTable("Boxes")
Property(t=>t.Id).HasColumnName("Id")
Property(t=>t.CrankId).HasColumnName("Crank_Id")
HasRequired(t=>t.Crank)
It will be detected by convention of EF.
I have a question about defining Foreign Key in EF Code First Fluent API.
I have a scenario like this:
Two class Person and Car. In my scenario Car can have assign Person or not (one or zero relationship).
Code:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public Person Person { get; set; }
public int? PPPPP { get; set; }
}
class TestContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Car> Cars { get; set; }
public TestContext(string connectionString) : base(connectionString)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasOptional(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PPPPP)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
}
In my sample I want to rename foreign key PersonId to PPPPP. In my mapping I say:
modelBuilder.Entity<Car>()
.HasOptional(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PPPPP)
.WillCascadeOnDelete(true);
But my relationship is one to zero and I'm afraid I do mistake using WithMany method, but EF generate database with proper mappings, and everything works well.
Please say if I'm wrong in my Fluent API code or it's good way to do like now is done.
Thanks for help.
I do not see a problem with the use of fluent API here. If you do not want the collection navigational property(ie: Cars) on the Person class you can use the argument less WithMany method.