Custom Code First Conventions - EF 6 New Feature
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.
Model Conventions
I
will post a separate blog on this soon.
Hope
this helps.
nice share sir... can you please explain why the last property in both the classes are marked virtual?
ReplyDeleteWe use 'virtual' keyword for variety of reasons. In case we using 'virtual' with model property we don't need to use 'Include' extension method while querying data in controller. If you don't use 'virtual', you have use 'Include' extension method. By default while doing CRUD, it uses 'Include' extension method. Read this thread http://stackoverflow.com/questions/16449171/virtual-keyword-include-extension-method-lazy-loading-eager-loading-how-doe
DeleteGetting this error while running this ample code after Adding Entity Framework and said Model Classes:
ReplyDeleteInconsistent accessibility: property type 'Console_Application.State' is less accessible than property 'Console_Application.District.State' D:\Console_Application\Console_Application\State.cs 21 30
--------------------------------------------------------
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();
}
}
---------------------------------------------------------------------------
Make sure you have added 'public' access with your classes. Ex - public class State {....} and public class District {....}.
DeleteYes, I solved it right the moment but couldn't notify as there's no way to delete the comment in moderation. Thanks for your reply, Sir.
DeleteIs there a way to modify the value of the property. I want to create a convention where I want to change the DateTimeKind of all the DateTime fields to DateTimeKind.Utc.
ReplyDeleteHi Abhimanyu,
ReplyDeleteI'm using EF6 Code First. I've created view on Sql server manually and how to map view to a Class in C# ?
Advance Thanks,
Sunil