Daily Knowledge Drop
Entity Framework comes out of the box with a number of default conventions - however new conventions can easily be added
to an application specific EF configuration to customize how Entity Framework operates.
Scenario
By default, when a string
property on an entity is mapped to a SQL database column, it will be generated as nvarchar(max)
.
Consider the following class:
[Table("Blog")]
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime DateCreated { get; set; }
}
And the DbContext configuration, some additional logging for demo purposes:
public class DemoContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
@"Server=.\SQLEXPRESS;Database=ConfigConventions;Integrated Security=True;TrustServerCertificate=True");
// enable additional logging for demo purposes
optionsBuilder.EnableSensitiveDataLogging(true);
optionsBuilder.LogTo((string query) =>
{
Console.WriteLine(query);
}, LogLevel.Information);
}
}
The CREATE TABLE SQL generated is as follows:
CREATE TABLE [Blog] (
[Id] int NOT NULL IDENTITY,
[Title] nvarchar(max) NOT NULL,
[Description] nvarchar(max) NOT NULL,
[DateCreated] datetime2 NOT NULL,
CONSTRAINT [PK_Blog] PRIMARY KEY ([Id])
);
As one can see, the default for C# properties of type string
is nvarchar(max)
- next we'll go through the various steps to change the convention for non-explicitly set string properties from nvarchar(max)
to nvarchar(256)
.
Convention definition
The first step is to define the convention to change the default string length. This is achieved by implementing the IModelFinalizingConvention
interface (which inherits the IConvention
interface). The IModelFinalizingConvention
implementations, as the name suggests, are executed once the model has mostly been built (using the built-in and other custom conventions), and is being finalized. This is the "safest" time to execute custom conventions.
public class MaxStringLengthConvention : IModelFinalizingConvention
{
private readonly int _maxLength;
public MaxStringLengthConvention(int maxLength)
{
this._maxLength = maxLength;
}
// implement the only method on the interface
public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder,
IConventionContext<IConventionModelBuilder> context)
{
// iterate through each type defined in the EF model
// and through each property and check if a string
foreach (var property in modelBuilder.Metadata.GetEntityTypes()
.SelectMany(entityType => entityType.GetDeclaredProperties()
.Where( property => property.ClrType == typeof(string))))
{
// set the max length based on the value passed in
property.Builder.HasMaxLength(_maxLength);
}
}
}
The implementation for this convention effectively checks each type and each property on the type to see if it is of type string - if so, the max length is set based on the max length specified on initialization.
Now to register the newly created convention!
Convention configuration
The convention is required to be registered with Entity Framework - so EF knows to apply the convention to the model (adn when to apply it)
The ConfigureConventions
method is overridden on the DbContext, and the convention(s) registered:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.Add(_ => new MaxStringLengthConvention(256));
}
Generated SQL
With the convention configured, the SQL generated now looks as follows:
CREATE TABLE [Blog] (
[Id] int NOT NULL IDENTITY,
[Title] nvarchar(256) NOT NULL,
[Description] nvarchar(256) NOT NULL,
[DateCreated] datetime2 NOT NULL,
CONSTRAINT [PK_Blog] PRIMARY KEY ([Id])
);
All string properties/columns are set to have a length of 256 by default.
Explicitly set properties
Entity Framework is smart enough to know that if the max length has explicitly been set on a property/column, then the default length set by the convention will not be applied
.
If the Title property on the Blog was set to have a max length of 500:
[Table("Blog")]
public class Blog
{
public int Id { get; set; }
[MaxLength(500)]
public string Title { get; set; }
public string Description { get; set; }
public DateTime DateCreated { get; set; }
}
The SQL generated is now as follows:
CREATE TABLE [Blog] (
[Id] int NOT NULL IDENTITY,
[Title] nvarchar(500) NOT NULL,
[Description] nvarchar(256) NOT NULL,
[DateCreated] datetime2 NOT NULL,
CONSTRAINT [PK_Blog] PRIMARY KEY ([Id])
);
An explicitly defined max length on a property, takes precedence over the max length specified by the convention.
Notes
While all of the above could have been achieved by overwriting the OnModelCreating
method on the DbContext, the conventions approach is more flexible and reusable. The conventions can be packaged and shared across teams in an enterprise to ensure consistent database standards.
References
.NET Data Community Standup - EF7 Custom Model Conventions
Daily Drop 224: 16-12-2022
At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.
The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.