In this blog post you will learn a new feature ‘Custom Code First Conventions’ introduced with Entity Framework 6. This is 'Code First' only improvement. EF 6 comes with number of cool new features and improvements, so I decided to write blog posts and cover some of the new features.
I am going to split this post into two parts. In the first part I will show you the basic things on ‘Setting-up Console Application’ and in the second part we will use this newly created console application and try out this cool new feature ‘Custom Code First Conventions’ in EF 6. Clearly, I am targeting beginners here, so if you understand how EF works or if you use EF frequently you should jump to ‘Custom Code First Conventions’ point in this post.
Setting-up Console Application
To set-up console application, just follow the easy steps.
Step 1
Open Visual Studio and create a new Visual C# Console Application project.
Step 2
In order to do database work with Entity Framework, we need to have a model class. Right click on project name to add a class by name ‘State.cs’ and write following codes.
public class State
{
public int Id { get; set; }
public string StateName { get; set; }
public virtual ICollection<District> Districts { get; set; }
}
public class District
{
public int Id { get; set; }
public string DistrictName { get; set; }
public int StateId { get; set; }
public virtual State State { get; set; }
}
So, we created two different model classes State and District with few properties, all in the same .cs file. If you look closely we have established one to many relationship between both domain models. This can be defined like, for each ‘District’ there will be a single State in ‘District’ model and for each State there will be collection of District in ‘District’ model.
Step 3
Install the EntityFramework bits either from package manage console or from NuGet. Let’s use package manager console and type following command.
Install-Package EntityFramework
Hit enter, it will install the latest available EF library, which is 6.0.1 as of now, in the project.
Step 4
In above step we installed EF6 bits in the project, now we are ready to use EF as an ORM. To work with DbSet and DbContext, we need a class file, add a new class file by name ‘StateContext.cs’ that inherits DbContext and also write two DbSet, one for ‘State’ and another for ‘District’ models, here is the code.
class StateContext : DbContext
{
public DbSet<State> States { get; set; }
public DbSet<District> Districts { get; set; }
}
In the above code, I have created two DbSet, one for State and another for District.
Step 5
Now we are done with major things, just open ‘Program.cs’ file and write following easy codes inside main method.
static void Main(string[] args)
{
using (var context = new StateContext())
{
var stdQuery = (from d in context.States
select new { StateName = d.StateName });
foreach (var q in stdQuery)
{
Console.WriteLine("State Name : " + q.StateName);
}
Console.ReadKey();
}
}
In above code we are trying to see the list of States but unfortunately we do not have any record for States or Districts in the database, so it is obvious it will show you nothing. But it will generate a brand new database for you once you run. You can see it inside LocalDb list.
In above image, you can see three database tables States, Districts and one for MigrationHistory, which keeps track all the code first migration histories. If you want to learn code first migration, I have a blog for this here.
Let’s go and enter some records manually in States and Districts tables or we could use ‘EF Seed method’ to do it automatic, if you want to learn this I have a blog for this also Entity Framework's Database Seed Method.
Well, I have entered following records manually just to save time and keep codes simple.
Step 6
In above step we entered database records manually, let’s run the application to see how it works.
In the above output you can see all the States are listed.
Understanding Custom Code First Conventions
Now, we have a ready to run Console Application, where we will try out this new feature ‘Custom Code First Convention’.
Let’s understand each segment of ‘Custom Code First Conventions’.
Custom - means making things according to individuals.
Code First - this is an alternative to ‘database first’ and ‘model first’ approaches to create database.
Conventions - this is something like large formal thing that you have to follow in the code.
Now, mix every segment together and try to understand it.
Custom Code First Conventions feature lets us define own conventions to provide custom configuration defaults for our domain model. There are two main types of Conventions, Configuration Conventions and Model Conventions. In this blog we will be covering only ‘Configuration Conventions’.
Configuration Conventions
Configuration Conventions are the way to configure set of objects without overriding configuration provided by Fluent API. We can define this convention inside OnModelCreating method or we can use own class that inherits Convention class.
Convention with OnModelCreating method
Let’s first understand how we have do this with OnModelCreating method. In case you want to learn about OnModelCreating method, just read my one of the post here, this is a great post you should read before proceeding here.
Example 1
Entity Framework Team worked on a most requested feature to set the precision of all decimal properties in their model. Assume, we want all properties with decimal datatype on all entities should be configured to have a precision of 5 and a scale of 2
To achieve this with Configuration Conventions, we can use following code.
class StateContext : DbContext
{
public DbSet<State> States { get; set; }
public DbSet<District> Districts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<decimal>()
.Configure(prop => prop.HasPrecision(5, 2));
}
}
Look at the highlighted code, this code creates a convention that tells EF that all properties on all entities should be configured to have a precision of 5 and a scale of 2.
This can be conceptualized as the Properties method returning a list of all properties in your model. The parameter <decimal> then filters all properties to only be those that are of type decimal, and finally you call configure. Configure method is where we decide what to do with all the properties that we have filtered out in the preceding method.
So far we don’t have any decimal property in the model, let’s add a new property in State model.
public decimal StateFund { get; set; }
To check that how above changes got effected in database, I enabled the Code First Database Migration and then added a StateFund decimal property in the State model. Once we have a new property in the model, we need to execute Code First Migration.
When you execute update-database command of Code First Migration. This will go and update the database and you will see StateFund field with decimal data type of size (5, 2), super.
You can see it is working.
Example 2
Another example of Configuration Conventions is when you want to define a different primary key convention. By default EF convention looks for either ‘Id’ or name followed by ‘Id’ as a Primary Key. Assume, we want all properties on all entities that end with ‘Key’ (like StateKey or DistrictKey) to be a primary key or something similar without using [Key] annotation with property. Look at the model given below, I’m using ‘StateKey’ as a Primary Key.
Delete ‘Id’ and ‘StateKey’ with [Key] annotation, just keep last ‘StateKey’ property. Now, in order to run this we need to create ‘Configuration Conventions’, for this we can use following code.
class StateContext : DbContext
{
public DbSet<State> States { get; set; }
public DbSet<District> Districts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties()
.Where(prop => prop.Name.EndsWith("Key"))
.Configure(config => config.IsKey());
}
}
Look at the highlighted code, this code creates a convention that tells EF that all properties on all entities, which ends with ‘Key’ should be considered as Key (Primary Key).
Note: If you just striped the [Key] annotation from already running application, don’t need to execute ‘Update-Database’ command as given below.
Now, go ahead and add new migration and update the database as we did above. Sometimes, ‘Update-Database’ command produces error, while you executing ‘Update-Database’ command. I experienced following errors.
Error 1 - Here is the error I got inside Package Manager Console:
Multiple identity columns specified for table 'States'. Only one identity column per table is allowed.
I encountered this error when trying to execute ‘Update-Database’ command and the scaffolded migration script class [which has Up() and Down() methods] failed to arrange drop/add/create/rename job order.
Error 2 - If you again do ordering in a wrong way, you will see another error very similar to one given below.
The object 'PK_dbo.States' is dependent on column 'Key'.
ALTER TABLE DROP COLUMN Key failed because one or more objects access this column.
Now, to make this migration (scaffolded migration script class) work. We have to arrange jobs this way. In the image, green sign is a valid way and red sign is a wrong way.
Once we made order correctly specially Drop the ‘key’ before adding new ‘key’, and it worked.
I don’t know is it a template bug or I am doing things in a wrong way, if you know please share.
Example 3
Instead of using Pascal Code in model property name we can use lower case and _ (underscore sign) between the characters maybe that our DBA would like. For example, ‘StateName’ should become ‘state_name’, ‘StateFund’ should become ‘state_fund’ and ‘StateKey’ should become ‘state_key’. For this we can write a method as given below.
class StateContext : DbContext
{
public DbSet<State> States { get; set; }
public DbSet<District> Districts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties()
.Where(prop => prop.Name.EndsWith("Key"))
.Configure(config => config.IsKey());
modelBuilder.Properties()
.Configure(config => config.HasColumnName(GetColumnName(config.ClrPropertyInfo)));
}
private static string GetColumnName(PropertyInfo property)
{
var result = property.DeclaringType.Name + "_";
result += Regex.Replace(
property.Name,
".[A-Z]",
m=> m.Value[0] + "_" + m.Value[1]);
return result.ToLower();
}
}
Look at the highlighted code it is self-explanatory. Now, add a new migration and update the database, you will see your database table got updated with new field names having _ (underscore) sign.
I am not necessarily suggesting that this is good database design, but this is the convention we have to follow, and this is a good new feature we should learn. Keep in mind, I did not made any changes in actual model, it is still as it was above. Here it is:
public class State
{
public int StateKey { get; set; }
public string StateName { get; set; }
public decimal StateFund { get; set; }
public virtual ICollection<District> Districts { get; set; }
}
And it will work.
Example 4
Let’s look at one better example, assume we want to change size of all nvarchar from ‘MAX’ to 50, so here ‘Custom Convention’ will help you as well. Here is a screenshot.
And here is the simple code.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties()
.Where(prop => prop.Name.EndsWith("Key"))
.Configure(config => config.IsKey());
modelBuilder.Properties()
.Configure(config => config.HasColumnName(GetColumnName(config.ClrPropertyInfo)));
modelBuilder.Properties<string>()
.Configure(p => p.HasMaxLength(50));
}
Now add a new migration and update the database, you will notice the database got updated as given in above image.
So, this is all I wanted to show in ‘Custom Conventions’ using OnModelCreating method. These conventions can be also achieved using separate class, how? Keep reading.
Custom Convention with class
All the examples (Example 1, Example 2, Example 3 and Example 4) I shown above can be done with separate classes that inherits Convention class. Here is one example.
class CustomKeyConvention : Convention
{
public CustomKeyConvention()
{
Properties()
.Where(prop => prop.Name.EndsWith("Key"))
.Configure(config => config.IsKey());
}
}
Now we need to call it from OnModelCreating method of DbContext.
modelBuilder.Conventions.Add<CustomKeyConvention>();
So, this is also easy. You can use any of the way (using OnModelCreating or using Custom Convention with class) for your Custom Code First Conventions. Excited to learn more cool new features, subscribe the feed.
No comments:
Post a Comment