In an existing sqlite database I have 3 tables.
First a table with `events`, it uses a composite primary key of `id` and `licence_key`.
The Second table holds `codes`, this too uses a composite key of `session_code` and `licence_key`.
The final table is an associative table from the following sql:
CREATE TABLE `event_code` (
`event_id` INTEGER NOT NULL,
`session_code` TEXT NOT NULL,
`licence_key` TEXT NOT NULL,
CONSTRAINT `event_code$event_id_session_code_licence_key_PK` PRIMARY KEY(`event_id` ASC,`session_code` ASC,`licence_key` ASC),
CONSTRAINT `event_code$event_id_licence_key_FK` FOREIGN KEY(`event_id`, `licence_key`) REFERENCES `event`(`id`, `licence_key`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT `event_code$session_code_licence_key_FK` FOREIGN KEY(`session_code`, `licence_key`) REFERENCES `code`(`session_code`, `licence_key`) ON UPDATE CASCADE ON DELETE CASCADE
);
My program uses entity framework and fluent mapping to load and store objects in these tables.
In EF6 I believe the relevant part of the mapping should look like this:
modelBuilder.Entity<EF6EventInformation>().HasMany(eventInfo => eventInfo.InternalSessionCodes).WithMany().Map(mapping =>
{
mapping.ToTable("event_code");
mapping.MapLeftKey("event_id", "licence_key");
mapping.MapRightKey("session_code", "licence_key");
});
However, this throws an exception:
System.Data.Entity.ModelConfiguration.ModelValidationException: One or more validation errors were detected during model generation:
licence_key: Name: Each property name in a type must be unique. Property name 'licence_key' is already defined.
It seems I can not re-use the same column.
Of course I could change the database design and store the licence_key for both entities in their own separate columns, but because the value for each of those licence_keys would always have to match the other that does not seem particularly useful.
Is there any way to set this mapping up correctly without having to change my database design?
This seems to be a limitation of the EF6 mapping on many-to-many via implicit join table.
It's possible to map the relationship without changing the database structure, but changing the entity model by adding explicit join entity and mapping many-to-many as two many-to-one.
So you'd need an entity like this:
public class EventCode
{
public int event_id { get; set; }
public int session_code { get; set; }
public int license_key { get; set; }
public Event Event { get; set; }
public Code Code { get; set; }
}
then change the existing collection navigation property to something like this:
public ICollection<EventCode> EventCodes { get; set; }
and use fluent configuration like this:
modelBuilder.Entity<EventCode>()
.ToTable("event_code");
modelBuilder.Entity<EventCode>()
.HasKey(e => new { e.event_id, e.session_code, e.license_key });
modelBuilder.Entity<EventCode>()
.HasRequired(e => e.Event)
.WithMany(e => e.EventCodes)
.HasForeignKey(e => new { e.event_id, e.license_key });
modelBuilder.Entity<EventCode>()
.HasRequired(e => e.Code)
.WithMany()
.HasForeignKey(e => new { e.session_code, e.license_key });
Of course you can create better C# conventional property names
public class EventCode
{
public int EventId { get; set; }
public int SessionCode { get; set; }
public int LicenseKey { get; set; }
public Event Event { get; set; }
public Code Code { get; set; }
}
and map them to the existing table column names
but that doesn't change fundamentally the relationship mapping solution.
modelBuilder.Entity<EventCode>()
.ToTable("Event_Code");
modelBuilder.Entity<EventCode>()
.HasKey(e => new { e.EventId, e.SessionCode, e.LicenseKey });
modelBuilder.Entity<EventCode>().Property(e => e.EventId)
.HasColumnName("event_id");
modelBuilder.Entity<EventCode>().Property(e => e.SessionCode)
.HasColumnName("session_code");
modelBuilder.Entity<EventCode>().Property(e => e.LicenseKey)
.HasColumnName("license_key");
modelBuilder.Entity<EventCode>()
.HasRequired(e => e.Event)
.WithMany(e => e.EventCodes)
.HasForeignKey(e => new { e.EventId, e.LicenseKey });
modelBuilder.Entity<EventCode>()
.HasRequired(e => e.Code)
.WithMany()
.HasForeignKey(e => new { e.SessionCode, e.LicenseKey });
Both ways of many-to-many mapping have pros and cons, but here there is simply no choice.
Related
Odd issue that I've been looking at all day. I am working with Entity Framework 6. The issue I have is that I have three entities:
public partial class Order : ILocationBearingObject
{
public int Id { get; set; }
// other properties and relationships here
public int? OrderProfileId { get; set; }
public int OrderTemplateId { get; set; }
public virtual OrderProfile Profile { get; set; } // optional property
public virtual OrderTemplate OrderTemplate{ get; set; }
}
public class OrderProfile
{
public int Id { get; set; }
// other properties
// added here 6/15/2021
public virtual OrderTemplate OrderTemplate{ get; set; }
}
public class OrderTemplate : EntityMetaData
{
public int Id { get; set; }
// other properties
public int? OrderProfileId{ get; set; }
public OrderProfile OrderProfile { get; set; }
}
In our model builder, we have these definitions:
modelBuilder.Entity<Order>()
.HasOptional(x => x.OrderProfile)
.WithMany(x => x.Orders)
.HasForeignKey(x => x.OrderProfileId);
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile);
But even with the above fluent api model, we get the error
Invalid column name 'OrderProfile_Id'
Throughout various testing I was unable to find why this issue was occurring, so I looked at our logs and found when this error started popping it's head up and then was able to find the changes associated to OrderProfile and found that the only change that was made was adding the relationship from OrderProfile to OrderTemplate.
When I removed that fluent api relationship OrderProfile to OrderTemplate, it worked as expected... I don't need that relationship to OrderTemplate, but would like it to be there, how can I establish a optional 1 to optional 1 relationship without breaking other relationships? Also, why would additional relationships be effected by this?
UPDATE 6/15/2021
So I found I had a reverse navigation property in the OrderProfile model:
public virtual OrderTemplate OrderTemplate{ get; set; }
removing that and the associated fluent relationship
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile);
Doing the above resolved the issue, but for some reason, the issue seems to have cascaded down to another relationship that has a circular reference like the above. The Order class is involved with this cascaded issue. I guess this is a pretty big cause for concern since this application worked fine for the last 4 years and for these relationships to be decaying like this is worrisome. Does anyone know why this is happening?
if you use the right naming convention, EF will do magic. in this sample, you don't need fluent API to relate entities.
public partial class Order : ILocationBearingObject
{
public int Id { get; set; }
public int? OrderProfileId { get; set; } //means HasOptional (nullable) and ForeignKey
//variable name must be OrderProfile not Profile
public virtual OrderProfile OrderProfile { get; set; }
}
public class OrderProfile
{
public OrderProfile()
{
Orders = new HashSet<Order>();
}
public int Id { get; set; }
//be aware circular reference at any conversion or mapping
public virtual ICollection<Order> Orders {get; set;} //means WithMany
}
I've got an error like this too. It's caused by unmatching OrderProfileId property in OrderTemplate class with the fluent api model
If I'm not wrong, you want the OrderProfile model a many to many relation between Order and OrderTemplate. Then if it was the case, add the nvaigation property in OrderProfile.
public class OrderProfile
{
public int Id { get; set; }
// other properties
public virtual ICollection<Order> Orders { get; set; }
public virtual OrderTemplate OrderTemplate { get; set; }
}
Then change the fluent api model to be like this
// the EF has modelled the relation for normal 1 to many relation
// modelBuilder.Entity<Order>()
// .HasOptional(x => x.OrderProfile)
// .WithMany(x => x.Orders)
// .HasForeignKey(x => x.OrderProfileId);
modelBuilder.Entity<OrderTemplate>()
.HasOptional(x => x.OrderProfile)
.WithOptional(x => x.OrderTemplate);
You're working database-first, which always leaves room for a mismatch between the actual database model and the model EF infers from class and property names and mapping code (= conceptual model). If this happens, it may help to make EF generate a database from the conceptual model and see where it creates the column it expects, OrderProfile_Id.
This is what you'll see when logging the SQL statements:
CREATE TABLE [dbo].[OrderTemplates] (
[Id] [int] NOT NULL IDENTITY,
[OrderProfileId] [int],
[OrderProfile_Id] [int],
CONSTRAINT [PK_dbo.OrderTemplates] PRIMARY KEY ([Id])
)
...
ALTER TABLE [dbo].[OrderTemplates]
ADD CONSTRAINT [FK_dbo.OrderTemplates_dbo.OrderProfiles_OrderProfile_Id]
FOREIGN KEY ([OrderProfile_Id]) REFERENCES [dbo].[OrderProfiles] ([Id])
There you see the expected nullable column OrderProfile_Id which is the FK to OrderProfiles. It's noteworthy to see that EF does not use OrderProfileId as a foreign key field. It's just a field that could be used for anything.
That's because EF6 doesn't support 1:1 associations as foreign key associations (reference property and primitive FK property).
Knowing this, the remedy is simple: remove the property OrderTemplate.OrderProfileId and tell EF to use the field OrderTemplate.OrderProfileId in the database:
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile)
.Map(m => m.MapKey("OrderProfileId"));
That said, I wonder why Order has a foreign key to OrderProfile. Isn't its OrderProfile determined by its OrderTemplate? If it's a redundant relationship it may be better to remove it.
I am new in EF. And I ran into a problem with creation many-to-many self referencing relation.
I've tried to use solution from: Entity Framework Core: many-to-many relationship with same entity
my entities :
public class WordEntity
{
public long Id { get; set; }
public string Name { get; set; }
public string Json { get; set; }
public virtual List<WordSinonymEntity> Sinonyms { get; set; }
}
public class WordSinonymEntity
{
public long WordId { get; set; }
public virtual WordEntity Word { get; set; }
public long SinonymId { get; set; }
public virtual WordEntity Sinonym { get; set; }
}
and next configuration:
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Sinonym)
.WithMany(p => p.Sinonyms)
.HasForeignKey(pt => pt.SinonymId);
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Word)
.WithMany(t => t.Sinonyms)
.HasForeignKey(pt => pt.WordId);`
but it leads to next exception.
System.InvalidOperationException: 'Cannot create a relationship between 'WordEntity.Sinonyms' and 'WordSinonymEntity.Word', because there already is a relationship between 'WordEntity.Sinonyms' and 'WordSinonymEntity.Sinonym'. Navigation properties can only participate in a single relationship.'
Does anyone can help me or may be suggest some examples to learn ?
Thanks.
The post you are following is definitely wrong.
Every collection or reference navigation property can only be a part of a single relationship. While many to many relationship with explicit join entity is implemented with two one to many relationships. The join entity contains two reference navigation properties, but the main entity has only single collection navigation property, which has to be associated with one of them, but not with both.
One way to resolve the issue is to add a second collection navigation property:
public class WordEntity
{
public long Id { get; set; }
public string Name { get; set; }
public string Json { get; set; }
public virtual List<WordSinonymEntity> Sinonyms { get; set; }
public virtual List<WordSinonymEntity> SinonymOf { get; set; } // <--
}
and specify the associations via fluent API:
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Sinonym)
.WithMany(p => p.SinonymOf) // <--
.HasForeignKey(pt => pt.SinonymId)
.OnDelete(DeleteBehavior.Restrict); // see the note at the end
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Word)
.WithMany(t => t.Sinonyms)
.HasForeignKey(pt => pt.WordId);
Another way is to leave the model as is, but map the WordSinonymEntity.Sinonym to unidirectional association (with refeference navigation property and no corresponding collection navigation property):
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Sinonym)
.WithMany() // <--
.HasForeignKey(pt => pt.SinonymId)
.OnDelete(DeleteBehavior.Restrict); // see the note at the end
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Word)
.WithMany(t => t.Sinonyms)
.HasForeignKey(pt => pt.WordId);
Just make sure that WithMany exactly matches the presence/absence of the corresponding navigation property.
Note that in both cases you have to turn the delete cascade off for at least one of the relationships and manually delete the related join entities before deleting the main entity, because self referencing relationships always introduce possible cycles or multiple cascade path issue, preventing the usage of cascade delete.
Since Table Per Type isn't available in Core, I had to do a bit of a workaround to get my entities how I like them. Essentially I have a base class with its properties, and a navigation property to its parent:
public class Provision
{
public Guid ProvisionId { get; set; }
public string ProvisionName { get; set; }
public string ProvisionDescription { get; set; }
public Provision(){}
}
public class CompanyLeaveProvision
{
public Guid ProvisionId { get; set; }
public int CompanyId { get; set; }
public Provision Provision { get; set; }
public CompanyLeaveProvision() { }
}
Configurations:
public void Configure(EntityTypeBuilder<Provision> builder)
{
// Primary Key
builder.HasKey(t => t.ProvisionId);
// Properties
builder.Property(t => t.ProvisionName)
.IsRequired()
.HasMaxLength(40);
builder.Property(t => t.ProvisionDescription)
.HasMaxLength(500);
// Table & Column Mappings
builder.Property(t => t.ProvisionId).HasColumnName("ProvisionID");
builder.Property(t => t.ProvisionName).HasColumnName("ProvisionName");
builder.Property(t => t.ProvisionDescription).HasColumnName("ProvisionDescription");
builder.ToTable("Provision", "Organization");
}
public void Configure(EntityTypeBuilder<CompanyLeaveProvision> builder)
{
// Primary Key
builder.HasKey(t => t.ProvisionId);
// Properties
builder.Property(t => t.ProvisionId)
.IsRequired();
builder.Property(t => t.CompanyId)
.IsRequired();
// Table & Column Mappings
builder.ToTable("CompanyLeaveProvision", "Organization");
builder.Property(t => t.ProvisionId).HasColumnName("ProvisionID");
builder.Property(t => t.CompanyId).HasColumnName("CompanyID");
builder.HasOne(t => t.Provision).WithOne().HasForeignKey<Provision>(t => t.ProvisionId);
}
My context:
ProvisionContext: DbContext, IContext {
public DbSet<Provision> Provisions { get; set; }
public DbSet<CompanyLeaveProvision> CompanyLeaveProvisions { get; set;}
// OnModelCreating and other code below
}
I have a foreign key constraint on the the Organization.CompanyProvision table that references the ProvisionId property on the Organization.Provision table.
What is happening is the CompanyProvision is being inserted before the base Provision, resulting in this error:
The INSERT statement conflicted with the FOREIGN KEY constraint
"fk_CompanyLeaveProvision_Provision". The conflict occurred in
database "Dev", table "Organization.Provision", column 'ProvisionID'.
To attempt to save, here is the code I am calling:
_context.Entry(command.Provision.Provision).State = EntityState.Added;
_context.Entry(command.Provision).State = EntityState.Added;
await _context.SaveChangesAsync();
Aside from calling SaveChanges() after each _context.Entry(MyEntity).State = EntityState.Added, is there any way around this issue? I would prefer to have these save at once. I know a stored procedure is also an option, but I would prefer not to do that.
Thank you for your help!
It's because this fluent mapping
.HasForeignKey<Provision>(t => t.ProvisionId)
is telling EF Core that Provision is the dependent entity and has FK to the principal entity CompanyLeaveProvision, while the database model is the opposite.
So simply change Provision to CompanyLeaveProvision
.HasForeignKey<CompanyLeaveProvision>(t => t.ProvisionId)
I am having problems configuring a model to not cascade delete.
The modeL.
public class Hit
{
public int Id { get; set; }
// Foreign Key
public int AccountId { get; set; }
// Foreign Key
public int LeadId { get; set; }
//navigation properties
[ForeignKey("LeadId")]
public Lead lead { get; set; }
//navigation properties
[ForeignKey("AccountId")]
public Account account { get; set; }
}
Fluent API:
modelBuilder.Entity<Hit>()
.HasRequired(t => t.lead)
.WithMany()
.HasForeignKey(t => t.LeadId)
.WillCascadeOnDelete(false);
When I try to migrate it, i get this in create table on Hit:
.ForeignKey("dbo.Accounts", t => t.AccountId, cascadeDelete: true)
.ForeignKey("dbo.Leads", t => t.Lead_Id)
.ForeignKey("dbo.Leads", t => t.LeadId, cascadeDelete: true)
What I want to achieve for both leads and accounts is this in the migration script:
.ForeignKey("dbo.Leads", t => t.LeadId)
How should I go about doing this? Its prbably easy, but I am new to all this:(
EDIT 1:
I added this to the fluent API:
modelBuilder.Entity<Lead>().HasMany(i => i.Hits).WithOptional().HasForeignKey(s=> s.LeadId).WillCascadeOnDelete(false);
Which remove cascadelete from: .ForeignKey("dbo.Leads", t => t.LeadId)
However, I also want to remove it from:
.ForeignKey("dbo.Accounts", t => t.AccountId, cascadeDelete: true)
The easy solution would be to remove it from the migration script. But I would like how to do this with FLuent API or other methods.
Any ideas?
Like I already said in the comments above, if you use Fluent API for mapping your FKs remove the [ForeignKey] attributes from your navigation properties.
You can then do non-cascading mappings like this:
modelBuilder.Entity<Hit>().HasOptional(m => m.account).WithMany().HasForeignKey(m
=> m.AccountId).WillCascadeOnDelete(false);
Finally you have to check if your FK columns should be optional or required, for optional mappings change your FK properties AccountId and LeadId to data type int?, otherwise you have to change the HasOptional() part of your FK mappings to HasRequired().
Define your model like so:
public class Hit
{
public int Id { get; set; }
public virtual Lead Lead { get; set; }
public virtual Account Account { get; set; }
}
The virtual keyword will let Entity Framework know you are defining a foreign key and will match on Id automatically.
Remove the HasRequired(t => t.lead).WithMany() part from Fluent API. If a property is required it cannot exist without it and will cascade on delete.
What I've done so far to achieve what I want using Entity Framework is something like this:
// User.cs
public class User {
public Guid ID { get; set; } // column: user_id
public virtual ICollection<Event> Events { get; set; }
}
// Event.cs
public class Event {
public Guid ID { get; set; } // column: event_id
public virtual Guid UserID { get; set; } // column: event_userid
public virtual ICollection<User> Guests { get; set; }
}
// MyAppContext.cs
...
protected override void OnModelCreating(DbModelBuilder mb) {
mb.Entity<User>()
.HasKey(u => u.ID)
.HasMany(u => u.Events)
.WithOptional()
.HasForeignKey(e => e.UserID);
mb.Entity<Event>()
.HasKey(e => e.ID)
.HasMany(e => e.Guests)
.WithMany();
}
...
I was expecting the database structure to be as follows:
TABLE: user
user_id uniqueidentifier not null primary key
TABLE: event
event_id uniqueidentifier not null primary key
event_userid uniqueidentifier not null foreign key references user(user_id)
TABLE: event_guests
event_id uniqueidentifier not null
user_id uniqueidentifier not null
I have a feeling that the fluent API I'm using above is not going to give the expected database structure and also, I get the following exception that I've no clue how to fix:
Introducing FOREIGN KEY constraint 'FK_xxx' on table 'event_guests'
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. See previous errors.
I'm new to entity framework, any help would be much appreciated.
Try replacing your configurations with a single many to many configuration.
modelBuilder.Entity<User>()
.HasMany(a => a.Events)
.WithMany(b=> b.Guests)
.Map(x =>
{
x.MapLeftKey("UserId");
x.MapRightKey("EventId");
x.ToTable("EventGuests");
});