使用值对象时如何在EF Core中映射调用SQL Server用户定义函数
EF Core 传入值对象属性调用SQL Server自定义函数方案
问题根因
EF Core 默认不会给DbFunction参数复用实体属性上配置的值转换器,会直接校验参数类型是不是数据库提供程序支持的原生类型,所以之前两种写法要么编译时卡类型不匹配,要么运行时报参数类型无效。异常里显示的类型一个带完全限定名一个不带只是消息格式化的问题,不是报错根因,不用纠结。
配置方法
- 先调整DbContext中
GetLanguage方法的签名,第一个参数接收值对象类型,先解决编译报错问题。如果要给所有用到ML<T>的实体做通用适配,直接定义成泛型方法即可:
// 单实体场景直接用ML<CountryName>做参数类型即可 public string? GetLanguage<T>(ML<T> json, string preferredLanguages) where T : class => throw new NotSupportedException($"{nameof(GetLanguage)} 不支持客户端调用");
- 在
OnModelCreating中配置DbFunction时,通过HasTranslation手动指定SQL生成逻辑,告诉EF直接把传入的值对象属性对应到原始表列,跳过默认的类型映射检查,不需要做额外转换:
protected override void OnModelCreating(ModelBuilder modelBuilder) { var getLanguageMethod = typeof(AccountsDbContext) .GetMethod(nameof(GetLanguage), new[] { typeof(ML<CountryName>), typeof(string) })!; // 如果用泛型方法,这里取对应泛型构造的方法实例即可 modelBuilder.HasDbFunction(getLanguageMethod) .HasTranslation(args => new SqlFunctionExpression( functionName: "GetLanguage", schema: "dbo", arguments: args, nullable: true, argumentsPropagateNullability: new[] { true, false }, returnType: typeof(string), typeMapping: null ) ); }
- 之后LINQ查询直接按原有写法编写即可:
string preferredLanguages = "de-DE,en-US,fr-FR"; List<string?> list = _dbContext.Country .Select(c => _dbContext.GetLanguage(c.Name, preferredLanguages)) .ToList();
配置完成后EF Core会生成预期的SQL,直接把表的Name列作为参数传给dbo.GetLanguage,不会触发客户端求值,直接用该函数返回值做OrderBy就能实现数据库端多语言排序,完全支持分页需求。
这种写法是通用方案,只要是配置了值转换器的值对象,需要把原始列值直接传给数据库自定义函数,都可以通过自定义翻译逻辑实现,不需要修改现有值转换器配置,也不需要编写额外的类型映射规则。
内容的提问来源于stack exchange,提问作者M. Koch
相关产品推荐
相关产品推荐

