Override Mapping
EFCore handles target type and target names from source code to database schema. But you may want to customize the behavior to avoid collision or for some other reasons. There's majorly two kinds of attribute to do the mapping:
- Attributes from
System.ComponentModel.DataAnnotations
: common attributes for data validation. - Attributes from
System.ComponentModel.DataAnnotations.Schema
: attributes for schema mapping.
Type mapping
Mapping a field
To map type of a filed in a table,
cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public class Movie
{
public int Id { get; set; }
[Column(TypeName = "varchar")]
public string? Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string? Synopsis { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
Naming mapping
cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("Film")]
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
public DateTime ReleaseDate { get; set; }
[Column("Abstract", TypeName = "varchar(128)", Order = 3)]
public string? Synopsis { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
TIP
It's kind of weird that TableAttribute
should be appiled on a data model instead of a DbSet
Constraint mapping
cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public class Movie
{
[Key] // primary key
public int Unique { get; set; }
[Required] // marks not null
[MaxLength(128)] // max length in database
public string? Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string? Synopsis { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Fluent mapping
Entity framework core also has a way to register mapping with fluent builder.
cs
public class MoviesContext : DbContext
{
public DbSet<Movie> Movies => Set<Movie>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Movie>() // which entity model to map
.ToTable("Film")
.HasKey(x => x.Id);
modelBuilder.Entity<Movie>()
.Property(x => x.Title) // pick a field from model
.HasColumnType("varchar")
.HasMaxLength(128)
.IsRequired();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Better organized mapping
If you got a lot entity model to map, OnModelCreating
can be a complete mess. To separate those registrations, create different classes to handle that.
cs
// some other files
class MovieMapper : IEntityTypeConfiguration<Movie>
{
public void Configure(EntityTypeBuilder<Movie> builder)
{
// no more entity model selection since `builder` is a `EntityTypeBuilder<Movie>`
builder.ToTable("Film").HasKey(x => x.Id);
builder.Property(x => x.Title)
.HasColumnType("varchar")
.HasMaxLength(128)
.IsRequired();
}
}
// MovieContext.cs
public class MoviesContext : DbContext
{
public DbSet<Movie> Movies => Set<Movie>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new MovieMapper());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
INFO
Unfortunately, C#
doesn't allow static classes to implement interface
, it could be cleaner if just pass a delegate from static class.