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());
}
}
Related
I have three entities as shown here:
public class Application
{
[Key]
public int ApplicationId { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User
{
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<Application> Applications { get; set; }
}
Join entity
public class UserApplication
{
public int UserId { get; set; }
public int ApplicationId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
[ForeignKey("ApplicationId")]
public Application Application { get; set; }
}
OnModelCreating section =>
modelBuilder.Entity<User>()
.HasMany(x => x.Applications)
.WithMany(x => x.Users)
.UsingEntity(ua => ua.ToTable("UserApplication"));
modelBuilder.Entity<UserApplication>()
.HasKey(a=> new { a.ApplicationId, a.UserId});
Running the code is causing an error
invalid object name => ApplicationUser.
Note - while OnModelCreating only entity with wrong name is there. DB Has table with name UserApplication
You are using mixture of explicit and implicit join entity. I'm afraid EF Core assumes 2 separate many-to-many relationships with 2 separate tables. Note that by convention the implicit join entity name is {Name1}{Name2} with names being in ascending order, which in your case is ApplicationUser.
What you need is to use the the generic overload of UsingEntity fluent API and pass the explicit join entity type as generic type argument. Also configure the join entity there instead of separately. e.g.
modelBuilder.Entity<User>()
.HasMany(x => x.Applications)
.WithMany(x => x.Users)
.UsingEntity<UserApplication>(
// ^^^
ua => ua.HasOne(e => e.Application).WithMany().HasForeignKey(e => e.ApplicationId),
ua => ua.HasOne(e => e.User).WithMany().HasForeignKey(e => e.UserId),
ua =>
{
ua.ToTable("UserApplication");
ua.HasKey(a => new { a.ApplicationId, a.UserId });
});
I am quite new to EF Core 6.0. We currently have a projet to upgrade, we cannot change the actual tables (use by another program) so we use Database fisrt approch.
So I need to add some Permission on user (the database are in french) We curently have an UsagerEW table (user table) and we add an Permission Table and an joint table PermissionUsagerEW for the Many2Many. After doing Scaffold-dbContect here is the result:
UsagerEW (primary key is Code_Int)
public partial class UsagerEW
{
public UsagerEW()
{
PermissionUsagerEW = new HashSet<PermissionUsagerEW>();
RefreshToken = new HashSet<RefreshToken>();
}
public string Code { get; set; }
public string Nom { get; set; }
public string Email { get; set; }
public string ModeLogin { get; set; }
public string PasswordTemp { get; set; }
public DateTime? PasswordTempExp { get; set; }
public int code_int { get; set; }
public virtual ICollection<PermissionUsagerEW> PermissionUsagerEW { get; set; }
}
Pemrssion and PermissionUsagerEW
public partial class Permission
{
public Permission()
{
PermissionUsagerEW = new HashSet<PermissionUsagerEW>();
}
public int id { get; set; }
public string code { get; set; }
public string description { get; set; }
public int? moduleId { get; set; }
public virtual Module module { get; set; }
public virtual ICollection<PermissionUsagerEW> PermissionUsagerEW { get; set; }
}
public partial class PermissionUsagerEW
{
public int id { get; set; }
public int permissionId { get; set; }
public int usagerCodeInt { get; set; }
public virtual Permission permission { get; set; }
public virtual UsagerEW usagerCodeIntNavigation { get; set; }
}
That compile and I can "navigate with include" from UsagerEW and get an list of PermissionUsagerEW for a specific UsagerEW.
Now like I am in EF COre 6.0 that supposed to support Many2Many
I add this nav propertie in the Permnission class
public virtual ICollection<UsagerEW> UsagerEW { get; set; }
and this in the UsagerEW class:
public virtual ICollection<Permission> Permission { get; set; }
But I got execution error either I just try to load some user wintout any include:
UsagerEW user = _EWContext.UsagerEW.Where(u=>u.Code == usagerId).SingleOrDefault();
System.InvalidOperationException: 'Cannot use table
'PermissionUsagerEW' for entity type 'PermissionUsagerEW
(Dictionary<string, object>)' since it is being used for entity type
'PermissionUsagerEW' and potentially other entity types, but there is
no linking relationship. Add a foreign key to 'PermissionUsagerEW
(Dictionary<string, object>)' on the primary key properties and
pointing to the primary key on another entity type mapped to
'PermissionUsagerEW'.'
The FK are detect by the scaffold:
modelBuilder.Entity<PermissionUsagerEW>(entity =>
{
entity.HasOne(d => d.permission)
.WithMany(p => p.PermissionUsagerEW)
.HasForeignKey(d => d.permissionId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_PermissionUsager_Permission");
entity.HasOne(d => d.usagerCodeIntNavigation)
.WithMany(p => p.PermissionUsagerEW)
.HasForeignKey(d => d.usagerCodeInt)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_PermissionUsager_Usager");
});
Any idea?
---EDIT 1
I change your code to reflect the scaffolded PermissionUsagerEW table:
//--UsagewrEW
modelBuilder.Entity<UsagerEW>()
.HasKey(u => u.code_int);
modelBuilder.Entity<UsagerEW>()
.HasMany(u => u.Permissions)
.WithMany(p => p.Users)
.UsingEntity<PermissionUsagerEW>(
p => p.HasOne(e => e.permission)
.WithMany()
.HasForeignKey(e => e.permissionId),
p => p.HasOne(p => p.usagerCodeIntNavigation)
.WithMany()
.HasForeignKey(e => e.usagerCodeInt)
);
modelBuilder.Entity<PermissionUsagerEW>()
.HasOne(p => p.usagerCodeIntNavigation)
.WithMany()
.HasForeignKey(p => p.usagerCodeInt);
When testing with
UsagerEW user = _EWContext.UsagerEW.Where(u=>u.Code == usagerId).Include(u => u.Permissions).SingleOrDefault();
Now I got this error:
Microsoft.Data.SqlClient.SqlException: 'Invalid column name
'UsagerEWcode_int'.'
I think EF tries to link something automatically. I do not have any UsagerEWcode_int in my solution.
EDIT2:
There is the SQL generated. Wierd column name and some repetition...
SELECT [u].[code_int], [u].[Administrateur], [u].[Code], [u].[Email], [u].[EmpContact], [u].[Inactif], [u].[KelvinConfig], [u].[LectureSeule], [u].[ModeLogin], [u].[Nom], [u].[ParamRole], [u].[Password], [u].[PasswordTemp], [u].[PasswordTempExp], [u].[RestreintCommContrat], [u].[RestreintProjet], [u].[Role], [u].[UsagerAD], [u].[doitChangerPW], [u].[estSuperviseur], [u].[idSuperviseur], [u].[infoSession], [u].[paramRole2], [u].[permsGrps], [t].[id], [t].[Permissionid], [t].[UsagerEWcode_int], [t].[permissionId0], [t].[usagerCodeInt], [t].[id0], [t].[code], [t].[description], [t].[moduleId]
FROM [UsagerEW] AS [u]
LEFT JOIN (
SELECT [p].[id], [p].[Permissionid], [p].[UsagerEWcode_int], [p].[permissionId] AS [permissionId0], [p].[usagerCodeInt], [p0].[id] AS [id0], [p0].[code], [p0].[description], [p0].[moduleId]
FROM [PermissionUsagerEW] AS [p]
INNER JOIN [Permission] AS [p0] ON [p].[permissionId] = [p0].[id]
) AS [t] ON [u].[code_int] = [t].[usagerCodeInt]
WHERE [u].[Code] = #__usagerId_0
ORDER BY [u].[code_int], [t].[id]
You can configure direct Many-to-Many relationships with an existing database, and you can have the linking entity in the model or exclude it. There are several examples in the docs. And you can leave the foreign key properties in the model, or you can replace them with shadow properties. But the Scaffolding code doesn't do any of this for you. It creates the simplest correct model for the database schema.
Also you usually should rename the entities and properties to align with .NET coding conventions.
Anyway something like this should work:
public partial class UsagerEW
{
public string Code { get; set; }
public string Nom { get; set; }
public string Email { get; set; }
public string ModeLogin { get; set; }
public string PasswordTemp { get; set; }
public DateTime? PasswordTempExp { get; set; }
public int code_int { get; set; }
public virtual ICollection<Permission> Permissions { get; } = new HashSet<Permission>();
}
public partial class Permission
{
public int Id { get; set; }
public string Code { get; set; }
public string Description { get; set; }
public int? ModuleId { get; set; }
//public virtual Module module { get; set; }
public virtual ICollection<UsagerEW> Users { get; } = new HashSet<UsagerEW>();
}
public partial class PermissionUsagerEW
{
public int Id { get; set; }
public int PermissionId { get; set; }
public int UsagerCodeInt { get; set; }
public virtual Permission Permission { get; set; }
public virtual UsagerEW User { get; set; }
}
public class Db : DbContext
{
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<UsagerEW>()
.HasKey(u => u.code_int);
builder.Entity<UsagerEW>()
.HasMany(u => u.Permissions)
.WithMany(p => p.Users)
.UsingEntity<PermissionUsagerEW>(
p => p.HasOne(e => e.Permission)
.WithMany()
.HasForeignKey(e => e.PermissionId),
p => p.HasOne(p => p.User)
.WithMany()
.HasForeignKey( e => e.UsagerCodeInt)
);
builder.Entity<PermissionUsagerEW>()
.HasOne(p => p.User)
.WithMany()
.HasForeignKey(p => p.UsagerCodeInt);
foreach (var prop in builder.Model.GetEntityTypes().SelectMany(e => e.GetProperties()))
{
prop.SetColumnName(char.ToLower(prop.Name[0]) + prop.Name.Substring(1));
}
base.OnModelCreating(builder);
}
But when you're working in a database-first workflow, there's a downside to deeply customizing the EF model: you loose the ability to regenerate the EF model from the database.
So you can use a "nice" customized EF model, or a "plain" scaffolded model. If you customize the model, you can no longer regenerate it, and need to alter it to match future database changes by hand.
You can apply some customizations, though, like the convention-based property-to-column and entity-to-table mappings in the example. But changing the generated "indirect many-to-many" to "direct many-to-many" will prevent you from regenerating the EF model through scaffolding.
I'm having trouble configurating my relationships in EF Core. I've been greeted with the following exception -
Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other
FOREIGN KEY constraints
I've trimmed back the entities for this post, but both of these entities have their own table.
public class ApplicationSetupTest
{
public Guid Id { get; set; }
public Guid SchemeId { get; set; }
public string Description { get; set; }
public Guid LatestVersionId { get; set; }
public ApplicationSetupVersionTest LatestVersion { get; set; }
public ICollection<ApplicationSetupVersionTest> VersionHistory { get; set; }
}
public class ApplicationSetupVersionTest
{
public Guid Id { get; set; }
public Guid SetupId { get; set; }
public string Data { get; set; }
public string AuditComment { get; set; }
public Guid PreviousVersionId { get; set; }
}
The ApplicationSetupTest class effectively defines static data with a LatestVersionId that is the key for navigation property LatestVersion.
The ApplicationSetupVersionTest class is the versioned/audited data. Each one of these has a SetupId to link it back to the ApplicationSetupTest to which is refers.
I added the VersionHistory property purely for this post to demonstrate that there could be multiple ApplicationSetupVersionTest on every ApplicationSetupTest. I haven't added an ApplicationSetupTest on the ApplicationSetupVersionTest as this isn't something I expect to need.
My configuration for ApplicationSetupTest is then as follows:
public class ApplicationSetupEntityConfiguration : IEntityTypeConfiguration<ApplicationSetupTest>
{
public void Configure(EntityTypeBuilder<ApplicationSetupTest> builder)
{
builder.Property(t => t.SchemeId).IsRequired();
builder.Property(t => t.Description).IsRequired();
builder.Property(t => t.LatestVersionId).IsRequired();
builder.HasMany(t => t.VersionHistory)
.WithOne()
.HasForeignKey(t => t.SetupId)
.IsRequired();
builder.HasOne(t => t.LatestVersion)
.WithOne()
.HasForeignKey<ApplicationSetupTest>(t => t.LatestVersionId)
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
builder.HasOne<Scheme>()
.WithMany()
.HasForeignKey(t => t.SchemeId)
.IsRequired();
}
}
The HasMany -> WithOne on VersionHistory is there to define that when I delete a setup, I should delete all version entities.
I assume the second configuration is therefore the area to change. The OnDelete(NoAction) was added following Google searches and I also tried removing the IsRequired() as well as making the LatestVersionId nullable.
I am looking to configure the second relationship so that the LatestVersion property can be included on query.
Any thoughts out there on how to configure such a relationship? Or am I doing something that you wouldn't recommend?
(I will refer to the models as Setup and Version for simplicity).
With your one-to-many configuration -
builder.HasMany(t => t.VersionHistory)
.WithOne()
.HasForeignKey(t => t.SetupId)
.IsRequired();
you have declared Setup as the principal end, and Version as the dependent end, which is correct.
But then you have a LatestVersionId foreign key in Setup, referencing to Version, and configuration of the one-to-one relationship -
builder.HasOne(t => t.LatestVersion)
.WithOne()
.HasForeignKey<ApplicationSetupTest>(t => t.LatestVersionId)
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
trying to configure Setup as the dependent end and Version as the principal end. I'm sure you can see the contradiction.
With the following simplified models -
public class Setup
{
public Guid Id { get; set; }
public string Description { get; set; }
public Version LatestVersion { get; set; }
public ICollection<Version> VersionHistory { get; set; }
}
public class Version
{
public Guid Id { get; set; }
public string Data { get; set; }
// not nullable - every Version must belong to a Setup
public Guid SetupIdHistory { get; set; }
// nullable - not every Version is a latest version
public Guid? SetupIdLatest { get; set; }
}
you can configure them correctly to represent your relationships as -
public void Configure(EntityTypeBuilder<Setup> builder)
{
builder.HasMany(p => p.VersionHistory)
.WithOne()
.HasForeignKey(p => p.SetupIdHistory)
.OnDelete(DeleteBehavior.Cascade) // not required, cascading is default
.IsRequired();
builder.HasOne(p => p.LatestVersion)
.WithOne()
.HasForeignKey<Version>(p => p.SetupIdLatest)
.OnDelete(DeleteBehavior.NoAction)
.IsRequired(false);
}
If you choose not to have a foreign key for the one-to-many relationship, EF will create a nullable one for you and manage the relationship at model level with a shadow property. But for the one-to-one relationship, you must define a foreign key.
public class Version
{
public Guid Id { get; set; }
public string Data { get; set; }
// nullable - not every Version is a latest version
public Guid? SetupId { get; set; }
}
public void Configure(EntityTypeBuilder<Setup> builder)
{
builder.HasMany(p => p.VersionHistory)
.WithOne()
.OnDelete(DeleteBehavior.Cascade)
.IsRequired(); // this will have no effect, the FK will be nullable
builder.HasOne(p => p.LatestVersion)
.WithOne()
.HasForeignKey<Model.Version>(p => p.SetupId)
.OnDelete(DeleteBehavior.NoAction)
.IsRequired(false);
}
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'm using Entity Framework Code First approach. I have these to classes. Of course, they have other fields but, they are not connected with the issue.
public class User
{
public int Id { get; set; }
public Role Role { get; set; }
public int HouseholdId { get; set; }
public virtual Household Household { get; set; }
}
public class Household
{
private ICollection<User> users;
public Household()
{
users = new HashSet<User>();
}
public int Id { get; set; }
public DateTime DateCreated { get; set; }
public virtual ICollection<User> Users
{
get { return users; }
set { users = value; }
}
}
I have the following settings applied in my OnModelCreating method:
modelBuilder.Entity<User>()
.HasRequired(u => u.Household)
.WithMany()
.WillCascadeOnDelete(false);
When I run the application, everything is correct except of the fact that the generated User table has one more foreign key added to its design:
User Table:
Id
Role
HouseholdId
**Household_Id**
Why is this happening?
Just remove this from your code:
modelBuilder.Entity<User>()
.HasRequired(u => u.Household)
.WithMany()
.WillCascadeOnDelete(false);
EDIT:
If you need WillCascadeOnDelete(false) and cannot remove this code try this:
modelBuilder.Entity<User>()
.HasRequired(u => u.Household)
.WithMany(t => t.Users)
.WillCascadeOnDelete(false);