我不知道我是否期望过高,但我遇到了一些不同的事情,我尝试在OnModelCreating
和/或ConfigureConventions
中配置EF 6行为,但在创建新迁移时似乎并不受尊重。
例如,我希望强制所有字符串属性为varchar
(而不是nvarchar
)。有几十个通过模型属性处理这一问题的例子,这些属性在迁移代码生成时似乎确实受到尊重,但不幸的是,它们都在属性级别。我还看到了通过重写IsUnicode(false)
设置ConfigureConventions
的示例,如下所示:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.DefaultTypeMapping<string>()
.HasColumnType("varchar")
.IsUnicode(false);
}
但是,在迁移代码生成时,这些代码似乎没有得到尊重。它只是被忽略了。当生成查询时,我意识到它在运行时会得到尊重,但我在生成新的迁移代码时,在开发过程中特别提到了这一点。
如果在创建新的迁移代码时尊重ConfigureConventions
中的这段代码,那么如果我向模型添加了一个新的字符串属性,然后添加了一个新的迁移,那么生成的代码应该将新的列类型显示为varchar
。
UPDATE @David的repro示例请求:
我创建了一个新的.NET6核心控制台应用程序,其功能如下:
Program.cs
using ConsoleApp1;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddDbContext<MyDbContext>();
})
.Build();
host.Run();
MyDbContext.cs
using Microsoft.EntityFrameworkCore;
namespace ConsoleApp1
{
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
public DbSet<WorkRequest> WorkRequests { get; set; }
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.DefaultTypeMapping<string>()
.HasColumnType("varchar")
.IsUnicode(false);
base.ConfigureConventions(configurationBuilder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=MyDb;Trusted_Connection=True;");
}
}
}
WorkRequest.cs
namespace ConsoleApp1
{
public class WorkRequest
{
public Int64 Id { get; set; }
//[Unicode(false), MaxLength(256)]
public string SubmitterId { get; set; }
}
}
ConsoleApp1.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Migrations\" />
</ItemGroup>
</Project>
然后,我简单地放到控制台并输入:
add-migration InitialCreate -verbose
生成的迁移代码总是将SubmitterId显示为nvarchar(max),除非我使用注释属性而不是ConfigureConventions代码:
SubmitterId = table.Column<string>(type: "nvarchar(max)", nullable: false),
发布于 2022-08-30 04:39:08
当然,在迁移中,OnModelCreating
/ ConfigureConventions
中的代码是受尊重的。
您遇到的问题是DefaultTypeMapping
方法,特别是期望和它真正做的事情之间的区别。
只要看一下方法名,就会看到您所做的事情--它指定了CLR类型的默认映射。另一方面,还有一个名为Properties
的方法,它使用类似的方法返回一个构建器(从Are
/ Have
而不是Is
/Has
开始),所以不清楚区别是什么,应该使用哪一个。
DefaultTypeMapping
的文档并没有很大帮助:
将给定类型标记为标量,即使在实体类型之外使用时也是如此。这允许在不引用此类型属性的查询中使用此类型的值。 与
Properties(Type)
不同,此方法只应在不可空的具体类型上调用。在基类型上调用它将不会将配置应用于派生类型。 很少需要这样的称呼。如果存在给定类型的属性,那么在大多数情况下,调用Properties(Type)
就足够了。
它显示了行为上的细微差别,并表示很少需要使用这种方法,但没有说明原因。
文档的默认类型映射部分稍微干净一点:
通常,只要您已经为该类型的属性指定了值转换器,EF就能够使用提供程序不支持的类型的常量来转换查询。但是,在不涉及任何此类属性的查询中,EF无法找到正确的值转换器。在这种情况下,可以调用
DefaultTypeMapping
来添加或覆盖提供程序类型映射
因此,这个方法和相关的fluent配置调用只影响查询转换,而不影响实体属性映射(因此是迁移)。
很快,在ConfigureConventions
内部使用Properties
方法和fluent API配置模型默认值,它们在任何地方都会得到尊重,包括迁移,例如在您的示例中:
configurationBuilder
.Properties<string>()
//.HaveColumnType("varchar") // not needed, see below
.AreUnicode(false); // abstract way of specifying varchar vs nvarchar
作为附带说明,也要注意以下文档中的小注释
数据注释不覆盖约定前的配置。
这意味着,如果对通过Properties
配置的非默认类型的列使用数据注释(属性),则不会尊重它们(必须使用fluent API和OnModelCreating
)。这与“默认”EF核心约定有相当重要的区别,后者的优先级低于数据注释,这只是另一个例子,说明了糟糕的设计决策(对我来说,这是实现缺陷)逻辑期望之间的差异(我们只是覆盖默认约定,对吗?)以及实际的行为。
https://stackoverflow.com/questions/73533656
复制相似问题