EF re-adding FK columns after adding ICollection navigation property - c#

I have the following classes, that already have their tables created and defined using EF migrations:
[Table("Account")]
public class AccountEntity
{
[Key]
public virtual int Id { get; set; }
}
[Table("Request")]
public class RequestEntity
{
[Key]
public virtual int Id { get; set; }
public int? AccountID { get; set; }
[ForeignKey("AccountID")]
public virtual AccountEntity Account { get; set; }
}
In the Request table, this properly created the FK FK_dbo.Request_dbo.Account_AccountID
Using SSMS, I can confirm the FK is setup properly.
In order to be able to access the Request's one-to-many property from the Account entity, I added the following property to the AccountEntity class:
public virtual ICollection<RequestEntity> Requests { get; set; }
However, now EF suspects that I need to run migrations due to domain changes.
Here's the migration class that it creates, and wants to run:
public partial class RequestUpdate : DbMigration
{
public override void Up()
{
AddColumn("dbo.Request", "AccountEntity_Id", c => c.Int());
CreateIndex("dbo.Request", "AccountEntity_Id");
AddForeignKey("dbo.Request", "AccountEntity_Id", "dbo.Account", "Id");
}
public override void Down()
{
DropForeignKey("dbo.Request", "AccountEntity_Id", "dbo.RealtorAccount");
DropIndex("dbo.Request", new[] { "AccountEntity_Id" });
DropColumn("dbo.Request", "AccountEntity_Id");
}
}
As you can see, EF seems to not recongize/respect that the FK relationshp has already been setup.
I don't suspect that any migrations need to be setup. The FK is already established, and I'm simply adding the collection "navigation" property.
Migrations need to be enabled for this project. EF version is 6, .NET 4.5.

One possible way this can happen is if you have used fluent configuration like this:
modelBuilder.Entity<RequestEntity>()
.HasOptional(e => e.Account)
.WithMany();
and forgot to update the .WithMany() to .WithMany(e => e.Requests) after introducing the collection navigation property, in which case EF considers two one-to-many relationships, hence adds a second FK column with default name.

Related

Purpose of OnModelCreating - EF Core Database first Approach

