Purpose of OnModelCreating - EF Core Database first Approach - c#

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

Related

How to Rename all Entity Framework Primary Columns to Id in Generic Repository?

The following resource displays how to generate Partial Class mapping for Generic repository. Our Models don't have Id column, they are always named ProductId, CustomerId, etc. (Know generic repository is under lot of debate, however manager told us to apply it)
The method results in client-side evaluation, which is slow/not allowed in EFCore 3.
Net Core: Create Generic Repository Interface Id Mapping for All Tables Auto Code Generation
public partial class Product: IEntity
{
[NotMapped]
public int Id { get => ProductId; set => ProductId= value; }
}
Does anyone have auto generated code or T4 to create this result? Would like to go through all scaffolded models and rename class member to id, and Column to CustomerId, SalesId, ProductId, etc? Code also needs to update ModelBuilder as need. Does EF Core 2 have option like this?
How to map table names and column name different from model in onmodelcreating method in Entity Framework -6?
It can be done with Data Annotation or FluentAPI, open to any option.
public class Product
{
[Column("ProductId")]
public int Id { get; set; }
public string ProductName { get; set; }
public string ProductDescription { get; set; }
public float Cost { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.Property(p => p.Id)
.HasColumnName("ProductId");
}

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.

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

Discover all DbContext

I have two DbContext in my application, which configure some models with Fluent API. One model of my first DbContext has a foreign key to a second model configured in my second DbContext.
public class UserData
{
public double Id { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string FullName {
get {
return $"{this.FirstName} {this.LastName}";
}
}
public string Adress1 { get; set; }
public virtual BaseUserTreeData BaseUserTree { get; set; }
public double? BaseUserTreeId { get; set; }
public virtual List<DeviceData> Devices { get; set; }
}
The model BaseUserTreeData is my foreign property configured in my second DbContext.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<BaseUserTreeData>().ToTable("sw_data_baseusertree");
modelBuilder.Entity<BaseUserTreeData>().Property(baseusertree => baseusertree.Id).HasColumnName("baseusertree_ID");
modelBuilder.Entity<BaseUserTreeData>().Property(baseusertree => baseusertree.Label).HasColumnName("label");
modelBuilder.Entity<BaseUserTreeData>().Property(baseusertree => baseusertree.ParentTreeId).HasColumnName("baseUserTree_ID_parent");
modelBuilder.Entity<BaseUserTreeData>().HasKey(baseusertree => baseusertree.Id);
modelBuilder.Entity<BaseUserTreeData>()
.HasOptional(tree => tree.ParentTree)
.WithMany(tree => tree.ChildTrees)
.HasForeignKey(tree => tree.ParentTreeId);
}
When I use UserData with my UserDbContext for the first time, the OnModelCreating of my second DbContext is not called, so BaseUserTreeData mapping is not executed, and the query generated by Entity Framework 6 is wrong. I saw that I can share EntityConfiguration in separate classes, but is there a way to tell to EF6 to call every OnModelCreating of all my DbContext?
I think you're in wrong path.This is not a recommended way of handling Context.If there is too strong relationship between models, you have to concentrate the models inside one unique context.Otherwise you'll have to face so many issues in the future.So my advice is to use the pattern which EF team suggested below.
This is the way Microsoft EF Team has suggested :
When working with Web applications, use a context instance per
request.
You can read more about context handling using below articles :
Working with DbContext
Managing DbContext the right way with Entity Framework

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

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?

Categories

Resources