C# Study Note 2 - Entity Framework
This is second part of DotNet study notes – EF Core 2.0.
Introduction
As ORM framework, EF has three design approaches:
- Database First – Database –> EDM
- Model First – EDM –> Database
- Code First – Code –> Database
I had spent most of my time in code first for the last two weeks. Here are the key notes of my study journey.
Cornerstone
- DbContext which is container in the memory of all entities and states
- communication to database
- mapping between entities and database tables
- tracing states of entities
- DbSet Entities inside DbContext
Connecting to database
Connect to database is simple just override OnConfiguring
method
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// UseSqlServer for sql server
// UseSqlite for sqlite
// UseNpgsql for postgresql
}
Mapping
There are two way to indicate mapping model:
- Data Annotation: which is very convenient but should not be heavily used here for loose coupling purpose
- Fluent API
As far as I am concerned, it is a good to decouple the class and mappers by using fluent apis. This is because we can focus on the main logic regardless of the data persist and it is easy to maintain and extend.
Fluent API could be achieved by override OnModelCreating
method:
eg:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// configure mapper here
}
It is possible OnModelCreating methods would grow to hundreds lines. We could add a mapper layer for an aggregate by implement IEntityTypeConfiguration
interface
public class SomeAggregateMapper : IEntityTypeConfiguration<SomeAggregate>
{
public void Configure(EntityTypeBuilder<SomeAggregate> builder)
{
// configure aggregate mapper here
}
}
then add mapper to Configurations
in modelBuilder
modelBuilder.ApplyConfiguration(new SomeAggregateMapper());
One to One
A user has a profile
// user
public class User
{
public int Id { set; get; }
public string Email { set; get; }
public int ProfileId { set; get; }
public Profile Profile { set; get; }
}
// user mapper
public class UserMapper : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.HasKey(c => c.Id);
// profile not null
// builder.HasOne(p => p.Profile).WithMany().HasForeignKey(p => p.ProfileId);
// profile could be null
builder.HasOne(p => p.Profile).WithMany().HasForeignKey(p => p.ProfileId);
}
}
- HasRequired / HasOptional as means in its name
- WithMany() no parameters means no linking back
// profile
public class Profile
{
public int Id { set; get; }
public int FirstName { set; get; }
}
// profile mapper
public class ProfileMapper : IEntityTypeConfiguration<Profile>
{
public void Configure(EntityTypeBuilder<Profile> builder)
{
builder.HasKey(c => c.Id);
}
}
One to Many
A profile has many projects add public virutal ICollection<Project> Projects { get; set; }
to Profile class
public class Project
{
public int Id { get; set; }
public string Name { get; set; }
public int ProfileId { get; set; }
public Profile Profile { get; set; }
}
public class ProjectMapper : IEntityTypeConfiguration<Project>
{
public void Configure(EntityTypeBuilder<Project> builder)
{
builder.HasKey(c => c.Id);
builder.HasOne(p => p.Profile).WithMany(p => p.Projects).HasForeignKey(p => p.ProfileId);
}
}
Many to Many
Tags and Keywords
// tag
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<TagKeyword> TagKeywords { get; set; }
}
// keyword
public class Keyword
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<TagKeyword> TagKeywords { get; set; }
}
// TagKeyword
public class TagKeyword
{
public int TagId { get; set; }
public Tag Tag { get; set; }
public int KeywordId { get; set; }
public Keyword Keyword { get; set; }
}
public class TagKeywordMapper : IEntityTypeConfiguration<TagKeyword>
{
public void Configure(EntityTypeBuilder<TagKeyword> builder)
{
builder.HasKey(t => new { t.TagId, t.KeywordId });
builder.HasOne(tk => tk.Tag).WithMany(t => t.TagKeywords).HasForeignKey(tk => tk.TagId);
builder.HasOne(tk => tk.Keyword).WithMany(k => k.TagKeywords).HasForeignKey(tk => tk.KeywordId);
}
}
Self-reference
- One to many / parent - children
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
public Category Parent { get; set; }
public ICollection<Category> Children { get; set; }
}
public class CategoryMapper : IEntityTypeConfiguration<Category>
{
public void Configure(EntityTypeBuilder<Category> builder)
{
builder.HasOne(tk => tk.Parent).WithMany(t => t.Children).HasForeignKey(tk => tk.ParentId);
}
}
- Many to many
public class Keyword
{
public int Id { get; set; }
public string Name { get; set; }
[NotMapped]
public int Weight { get; set; }
public virtual ICollection<KeywordNeighbors> Lefts { get; set; }
public virtual ICollection<KeywordNeighbors> Rights { get; set; }
}
public class KeywordNeighborsMapper : IEntityTypeConfiguration<KeywordNeighbors>
{
public void Configure(EntityTypeBuilder<KeywordNeighbors> builder)
{
builder.HasKey(t => new { t.LeftId, t.RightId });
builder.HasOne(tk => tk.Left).WithMany(t => t.Lefts).HasForeignKey(tk => tk.LeftId);
builder.HasOne(tk => tk.Right).WithMany(k => k.Rights).HasForeignKey(tk => tk.RightId);
builder.HasIndex(t => t.Weight);
builder.Property(t => t.Weight).HasDefaultValue(99);
}
}
Ps: This form a graph.
Inheritance
When it comes to inheritance in EF Core 2.0, there are three types of relations:
- TPH:Table per inheritance
- TPT:Table per type
- TPC:Table per Concrete Type
### Conclusion
This is just a first glance of EF, there are so many things need to be learnt during the development of projects.
API Reference
For API lookup2
Fluent API Methods | Usage |
---|---|
HasDefaultSchema() | Specifies the default database schema. |
ComplexType() | Configures the class as complex type. |
HasIndex() | Configures the index property for the entity type. |
HasKey() | Configures the primary key property for the entity type. |
HasMany() | Configures the Many relationship for one-to-many or many-to-many relationships. |
HasOptional() | Configures an optional relationship which will create a nullable foreign key in the database. |
HasRequired() | Configures the required relationship which will create non-nullable foreign key column in the database. |
Ignore() | Configures that the class or property should not be mapped to a table or column. |
Map() | Allows advanced configuration related to how the entity is mapped to the database schema. |
MapToStoredProcedures() | Configures the entity type to use INSERT, UPDATE and DELETE stored procedures. |
ToTable() | Configures the table name for the entity. |
HasColumnAnnotation() | Sets an annotation in the model for the database column used to store the property. |
IsRequired() | Configures the property to be required on SaveChanges(). |
IsConcurrencyToken() | Configures the property to be used as an optimistic concurrency token. |
IsOptional() | Configures the property to be optional which will create nullable column in the database. |
HasParameterName() | Configures the name of the parameter used in stored procedure for the property. |
HasDatabaseGeneratedOption() | Configures how the value will be generated for the corresponding column in the database e.g. computed, identity or none. |
HasColumnOrder() | Configures the order of the database column used to store the property. |
HasColumnType() | Configures the data type of the corresponding column in the database for the property. |
HasColumnName() | Configures the corresponding column name in the database for the property. |
IsConcurrencyToken() | Configures the property to be used as an optimistic concurrency token. |
Ps: The above are the cheat sheet for EF, there are several mutation for EF Core eg: HasOne