I am studying EF Core with database first. There is no issue to get entities and DbContext after reverse-engineering. But I couldn't understand the role(or purpose) OnModelCreating Method in DbContext(database first approach).
Here is code snippet.
public partial class VitiLevuContext : DbContext
{
public virtual DbSet<Order> Orders { get; set; }
public virtual DbSet<Invoice> Invoices { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Invoice>(entity =>
{
entity.ToTable("Invoice");
entity.Property(e => e.DueAmount)
.IsRequired();
entity.Property(e => e.PaidAmount).HasColumnType("money");
entity.HasOne(d => d.Order)
.WithMany(p => p.Invoices)
.OnDelete(DeleteBehavior.Cascade)
.HasForeignKey(d => d.OrderId)
.HasConstraintName("FK__Invoice__OrderId__44FF419A");
});
modelBuilder.Entity<Order>(entity =>
{
entity.ToTable("Order");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
Database has a relation and "NOT NULL Contraints".
ALTER TABLE [dbo].[Invoice] ADD FOREIGN KEY ([OrderId]) REFERENCES [dbo].Order ON DELETE CASCADE.
ALTER TABLE [dbo].[Invoice] ADD [DueAmount] int NOT NULL
The OnModelCreating method represents well. I created very simple Rest API project and tested add/delete for Order/Invoice.
"NOT NULL Constraints" and "Cascade deleting" may be verified on database not EF model side.
(In case of creating an invoice instance with null DueAmount, I expected exceptions before submitting to SQL)
My question is very simple.
Can I delete "OnModelCreating" method if don't consider migration?
(I thought the OnModelCreating method is only for migration purpose.)
If you follow the Entity framework model naming convention and your model directly reflects your database table name, column names and so on, you don't need the OnMOdelCreating method. This is because the entity framework will generate the binding behind the scene.
But, if you want customization, for example, your model field name does not match your database table column name, you configure that on the OnModelCreating method. Another way of using this configuration is called fluent API.
This doesn't mean you have to use the OnModelCreating method. There are other options for customization. Which is DataAnotation.
For example:
If you have a model named User...
public class User
{
public int Id { get; set; }
public string FullName { get; set; }
public string Password { get; set; }
}
on your DbContext, you set the following
public AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) {}
public DbSet<User> Users { get; set; }
}
So, by convention, the Entity framework expects
A table named Users, because of the name you used on the DbSet property for the User model.
It uses Id as the primary key.. because the model property name Id
Entity framework will set this all up for you.
When we come to the custom configuration, let's say your model property name Password is not the same as the Users table column name Pwd. You have to tell the entity framework in one of the following ways.
using the OnModelCreating method (fluent API)
protected override void OnModelCreating(ModelBuilder modelBuild)
{
modelBuilder.Entity<User>(entity => {
entity.Property(p => p.Password)
.HasColumnName("Pwd");
})
}
The other way is Data annotation
public class User
{
public int Id { get; set; }
public string FullName { get; set; }
[Column("Pwd")]
public string Password { get; set; }
}

Entity Framework : Invalid column name *_ID even with fluent api or data annotations

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.

How do I configure Entity Framework cascade delete to work properly with a one to many relationship?

After researching cascade deletes and browsing issues here, I'm under the impression that the following scenario will work
Entity with many:
public partial class master
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public master()
{
analysis = new HashSet<analysis>();
}
[Key]
public int id { get; set; }
[StringLength(50)]
public string description { get; set; }
public virtual ICollection<analysis> analysis { get; set; }
}
Entity with one:
public partial class analysis
{
[Key]
public int id { get; set; }
[StringLength(50)]
public string description { get; set; }
public int? master_id { get; set; }
public virtual master master { get; set; }
}
The foreign key on analysis entity is nullable, so that cascade deletion will automagically set the FK to null when it tries to delete the master record.
That configuration is in my Context:
public class Context : DbContext
{
public Context() : base("Context")
{
this.Configuration.LazyLoadingEnabled = true;
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context>());
}
public virtual DbSet<master> master { get; set; }
public virtual DbSet<analysis> analysis { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<master>()
.HasMany(e => e.analysis)
.WithOptional(e => e.master)
.HasForeignKey(e => e.master_id)
.WillCascadeOnDelete(true);
}
}
Then when I attempt to delete a master record by passing the id into the following (generic repo snippet, this works fine when deleting a master record with no analyses):
public void Delete(int id)
{
var entity = FindByKey(id);
_dbSet.Remove(entity);
_context.SaveChanges();
}
I get an error
Cannot delete or update a parent row: a foreign key constraint fails
on this record. What am I missing here?
The use of CascaseOnDelete should mirror cascade rules on the database. With CodeFirst when you set a relationship as Required, EF maps this both in the mapping and the schema as a delete cascade. With Optional relationships this isn't the case, it assumes deleting a parent will leave the child orphaned since the child accepts an existence without a parent due to it's null-able FK. However, without knowledge of the child records it cannot clear the ID out of the FK or delete the data so it relies on whatever the DB is set up to do.
You can still use .WillCascadeOnDelete(), though for it to work, the context needs to know about the children. For instance, on a new context:
var master = _db.Masters.Find(id);
_db.Masters.Remove(master);
This typically fails with a FK constraint error. (SQL Server) The error message differs from yours so I suspect like Tetsuya that is a different provider?
To resolve this you can use:
var master = _db.Masters.Include(x=>x.analysis).Find(id);
_db.Masters.Remove(master);
though that can be tedious for objects with larger graphs to include all children.
As a general rule if you want to use cascade deletes, you need to ensure that the database schema is set up with a cascade on delete behaviour. With a null-able FK and a cascade delete rule the original code should behave as expected. Alternatively, Setting a cascade rule to "Set to Null" will leave the child records orphaned with a null FK. (no error from EF)

ASP.NET MVC5 - unwanted field created after update database

