Entity Framework Core: Core Features
This document explores the fundamental features that make Entity Framework Core (EF Core) a powerful and versatile Object-Relational Mapper (ORM) for .NET developers.
1. LINQ to Entities
LINQ to Entities allows you to write queries against your data using Language Integrated Query (LINQ). EF Core translates these LINQ queries into SQL that is executed against the database.
using (var context = new MyDbContext())
{
var customers = from c in context.Customers
where c.City == "London"
select c;
foreach (var customer in customers)
{
Console.WriteLine(customer.CompanyName);
}
}
This feature significantly enhances developer productivity by allowing them to work with data using familiar C# or VB.NET syntax, rather than writing raw SQL strings.
2. Change Tracking
EF Core automatically tracks changes made to entities that are loaded into the context. When SaveChanges()
is called, EF Core determines what SQL statements need to be executed to persist these changes to the database.
using (var context = new MyDbContext())
{
var product = context.Products.Find(1);
if (product != null)
{
product.Price = 19.99M; // EF Core will detect this change
context.SaveChanges(); // Generates an UPDATE statement
}
}
EF Core supports tracking changes for entities in three states: Unchanged, Added, Modified, and Deleted.
3. Relationships and Navigation Properties
EF Core makes it easy to model and query relationships between entities, such as one-to-one, one-to-many, and many-to-many. Navigation properties simplify accessing related entities.
// Assuming Customer has a collection of Orders (one-to-many)
var firstCustomer = context.Customers.Include(c => c.Orders).FirstOrDefault();
if (firstCustomer != null)
{
foreach (var order in firstCustomer.Orders)
{
Console.WriteLine($"Order ID: {order.OrderId}, Date: {order.OrderDate}");
}
}
EF Core can automatically configure these relationships based on conventions or explicit configuration using the Fluent API or Data Annotations.
4. Database Provider Model
EF Core is designed with a pluggable database provider model. This means EF Core can interact with various database systems, including SQL Server, PostgreSQL, MySQL, SQLite, and more, without changing your application code.
You typically configure the database provider in your DbContext
's OnConfiguring
method or by using dependency injection.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("YourConnectionString");
// or optionsBuilder.UseNpgsql("YourConnectionString");
// or optionsBuilder.UseSqlite("Data Source=my-database.db");
}
5. Migrations
EF Core Migrations provide a way to incrementally update your database schema as your application's data model evolves. They allow you to manage database changes in a version-controlled manner.
Common commands include:
Add-Migration InitialCreate
: Creates a new migration file.Update-Database
: Applies pending migrations to the database.Script-Migration
: Generates a SQL script from a migration.
// Example of a generated migration file (simplified)
public override void Up()
{
CreateTable(
name: "Products",
columns: table => new
{
ProductId = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(nullable: true),
Price = table.Column<decimal>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Products", x => x.ProductId);
});
}
6. Entity States and DbContext Lifetime
Understanding the lifetime of a DbContext
and the states of entities is crucial for efficient data management. A DbContext
instance is typically designed to be short-lived and used within a single unit of work.
Entity States:
- Detached: An entity that is not being tracked by the context.
- Unchanged: The entity has not been modified since it was loaded.
- Added: The entity is new and will be inserted into the database.
- Modified: The entity has been changed since it was loaded.
- Deleted: The entity has been marked for deletion.
DbContext
instances, as they can lead to performance issues and stale data. Use dependency injection to manage their lifetime effectively.