Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF Core 2.1.0 set default string length and column type

Since Entity Framework uses nvarchar(max) as default for strings I would like to set something else as default.

https://dba.stackexchange.com/questions/48408/ef-code-first-uses-nvarcharmax-for-all-strings-will-this-hurt-query-performan

In Entity Framework 6.1.3 I could modify OnModelCreating(DbModelBuilder modelBuilder) like this:

modelBuilder.Properties<DateTime>().Configure(c => c.HasColumnType("datetime2"));
modelBuilder.Properties<DateTime>().Configure(c => c.HasPrecision(0));

modelBuilder.Properties<string>()
    .Configure(s => s.HasMaxLength(256).HasColumnType("nvarchar"));

If I then modified a property with data annotations EF used these values instead, like this:

[MaxLength(128)]
public string Name { get; set; }

[Column(TypeName = "nvarchar(MAX)")]
[MaxLength]
public string Comment { get; set; }

However using Microsoft.EntityFrameworkCore.SqlServer 2.1.0 I cant do it like this and I can't use Conventions either.

I could solve datetime like this but if I try to do the same for strings the migration says type: "nvarchar(256)", maxLength: 128 if I use data annotations for example. How can I solve this?

foreach (var property in modelBuilder.Model.GetEntityTypes()
    .SelectMany(t => t.GetProperties())
    .Where(p => p.ClrType == typeof(DateTime)))
{
    property.Relational().ColumnType = "datetime2(0)";
}
like image 286
Ogglas Avatar asked Jun 13 '18 15:06

Ogglas


1 Answers

Update (EF Core 6.0+):

The ability to specify different type mapping defaults finally has been added in EF Core 6.0 via the so called Pre-convention model configuration, so the code now would be something like this:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    base.ConfigureConventions(configurationBuilder);

    configurationBuilder.Properties<string>()
        //.AreUnicode(false)
        //.AreFixedLength()
        .HaveMaxLength(256);
}

More examples are provided in the documentation link.

Important Note: However, as mentioned by @PeterB in comments and verified in EF Core issue tracker, for some reason this configuration has higher precedence (same as model builder fluent API), so you won't be able to override these "defaults" with data annotations (fluent configuration in OnModelCreating will still do). So you might need to use the original approach below in case you rely on data annotations for overriding defaults.

Original:

There are several attributes indirectly affecting the column type of a string property - MaxLength (e.g. varchar(256) vs varchar(MAX), IsUnicode (e.g. nvarchar vs varchar) and IsFixedLength (e.g. char vs varchar).

The current model API is inconsistent. The first is accessible through GetMaxLength and SetMaxLength, the second - via IsUnicode and IsUnicode, and there is no public model API for the third one (only fluent API).

So to set the MaxLength you could use:

foreach (var property in modelBuilder.Model.GetEntityTypes()
    .SelectMany(t => t.GetProperties())
    .Where(p => p.ClrType == typeof(string)))
{
    if (property.GetMaxLength() == null)
        property.SetMaxLength(256);
}

which is not fully correct, because null in this case has dual meaning - not specified and MAX.

The correct way requires using EF Core internal API, which provides much more configuration methods, in particular allowing you to pass ConfigurationSource enum value along with the attribute value. ConfigurationSource enum values define the priority of the configuration - with Convention being the lowest, then DataAnnotation and finally Explicit being the highest. The whole idea is that the lower priority configuration do not overwrite the configuration already set by a higher priority. All public fluent API use Explcit, while in our case the Convention perfectly fits (since we are simulating conventional default).

So if you accept the warning "This API supports the Entity Framework Core infrastructure and is not intended to be used directly from your code. This API may change or be removed in future releases.", add

using Microsoft.EntityFrameworkCore.Metadata.Internal;

and use

foreach (var property in modelBuilder.Model.GetEntityTypes()
    .SelectMany(t => t.GetProperties())
    .Where(p => p.ClrType == typeof(string)))
{
    ((Property)property).Builder
        .HasMaxLength(256, ConfigurationSource.Convention);
}
like image 183
Ivan Stoev Avatar answered Oct 15 '22 23:10

Ivan Stoev