I'm trying to create a 'one to many' relation between the classes 'ApplicationUser' and one recently created called 'Issue'.
So, in Models / IdentityModels.cs / ApplicationUser i added this property:
public ICollection<Issue> Issues { get; set; }
And Issue.cs has this code:
namespace Test.Models
{
public class Issue
{
public int Id { get; set; }
public ApplicationUser Courier { get; set; }
public ApplicationUser Customer { get; set; }
}
}
I'm using automatic migrations. So, after building and running 'update-database', the Issues table was created with these fields:
Id
ApplicationUser_Id
CourierId
CustomerId
My question is why was the field 'ApplicationUser_Id ' created and how can i prevent it?
The problem is that EF thinks you actually want three one-to-many relationships between Issue and ApplicationUser:
one for ICollection<Issue> Issues on ApplicationUser (ApplicationUser_Id)
one for ApplicationUser Courier on Issue (CourierId)
one for ApplicationUser Customer on Issue (CustomerId)
(Note that EF allows to define relationships from either side.)
If you want that ApplicationUser.Issues contains all Issues of this User, whether he is a Courier or Customer, you will need the additional ApplicationUser_Id key. Configuring EF so that this works will be quite a pain.
Maybe a simpler solution will do: introduce two collections on ApplicationUser.
public ICollection<Issue> CourierIssues { get; set; }
public ICollection<Issue> CustomerIssues { get; set; }
And then configure the backlinks in the ModelBuilder using the fluent API to eliminate the ApplicationUser_Id key:
modelBuilder.Entity<ApplicationUser>().HasMany(au => au.CourierIssues).WithOptional(i => i.Courier);
modelBuilder.Entity<ApplicationUser>().HasMany(au => au.CustomerIssues).WithOptional(i => i.Customer);
Because you have two foreign keys to ApplicationUser, Courier and Customer, but only one collection referencing Issue on ApplicationUser. EF has no way of know which foreign key it should line up with, so it just created a new one. To handle this properly, you need to utilize fluent config:
public class ApplicationUser
{
...
public class Mapping : EntityTypeConfiguration<ApplicationUser>
{
HasMany(m => m.Issues).WithRequired(m => m.Customer);
}
}
Then, in your context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ApplicationUser.Mapping());
}
The problem here of course, is that you are likely wanting to track collections for both the Customer and Courier collections. For that, you need two collections:
public virtual ICollection<Issue> CustomerIssues { get; set; }
public virtual ICollection<Issue> CourierIssues { get; set; }
Then, the following fluent config:
HasMany(m => m.CustomerIssues).WithRequired(m => m.Customer);
HasMany(m => m.CourierIssues).WithRequired(m => m.Courier);

Cannot define EF 1 to 0..1 relationship properly

I am unable to defined 1:0..1 relationship in Entity Framework.
I have "PerfData" and "AttachmentData" entity.
Perf is parent entity and it has zero or one Attachment.
In code AttachmentData.CustomForeignKeyId is FK to PerfData.Id, but in database FK should have different name Attachment.PerfId is FK to PerfData.Id (this is due to some base class and inheritance that I am not describing here).
public class AttachmentData {
...
public Guid Id {get;set;}
public Guid CustomForeignKeyId{ get; set; } // this is FK to Perf.Id
public PerfData Perf { get; set; } // navigation property
}
and corresponding Configuration is
internal class AttachmentDataConfig : BaseConfig<AttachmentData>
{
public AttachmentDataConfig () : base("Attachment")
{
Property(x => x.CustomForeignKeyId)
.HasColumnName("PerfId");
HasRequired(o => o.Perf)
.WithMany()
.HasForeignKey(f => f.CustomForeignKeyId)
.WillCascadeOnDelete(true);
}
}
If I have only this, it works fine. Migration is generated properly. However, I also need to have navigation property on other side:
public class PerfData {
public Guid Id {get;set;}
public AttachmentData> Attachment { get; set; } //navigation property
}
If I add this navigation property, then EF creates new migration with new column Perf.Attachment_Id that is unnecessary, as FK is already defined within Attachment table.
EF does not support 1:0..1 relationship with explicit FK column at the dependent side (it considers such relationship to be 1:0..N). For 1:0..1 it uses a so called Shared Primary Key Associations in which the PK of the dependent side is also a FK to the principal side.
With that being said, in your model the CustomForeignKeyId is redundant. The correct (from EF perspective) model would be like this:
public class PerfData
{
public Guid Id { get; set; }
public AttachmentData Attachment { get; set; } //navigation property
}
public class AttachmentData {
...
public Guid Id { get; set;}
public PerfData Perf { get; set; } // navigation property
}
with the following configuration:
internal class AttachmentDataConfig : BaseConfig<AttachmentData>
{
public AttachmentDataConfig() : base("Attachment")
{
HasRequired(e => e.Perf)
.WithOptional(e => e.Attachment)
.WillCascadeOnDelete(true);
}
}
UPATE: If you want to keep the existing FK field and use 1:0..N relationship, then change the PerfData class navigation property
public AttachmentData Attachment { get; set; }
to
public ICollection<AttachmentData> Attachments { get; set; }
and inside the configuration
.WithMany()
to
.WithMany(e => e.Attachments)

Categories

Resources