Entity Framework Core 3.0 Many-to-Many for the same table - entity-framework

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);
}
}

Related

Correct way to use Many2Many in EF Core6?

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.

Self referencing loop detected for property with type

I have 3 instances, Author, EventInstance, and Comments. An author can write a comment. People (authors) can add comments to an EventInstance. Here's the model. I keep getting the following error.
public class Author
{
public Guid Id { get; set; }
//..
public List<EventInstance> Events { get; set; }
= new List<EventInstance>();
public ICollection<Comment> Comments { get; set; }
= new List<Comment>();
}
public class EventInstance
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; } //Author FK
//..
public Author Author { get; set; }
public ICollection<Comment> Comments { get; set; }
= new List<Comment>();
}
public class Comment
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; } //Author FK
public Guid EventId { get; set; }
public EventInstance Event { get; set; }
public Author Author { get; set; }
}
I've tried several ways to fix but nothing seems to be working. Even a simple query like:
var list = await db.Events
.Include(e => e.Author)
.ToListAsync();
Thank for helping.
EDIT
Here's my context class
public class EventContext : DbContext
{
public DbSet<EventInstance> Events { get; set; }
public DbSet<Comment> Comments { get; set; }
public DbSet<Author> Authors { get; set; }
public EventContext(DbContextOptions<EventContext> options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<EventInstance>()
.HasOne(e => e.Author)
.WithMany(a => a.Events)
.HasForeignKey(e => e.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<Comment>()
.HasOne(c => c.Event)
.WithMany(e => e.Comments)
.HasForeignKey(c => c.EventId)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<Comment>()
.HasOne(c => c.Author)
.WithMany(a => a.Comments)
.HasForeignKey(c => c.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
}
}
I'm using a Factory for add and run migrations
public class EventContextFactory : IDesignTimeDbContextFactory<EventContext>
{
public EventContext CreateDbContext(string[] args)
{
string connectionString =
Environment.GetEnvironmentVariable("SqlConnectionString");
var optionsBuilder = new DbContextOptionsBuilder<EventContext>();
optionsBuilder.UseSqlServer(connectionString);
return new EventContext(optionsBuilder.Options);
}
}
And this is the startup class
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
string connectionString = Environment.GetEnvironmentVariable("SqlConnectionString");
builder.Services.AddDbContext<EventContext>(
options => options.UseSqlServer(connectionString));
builder.Services.AddScoped<IEventService, EventService>();
}
}
I hope all the relevant pieces have been pasted here.
You should consider normalizing the structure to avoid the double referencing. For instance, if Authors have a collection of event instances and event instances have a list of comments then authors can get their comments through event instances.
public class Author
{
public Guid Id { get; set; }
//..
public virtual ICollection<EventInstance> Events { get; set; }
= new List<EventInstance>();
}
public class EventInstance
{
public Guid Id { get; set; }
[ForeignKey("Author")]
public Guid AuthorId { get; set; }
//..
public Author Author { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public Guid Id { get; set; }
[ForeignKey("Event")]
public Guid EventId { get; set; }
public EventInstance Event { get; set; }
}
Then to get the Comments for a particular Author:
var commentsForAuthor = context.Authors
.Where(x => x.Id == authorId)
.SelectMany(x => x.Events.SelectMany(e => e.Comments))
.ToList();
Alternatively, if Comments can be treated as a top-level entity (/w DbSet)
var commentsForAuthor = context.Comments
.Where(x => x.EventInstance.AuthorId == authorId)
.ToList();
Typically you would use Projection to get details about a comment, it's associated event, and author. (Using Select) This can navigate through the relational structure to get the appropriate details.
You likely can apply mapping expressions to remove the error with explicit mapping, but I'd recommend removing the denormalization. The trouble with the denormalization the references so that Author can have a collection of Comments is that there is no way to enforce that a Comment's Author reference matches the Author of the Comment's EventInstance. I.e. I can have an author ID 1, with EventInstance ID 1, and create a Comment with ID 101. Nothing stops me from changing that Comment's Author ID to 2. Now I have two sources of truth as to who the author is. Comment.Author (ID: 2) and Comment.EventInstance.Author. (ID: 1) Wherever possible, remove those duplicate reference paths.
EDIT: Mapping feedback
With mapping references and back, you will want to avoid double-mapping in the sense of mapping a relationship from both entities. Map relationships from one side. You could try:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EventInstance>()
.HasOne(x => x.Author)
.WithMany(x => x.Events)
.HasForeignKey(x => x.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<EventInstance>()
.HasMany(x => x.Comments)
.WithOne(x => x.Event)
.HasForeignKey(x => x.EventId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Author>()
.Ignore(a => a.AuthorName)
.HasMany(x => x.Comments)
.WithOne(x => x.Author)
.HasForeignKey(x => x.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
}
Edit #2: If Authors can create comments for another author's events, then I would suggest removing the Comments relationship from the Author side. Comments can be retrieved as a top-level entity or through the Events as a top level entity.
For instance:
public class Author
{
public Guid Id { get; set; }
//..
public virtual ICollection<EventInstance> Events { get; set; }
= new List<EventInstance>();
}
public class EventInstance
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; }
//..
public Author Author { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public Guid Id { get; set; }
public Guid EventId { get; set; }
public virtual EventInstance Event { get; set; }
public Guid AuthorId { get; set; }
public virtual Author Author { get; set; }
}
Then in mapping:
modelBuilder.Entity<Comment>()
.HasOne(x => x.Author)
.WithMany()
.HasForeignKey(x => x.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Comment>()
.HasOne(x => x.Event)
.WithMany(x => x.Comments)
.HasForeignKey(x => x.EventId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Author>()
.Ignore(a => a.AuthorName);
This switches around the mapping from the Event over to the Comment, but the Comment maintains a bi-directional reference to it's Event, but a single-direction reference to its Author. (HasMany()) This should satisfy EF's mapping. On a side note with the explicit relationship mapping using OnModelCreating or IEntityTypeConfiguration we don't need the [ForeignKey] attributes so I removed those.

Entity Framework Core: How to solve Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths

I'm using Entity Framework Core with Code First approach but recieve following error when updating the database:
Introducing FOREIGN KEY constraint 'FK_AnEventUsers_Users_UserId' on table 'AnEventUsers' 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.
My entities are these:
public class AnEvent
{
public int AnEventId { get; set; }
public DateTime Time { get; set; }
public Gender Gender { get; set; }
public int Duration { get; set; }
public Category Category { get; set; }
public int MinParticipants { get; set; }
public int MaxParticipants { get; set; }
public string Description { get; set; }
public Status EventStatus { get; set; }
public int MinAge { get; set; }
public int MaxAge { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
public ICollection<AnEventUser> AnEventUsers { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
public class User
{
public int UserId { get; set; }
public int Age { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Gender Gender { get; set; }
public double Rating { get; set; }
public ICollection<AnEventUser> AnEventUsers { get; set; }
}
public class AnEventUser
{
public int AnEventId { get; set; }
public AnEvent AnEvent { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
public class ApplicationDbContext:DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options):base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AnEventUser>()
.HasOne(u => u.User).WithMany(u => u.AnEventUsers).IsRequired().OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<AnEventUser>()
.HasKey(t => new { t.AnEventId, t.UserId });
modelBuilder.Entity<AnEventUser>()
.HasOne(pt => pt.AnEvent)
.WithMany(p => p.AnEventUsers)
.HasForeignKey(pt => pt.AnEventId);
modelBuilder.Entity<AnEventUser>()
.HasOne(eu => eu.User)
.WithMany(e => e.AnEventUsers)
.HasForeignKey(eu => eu.UserId);
}
public DbSet<AnEvent> Events { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<AnEventUser> AnEventUsers { get; set; }
}
The issue I thought was that if we delete a User the reference to the AnEvent will be deleted and also the reference to AnEventUser will also be deleted, since there is a reference to AnEventUser from AnEvent as well we get cascading paths. But I remove the delete cascade from User to AnEventUser with:
modelBuilder.Entity<AnEventUser>()
.HasOne(u => u.User).WithMany(u => u.AnEventUsers).IsRequired().OnDelete(DeleteBehavior.Restrict);
But the error doesn't get resolved, does anyone see what is wrong? Thanks!
In your sample code in OnModelCreating you have declared modelBuilder.Entity<AnEventUser>().HasOne(e => e.User)... twice: at start of method and at end.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AnEventUser>() // THIS IS FIRST
.HasOne(u => u.User).WithMany(u => u.AnEventUsers).IsRequired().OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<AnEventUser>()
.HasKey(t => new { t.AnEventId, t.UserId });
modelBuilder.Entity<AnEventUser>()
.HasOne(pt => pt.AnEvent)
.WithMany(p => p.AnEventUsers)
.HasForeignKey(pt => pt.AnEventId);
modelBuilder.Entity<AnEventUser>() // THIS IS SECOND.
.HasOne(eu => eu.User) // THIS LINES
.WithMany(e => e.AnEventUsers) // SHOULD BE
.HasForeignKey(eu => eu.UserId); // REMOVED
}
Second call overrides first. Remove it.
This is what I did from the answer of Dmitry,
and It worked for me.
Class:
public class EnviornmentControls
{
public int Id { get; set; }
...
public virtual Environment Environment { get; set; }
}
And it's Mapping
public EnviornmentControlsMap(EntityTypeBuilder<EnviornmentControls> entity)
{
entity.HasKey(m => m.Id);
entity.HasOne(m => m.Environment)
.WithMany(m => m.EnviornmentControls)
.HasForeignKey(m => m.EnvironmentID)
.OnDelete(DeleteBehavior.Restrict); // added OnDelete to avoid sercular reference
}
These solutions didn't work for my case, but I found a way. I am not quite sure yet if it is safe but there's just something that's happening with deleting. So I modified the generated Migration File instead of putting an override.
onDelete: ReferentialAction.Cascade
The reason I did this because all the overriding mentioned above is not working for me so I manually removed the code which relates to Cascading of Delete.
Just check which specific relation being mentioned at the error so you can go straightly.
Hope this will be able to help for some people who's having the same issue as mine.
public Guid? UsuarioId { get; set; }
builder.Entity<Orcamentacao>()
.HasOne(x => x.Usuario)
.WithMany(x => x.Orcamentacaos)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false)
.HasForeignKey(x => x.UsuarioId);

Create one to one relationship optional on both sides in EF6

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());
}
}

Entity Framework 5 using multiple relationships between two POCOs

I'm having issues applying multiple relationships (or possibly foreignkey) on two POCO objects. I've got the first relationship many-to-many working and when the database is created it creates the three tables (Projects, Users and ProjectsUsers) needed for the relationship.
Code so far:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? CompletionDate { get; set; }
public bool Deleted { get; set; }
public ICollection<User> Users { get; set; }
}
public class User
{
public User()
{
Name = new Name();
}
public int UserId { get; set; }
public string LoginId { get; set; }
public string Password { get; set; }
public Name Name { get; set; }
public ICollection<Project> ManagedProjects { get; set; }
}
public class ProjectConfiguration : EntityTypeConfiguration<Project>
{
public ProjectConfiguration()
{
HasMany(x => x.Users)
.WithMany(x => x.ManagedProjects);
}
}
public UserConfiguration()
{
HasMany(x => x.ManagedProjects)
.WithMany(x => x.Users);
}
Now I want to add an optional one-to-one relationship of Project.ManagingUser -> User. However, I can't seem to figure out how to indicate this in the configuration.
Code for what I think is needed:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? CompletionDate { get; set; }
public bool Deleted { get; set; }
public int? ManagingUserId { get; set; }
public User ManagingUser { get; set; }
public ICollection<User> Users { get; set; }
}
I don't think the User object needs to change.
This shows my last attempt on mapping the new relationship:
public ProjectConfiguration()
{
HasMany(p => p.Users)
.WithMany(u => u.Projects);
this.HasOptional(p => p.ManagingUser)
.WithOptionalDependent()
.Map(m=>m.MapKey("ManagingUserId"))
.WillCascadeOnDelete(false);
}
What is happening when the database is created, I now end up with only two tables (Projects and Users). And it looks like it is only trying to setup the one-to-one relationship.
Can someone tell me what I'm missing?
Richard I've not changed the UserConfiguration and below is the DbContext:
public class MyDbContext : DbContext
{
public MyDbContext() : base(Properties.Settings.Default.ConnectionString)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Project> Projects { get; set; }
}
You probably want WithMany instead of WithOptionalDependent - it's a one:many relationship, not a one:one.
HasOptional(p => p.ManagingUser)
.WithMany()
.HasForeignKey(m => m.ManagingUserId)
.WillCascadeOnDelete(false);
EDIT
I think you're missing the OnModelCreating override from the DbContext class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ProjectConfiguration());
modelBuilder.Configurations.Add(new UserConfiguration());
}