Setting up hierarchical, self-referencing model in Entity Framework code-first - c#

I am trying to create a self-referencing org table in Entity Framework v6 code-first - and I can't seem to get it right... (so far I always worked with the EF 4.0 visual designer).
I have this class (that corresponds to a SQL Server table):
[Table("Organisation")]
public partial class Organisation
{
public int OrganisationId { get; set; }
public int? ParentOrgId { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[ForeignKey("ParentOrgId")]
public virtual Organisation Parent { get; set; }
}
Pretty standard stuff - and this is my DbContext derived class which should set up this self-referencing hierarchy:
public partial class HierarchyCtx : DbContext
{
public HierarchyCtx() : base("name=HierarchyCtx")
{
}
public virtual DbSet<Organisation> Organisation { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Organisation>()
.Property(e => e.Name)
.IsUnicode(false);
modelBuilder.Entity<Organisation>()
.HasOptional(e => e.Parent)
.WithMany()
.HasForeignKey(m => m.ParentOrgId)
.WillCascadeOnDelete(false);
}
}
Now I added a few rows to my SQL Server table, but when trying to load these entries, I get an error:
System.Data.SqlClient.SqlException: Invalid column name 'Discriminator'.
Invalid column name 'Discriminator'.
Invalid column name 'Discriminator'.
Invalid column name 'ParentOrganisation_OrganisationId'.
Not entirely sure what's happening here.... what is Discriminator (a column I do not have) and why do I need it? How to add it? And why is EF trying to find a column ParentOrganisation_OrganisationId when I set the model up to use ParentOrgId as foreign key column??
This is a bit of a mystery to me..... anyone out there which has successfully done this in EF code-first before? What am I missing?

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.

EF re-adding FK columns after adding ICollection navigation property

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.

EntityFramework Core 2.0.1 - Cascade Delete with SQLite

I know this question has been asked and answered a few times:
Specifying ON DELETE NO ACTION in Entity Framework 7?
Configuring cascade delete with EF7
But in my case I'm using SQLite and none of the solutions seem to work. Similar code to update the database works in EF6 on SQL Server, but unless I'm missing something really obvious, doesn't work on EF Core with SQLite on .NET Standard 2.0. I have a parent Customer with child Contacts and am saving them all at once.
using (var dbc = new ContactsDbContext())
{
var customer = dbc.Customers.First(s => s.CustomerID == ReadProperty(CustomerIDProperty).ToString());
foreach (var item in dbc.Contacts.Where(x => x.CustomerID == ReadProperty(CustomerIDProperty).ToString()))
{
dbc.Contacts.Remove(item);
}
MapToEntity(customer);
dbc.SaveChanges();
}
I get the following exception upon SaveChanges()
System.InvalidOperationException occurred
HResult=0x80131509
Message=The instance of entity type 'Contact' cannot be tracked because another instance with the key value 'ContactID:99b44b3e-c981-4140-9198-b665d3d85600' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I loved having the ability to turn off cascade deletes globally in prior versions of EF. This is what used to work:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
And I've tried using these two solutions below, of which neither works for SQLite.
foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
// Also tried Cascade, and all other options
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
AND
modelBuilder.Entity<Entity.Contact>()
.HasOne(p => p.Customer)
.WithMany()
.HasForeignKey("CustomerID")
.OnDelete(DeleteBehavior.Restrict);
Has anyone successfully used parent/child updating on SQLite? My alternative is to manually check the status on each child object before saving, but that seems unnecessary for a feature that used to work. Thanks in advance for looking at this.
Here's my Contact and customer classes
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Contacts.Library.Data.Entity
{
[Serializable()]
public class Contact
{
[Key]
public int PrimaryKey { get; set; }
[Required, MaxLength(36)]
public string ContactID { get; set; }
[ForeignKey("CustomerID"), MaxLength(36)]
public string CustomerID { get; set; }
[MaxLength(50)]
public string Name { get; set; }
// Path back to related customer
public virtual Customer Customer { get; set; }
}
}
[Serializable()]
public class Customer
{
[Key]
public int PrimaryKey { get; set; }
[Required, MaxLength(36)]
public string CustomerID { get; set; }
[Required, MaxLength(50)]
public string Name { get; set; }
// A customer has a collection of contacts
public virtual ICollection<Contact> Contacts { get; set; }
}
And here's my indexes
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Name on customer is unique
modelBuilder.Entity<Entity.Customer>().HasIndex(c => c.Name).IsUnique();
// Add index
modelBuilder.Entity<Entity.Customer>().HasIndex(
customer => new { customer.CustomerID }).IsUnique(false);
// Add index
modelBuilder.Entity<Entity.Contact>().HasIndex(
contact => new { contact.ContactID }).IsUnique(false);
// Add index
modelBuilder.Entity<Entity.Contact>().HasIndex(
contact => new { contact.CustomerID }).IsUnique(false);
}

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

Categories

Resources