You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于JSON构建动态类模型,实现EF Core多源API数据入库

技术思路与解决方案

针对多数据源JSON动态插入SQL的需求,核心要解决异构JSON到SQL实体的动态映射和EF Core的动态数据操作问题,以下是具体实现方案:

一、优化JSON解析与元数据提取

你当前的BindModelAsync已能将JSON转为ExpandoObject,可补充字段类型分析,为后续数据库操作提供依据:

public Task BindModelAsync(ModelBindingContext bindingContext)
{
    if (bindingContext == null)
        throw new ArgumentNullException(nameof(bindingContext));

    var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
    if (valueProviderResult == ValueProviderResult.None)
        return Task.CompletedTask;

    try
    {
        var stringValue = valueProviderResult.FirstValue;
        // 解析JSON并提取字段名、类型元数据
        using var doc = JsonDocument.Parse(stringValue);
        var root = doc.RootElement;
        var fieldTypes = new Dictionary<string, Type>();
        foreach (var property in root.EnumerateObject())
        {
            fieldTypes[property.Name] = property.Value.ValueKind switch
            {
                JsonValueKind.String => typeof(string),
                JsonValueKind.Number => property.Value.TryGetInt64(out _) ? typeof(long) : typeof(decimal),
                JsonValueKind.True or JsonValueKind.False => typeof(bool),
                JsonValueKind.Null => typeof(object),
                _ => typeof(string) // 复杂类型转为JSON字符串存储
            };
        }

        // 同时返回解析后的数据和字段元数据
        var result = JsonSerializer.Deserialize<ExpandoObject>(stringValue);
        bindingContext.Result = ModelBindingResult.Success(new { Data = result, FieldTypes = fieldTypes });
    }
    catch
    {
        bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, "无效的JSON格式");
    }

    return Task.CompletedTask;
}

二、动态数据插入的两种实现方案

方案1:EF Core原生SQL参数化插入(推荐,简单高效)

无需动态创建实体类,直接将ExpandoObject转为键值对,构建参数化INSERT语句,彻底避免SQL注入:

public async Task<int> InsertDynamicDataAsync(DbContext dbContext, string tableName, ExpandoObject data, Dictionary<string, Type> fieldTypes)
{
    var dataDict = data as IDictionary<string, object>;
    var columns = string.Join(", ", dataDict.Keys);
    var parameters = string.Join(", ", dataDict.Keys.Select(k => $"@{k}"));
    var sql = $"INSERT INTO {tableName} ({columns}) VALUES ({parameters})";

    var sqlParameters = dataDict.Select(kv =>
    {
        var param = new SqlParameter($"@{kv.Key}", kv.Value ?? DBNull.Value);
        // 匹配SQL字段类型
        param.SqlDbType = fieldTypes[kv.Key] switch
        {
            Type t when t == typeof(string) => SqlDbType.NVarChar,
            Type t when t == typeof(long) => SqlDbType.BigInt,
            Type t when t == typeof(decimal) => SqlDbType.Decimal,
            Type t when t == typeof(bool) => SqlDbType.Bit,
            _ => SqlDbType.NVarChar
        };
        return param;
    }).ToArray();

    return await dbContext.Database.ExecuteSqlRawAsync(sql, sqlParameters);
}

方案2:动态创建实体类与EF Core DbSet映射

如果需要利用EF Core的变更追踪、迁移等特性,可通过反射动态生成实体类并注册到DbContext:

1. 动态生成实体类

public Type GenerateDynamicEntityType(string entityName, Dictionary<string, Type> fieldTypes)
{
    var assemblyName = new AssemblyName($"DynamicEntities_{Guid.NewGuid()}");
    var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
    var moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule");
    var typeBuilder = moduleBuilder.DefineType(entityName, TypeAttributes.Public | TypeAttributes.Class);

    // 添加主键字段(可根据业务调整规则)
    var idField = typeBuilder.DefineField("_id", typeof(int), FieldAttributes.Private);
    var idProperty = typeBuilder.DefineProperty("Id", PropertyAttributes.Public, typeof(int), null);
    idProperty.SetGetMethod(typeBuilder.DefineMethod("GetId", MethodAttributes.Public | MethodAttributes.HideBySig, typeof(int), Type.EmptyTypes)
        .Also(m => m.GetILGenerator().Emit(OpCodes.Ldarg_0).Emit(OpCodes.Ldfld, idField).Emit(OpCodes.Ret)));
    idProperty.SetSetMethod(typeBuilder.DefineMethod("SetId", MethodAttributes.Public | MethodAttributes.HideBySig, null, new[] { typeof(int) })
        .Also(m => m.GetILGenerator().Emit(OpCodes.Ldarg_0).Emit(OpCodes.Ldarg_1).Emit(OpCodes.Stfld, idField).Emit(OpCodes.Ret)));

    // 添加JSON解析得到的字段
    foreach (var (fieldName, fieldType) in fieldTypes)
    {
        var field = typeBuilder.DefineField($"_{fieldName.ToLower()}", fieldType, FieldAttributes.Private);
        var property = typeBuilder.DefineProperty(fieldName, PropertyAttributes.Public, fieldType, null);
        property.SetGetMethod(typeBuilder.DefineMethod($"Get{fieldName}", MethodAttributes.Public | MethodAttributes.HideBySig, fieldType, Type.EmptyTypes)
            .Also(m => m.GetILGenerator().Emit(OpCodes.Ldarg_0).Emit(OpCodes.Ldfld, field).Emit(OpCodes.Ret)));
        property.SetSetMethod(typeBuilder.DefineMethod($"Set{fieldName}", MethodAttributes.Public | MethodAttributes.HideBySig, null, new[] { fieldType })
            .Also(m => m.GetILGenerator().Emit(OpCodes.Ldarg_0).Emit(OpCodes.Ldarg_1).Emit(OpCodes.Stfld, field).Emit(OpCodes.Ret)));
    }

    return typeBuilder.CreateType()!;
}

2. 动态注册到DbContext并插入数据

public async Task InsertWithDynamicEntityAsync(DbContext dbContext, string tableName, ExpandoObject data, Dictionary<string, Type> fieldTypes)
{
    var entityType = GenerateDynamicEntityType(tableName, fieldTypes);
    // 实例化动态实体并赋值
    var entity = Activator.CreateInstance(entityType);
    var dataDict = data as IDictionary<string, object>;
    foreach (var (key, value) in dataDict)
    {
        entityType.GetProperty(key)?.SetValue(entity, value);
    }

    // 动态获取DbSet并添加数据
    var dbSetMethod = typeof(DbContext).GetMethod("Set", Type.EmptyTypes)!.MakeGenericMethod(entityType);
    var dbSet = dbSetMethod.Invoke(dbContext, null);
    var addMethod = dbSet!.GetType().GetMethod("Add")!;
    addMethod.Invoke(dbSet, new[] { entity });

    await dbContext.SaveChangesAsync();
}

三、关键注意事项

  • SQL注入防护:必须使用参数化查询,禁止直接拼接字段值到SQL语句中
  • 字段类型匹配:需根据JSON值类型与SQL表字段类型做对应映射,避免类型转换错误
  • 表结构一致性:如果数据源JSON结构多变,可考虑动态创建表(生成CREATE TABLE语句),但需注意数据库权限与性能
  • 性能考量:动态生成实体类会有一定性能开销,高并发场景优先选择方案1的原生SQL插入

内容的提问来源于stack exchange,提问作者Arul Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 15:34:57