Introduction
Entity framework is really a great framework to work with. And the code first approach is becoming popular day by day. But if we don’t use entity framework code first in a proper way, it may turn into a devastating scenario. So in this article, I will try to illustrate, what type of problems I have faced working with entity framework and how we can use this framework in a sure way.
Background
Before starting, I am assuming that we have little knowledge about entity framework code first. At least, we know how to create tables, relations and using seed data in entity framework.
If we don’t know, it wouldn’t be that hard to start with:
Or just Google for some time, it would be just fine.
So, what entity framework code first needs?
- Models: to turn them into tables …
- Configurations for models: to create relations between tables, and formatting table columns structures …
- Context: to represent the database, connections, calling model configurations, database initializations, database load configurations …
- Initializers: to initialize database context, using seeds …
- Migrations: if model changes how to deal with old data or structures ...
What Are the Problems that You Have Been Taking About?
The problems start when we are misusing sections like:
- Initialization of database context
- Working with migrations
Let’s explain those sections.
1. Initialization of Database Context
To initialize Database context, we can use IDatabaseInitializers
classes like:
CreateDatabaseIfNotExists
– creates database first, if there is no database available for the application, specified by the context and connection string (good for live projects)
But my host provider is not letting me to do such a thing. It asks me to create database manually, then use. What should I do now?
Updated requirement forces you to add a new field to a table. So you did and provided the updated context. Now if we run the application, exception would be thrown pointing to the current context and existing database doesn’t match with one another. What should I do now?
DropCreateDatabaseIfModelChanges
– drops the old database if it doesn’t match with the updated context, and creates new database as the context is pointing (good for development)
Updated requirement forces you to add a new field to a table. So you did and provided the updated context. Now if we run the application, then the old database would be dropped. All data would be lost. What should I do now?
DropCreateDatabaseAlways
- drops the old database and creates a new one every time, whenever the context is initialized. (Good for testing, if database is small in size.)
We will not be able to store enough data to work with. No way should we use it, if the application is one real-time action. What should I do now?
So some say, why not use CreateDatabaseIfNotExists
for live projects, DropCreateDatabaseIfModelChanges
during development, and SQL Script to create database for the first time. When we go live, replace DropCreateDatabaseIfModelChanges
with CreateDatabaseIfNotExists
, and the problem is solved. What about if one day, we provide the update, but forget to do so?
2. Working With Migrations
Migration is really a good way to work with entity frame work code first. It enables the database to hold information like, how many migrations have been made and when. Plus in migration files, we can find out what changes were made even we do go back and forth between migrations. In the next post, we will talk about it.
But the problem is, it is a little bit tricky to configure. If you have less time to do research, migration may force you to omit the option to use entity framework code first. Because some times, day by day applications expand so much, which enforce us to make changes in the database data and tables relations, etc.
And people don’t like to take risks for big projects. They want to handle things as they used to do with SQL Scripts.
What Is the Solution?
Here is the clue, Entity Framework can also be used to map context to existing database. So what are we going to do?
- Use a builder context for database, which uses
DropCreateDatabaseIfModelChanges
. The context’s access in limited to a particular project only, not to other projects of the solution. Or can be inherited by other projects codes (using internal
/ protected internal
as access modifier). This will be used to rebuild the database. - Use a mapper context for database, which is only mapped to database entities. This will be mainly used by application to affect tables.
- Inheriting common contents from
Base
context, for field table and configuration files
Mapper Context
Mapper context is simple as such, which inherits base Context
class.
We will use this context for any CRUD operations.
public class UmsContext : Context
{
public UmsContext() : base("DbUms")
{
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
}
}
Builder Context
Builder context which also inherits from base Context
class and its initializer class is like:
We will use this context class only to recreate the database:
internal class UmsBuildContext : Context
{
static UmsBuildContext()
{
string connectionString = ConfigurationManager.ConnectionStrings["DbUms"].ConnectionString;
Database.SetInitializer(new UmsBuildContextInitializer());
}
public UmsBuildContext()
: base("DbUms")
{
}
}
Initializer may also have seeds like this:
internal class UmsBuildContextInitializer
: DropCreateDatabaseIfModelChanges<UmsBuildContext>
{
protected override void Seed(UmsBuildContext context)
{
List<Department> departments = new List<Department>()
{
new Department(){Name = "Math"},
new Department(){Name = "Physics"},
new Department(){Name = "Chemistry"},
};
context.Departments.AddRange(departments);
context.SaveChanges();
List<Student> students = new List<Student>()
{
new Student(){ Name = "Nitol", DepartmentId = 1},
new Student(){ Name = "Tasnim", DepartmentId = 2},
new Student(){ Name = "Ripon", DepartmentId = 3}
};
context.Students.AddRange(students);
context.SaveChanges();
}
}
As we said, we want to set limit to the builder context. That’s why we are using internal
as access modifier for UmsBuildContext
. But it is also required to use this context for test projects to recreate the database for tests/ intentionally. So to access some of its functionality, we are using this class inside a public
class like:
public class DbBuilder
{
public void Build()
{
var departments = new UmsBuildContext().Departments.ToList();
}
}
Base Context
The main base context is mainly holding all the table configurations and database entity references, and the above contexts are inheriting it.
public class Context : DbContext
{
public DbSet<Department> Departments { get; set; }
public DbSet<Student> Students { get; set; }
protected Context(string connectionString)
: base(nameOrConnectionString: connectionString)
{
}
public DbSet<TSource> Table<TSource>() where TSource : class
{
return Set<TSource>();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new DepartmentConfiguration());
modelBuilder.Configurations.Add(new StudentConfiguration());
}
}
Using the Code
So if any change is made to the database structure, and we want to rebuild the hold database, we only have to call:
new DbBuilder().Build();
Now, let’s test the mapper context:
var db = new UmsContext();
var departments = db.Departments.ToList();
var departments2 = db.Table<Department>().ToList();
var student = new Student()
{
Name = "Rintu",
Department = new Department() {Name = "CSE"}
};
db.Students.Add(student);
db.SaveChanges();
Here, a new student
and its department
row would be created in the database which means the mapper is working fine.
Change Structure of the Database
To do so, we just have to:
- Modify the model class
- Rebuild the database at local machine
- Compare recreated database with the old one, to find the required alter/ update SQL-quires to update old database to the current version, and use such queries to the old database.
- Replace old DLL with the newly created one
Limitations
- Yes, there could be some errors which I haven't faced yet, or could make some ethical disturbance. So if you find any, just let me know.
- I am not discouraging anyone to use migrations. If you have time, just do some RNDs
- The rebuild system may seem a little bit creepy, but I am working to make it more logical.
public class DbBuilder
{
public void Build()
{
var departments = new UmsBuildContext().Departments.ToList();
}
}
or just take a look at this, http://stackoverflow.com/questions/4911329/ef-codefirst-ctp5-manually-drop-and-create-db
Find Visual Studio 2010 solution of framework 4 projects at the attachment. If you run the project, it would create a "UMS
" database with 3 tables, at the local machine. You can change it using app.config.