为 JObject 属性混合 EF Core Convensions 和 DBFunction
Posted
技术标签:
【中文标题】为 JObject 属性混合 EF Core Convensions 和 DBFunction【英文标题】:Mixing EF Core Convensions and DBFunction for a JObject property 【发布时间】:2019-10-09 04:53:44 【问题描述】:我有一个具有 JObject 类型属性的实体,我需要能够针对这些属性使用 DbFunctions。
当我执行时,项目抛出一个异常,指出 DbFunction 不允许 JObject 类型的参数。
实体就像...
public class OrchestrationRun
public long Id get; set;
public JObject MetaData get; set;
public JObject SystemMetaData get; set;
DbContext 看起来像...
public class MyDbContext : DbContext
public MyDbContext(DbContextOptions options)
: base(options)
public virtual DbSet<OrchestrationRun> OrchestrationRun get; set;
protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.ApplyConfiguration(new OrchestrationRunConfiguration());
// DbFunction mapping for JSON_VALUE
modelBuilder.HasDbFunction( typeof(MyDbContext).GetMethod(nameof(JsonValue)))
.HasName("JSON_VALUE")
.HasSchema("");
// DbFunction
public static string JsonValue(JObject column, [NotParameterized] string path) => throw new NotSupportedException();
OrchestrationRunConfiguration 是...
public class OrchestrationRunConfiguration : IEntityTypeConfiguration<OrchestrationRun>
public void Configure(EntityTypeBuilder<OrchestrationRun> builder)
builder.Property(e => e.MetaData).HasConversion(
jObject => jObject != null ? jObject.ToString(Formatting.None) : null,
json => string.IsNullOrWhiteSpace(json) ? null : JObject.Parse(json)
);
builder.Property(e => e.SystemMetaData).HasConversion(
jObject => jObject != null ? jObject.ToString(Formatting.None): null,
json => string.IsNullOrWhiteSpace(json) ? null : JObject.Parse(json)
);
我正在尝试执行的查询是...
var dbResponse = (from or in this.dbContext.OrchestrationRun
where MyDbContext.JsonValue(or.MetaData,"$.Product.ProductCategoryName") == "EXAMPLE"
select new
Id = or.Id,
CategoryId = "EXAMPLE"
).ToList();
注意:异常发生在 DbContext 实例化。因此查询永远不会被调用。
抛出的异常是……
System.InvalidOperationException:DbFunction 'MyDbContext.JsonValue' 的参数 'column' 具有无效类型 'JObject'。确保参数类型可以被当前提供者映射。 在 Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.ValidateDbFunctions(IModel 模型) 在 Microsoft.EntityFrameworkCore.Internal.SqlServerModelValidator.Validate(IModel 模型) 在 Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ValidatingConvention.Apply(InternalModelBuilder modelBuilder)
【问题讨论】:
看起来这两个功能,数据库函数和值转换根本不能以这种潜在有用的方式一起工作。我会在 EF Core 上创建一个问题:github.com/aspnet/EntityFrameworkCore/issues 你能把“CREATE FUNCTION Json_Value...”的SQL代码贴出来吗?您在那里定义了哪种数据类型? JSON_VALUE 是 SQL Server 的内置函数 【参考方案1】:以下代码适用于完全相同的场景..
var jsonValueMethod = typeof(MyDbContext).GetMethod(nameof(MyDbContext.JsonValue));
builder.HasDbFunction(jsonValueMethod)
.HasTranslation(args =>
return SqlFunctionExpression.Create("JSON_VALUE", args, jsonValueMethod.ReturnType, null);
)
.HasParameter("column").Metadata.TypeMapping = new StringTypeMapping("NVARCHAR(MAX)");
下面的行将JObject
列转换为NVARCHAR(MAX)
或任何您的字符串数据类型。
更新:这是 EF Core 5 及更高版本的语法。
protected override void OnModelCreating(ModelBuilder builder)
....
var jsonValueMethod = typeof(QueryExtentions).GetMethod(nameof(QueryExtentions.JsonValue));
var stringTypeMapping = new StringTypeMapping("NVARCHAR(MAX)");
builder
.HasDbFunction(jsonValueMethod)
.HasTranslation(args => new SqlFunctionExpression("JSON_VALUE", args, nullable: true, argumentsPropagateNullability: new[] false, false , jsonValueMethod.ReturnType, stringTypeMapping))
.HasParameter("column").Metadata.TypeMapping = stringTypeMapping;
.....
【讨论】:
我稍后会测试它。谢谢 @Raghu SqlFunctionExpression.Create() 有效,但它在 .NET 5 中被标记为过时。无法弄清楚如何重写它以使其工作。有什么想法吗? @Nexus .HasTranslation(args => new SqlFunctionExpression("JSON_VALUE", args, nullable: true, argumentsPropagateNullability: new[] false, false , typeof(string), null))跨度> 【参考方案2】:感谢 Raghu,您的回答对我帮助很大。对于可能来到这里并希望将转换与 json 函数混合并使用最新的 EF Core 5.0 更新 Raghu 的答案的用户:
jsonvalue函数:
public static class JsonExtensions
public static string JsonValue(object expression, string path) => throw new InvalidOperationException($"nameof(JsonValue) cannot be called client side");
在 DbContext 中 OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
var jsonValueMethodInfo = typeof(JsonExtensions).GetRuntimeMethod(nameof(JsonExtensions.JsonValue), new[] typeof(string), typeof(string) );
modelBuilder
.HasDbFunction(jsonValueMethodInfo)
.HasTranslation(args => new SqlFunctionExpression("JSON_VALUE", args, nullable: true, argumentsPropagateNullability: new[] false, false , typeof(string), null))
.HasParameter("expression").Metadata.TypeMapping = new StringTypeMapping("NVARCHAR(MAX)"); // conversion
[...]
// example of conversion of a json property
entity.Property(e => e.AdditionalProperties)
.HasColumnName("AdditionalJson")
.HasConversion(
v => Newtonsoft.Json.JsonConvert.SerializeObject(v, new Newtonsoft.Json.JsonSerializerSettings() ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore ),
v => Newtonsoft.Json.JsonConvert.DeserializeObject<AdditionalUserProperties>(v)
);
然后您就可以使用具有转换属性的扩展方法...示例:
var testId = (from u in this.Users
join e in this.Employees.IgnoreQueryFilters() on JsonExtensions.JsonValue(u.AdditionalProperties, "$." + nameof(AdditionalUserProperties.PersonalId)) equals e.PersonalId
where u.Id == userId
select e.Id).FirstOrDefault();
生成的sql:
[...][e] ON JSON_VALUE([u].[AdditionalJson], N'$.PersonalId') = [e].[PersonalId][...]
【讨论】:
以上是关于为 JObject 属性混合 EF Core Convensions 和 DBFunction的主要内容,如果未能解决你的问题,请参考以下文章
Blazor 服务器:将 EF Core DbContextFactory 与 DbContext 混合