OpenRobot.Framework.DynamicModel 1.3.7

dotnet add package OpenRobot.Framework.DynamicModel --version 1.3.7
                    
NuGet\Install-Package OpenRobot.Framework.DynamicModel -Version 1.3.7
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="OpenRobot.Framework.DynamicModel" Version="1.3.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OpenRobot.Framework.DynamicModel" Version="1.3.7" />
                    
Directory.Packages.props
<PackageReference Include="OpenRobot.Framework.DynamicModel" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add OpenRobot.Framework.DynamicModel --version 1.3.7
                    
#r "nuget: OpenRobot.Framework.DynamicModel, 1.3.7"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package OpenRobot.Framework.DynamicModel@1.3.7
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=OpenRobot.Framework.DynamicModel&version=1.3.7
                    
Install as a Cake Addin
#tool nuget:?package=OpenRobot.Framework.DynamicModel&version=1.3.7
                    
Install as a Cake Tool

OpenRobot.Framework.DynamicModel

NuGet .NET

OpenRobot.Framework.DynamicModel 是一个 .NET 8.0 类库,用于在强类型 DTO 和动态属性模型之间进行双向转换。主要应用于数据模型的扁平化存储和恢复场景。

特性

  • DTO 到 OpenModel: 将强类型 DTO 转换为扁平化的 OpenModel 集合,便于数据库存储
  • OpenModel 到 Entity: 将 OpenModel 集合转换为 ORM 实体,用于数据库操作
  • IOpenModel 到自定义对象: 通过基于特性的灵活配置,将 IOpenModel 集合映射回自定义对象
  • 模板同步: 支持模板新增属性的智能同步,自动保持 ParentID 编号的连续性
  • 嵌套对象支持: 无缝处理复杂的嵌套对象和集合
  • 验证元数据: 保留源 DTO 的验证规则和显示属性
  • 自定义转换器: 使用 ICustomApressConverter 扩展自定义转换逻辑

安装

dotnet add package OpenRobot.Framework.DynamicModel

快速开始

1. 定义 DTO

using OpenRobot.Framework.DynamicModel;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

public class UserDto : IDynamicTemplateDto
{
    [MainKey]
    public int Id { get; set; }

    [Description("用户名")]
    public string Name { get; set; }

    [RegularExpression(@"^\d{6}$", ErrorMessage = "必须是6位数字")]
    public string Code { get; set; }

    public AddressDto Address { get; set; }
    public List<PhoneDto> Phones { get; set; }
}

public class AddressDto
{
    public string City { get; set; }
    public string Street { get; set; }
}

2. 转换 DTO 为 OpenModel

var dto = new UserDto
{
    Id = 1,
    Name = "张三",
    Code = "123456",
    Address = new AddressDto { City = "北京", Street = "长安街" }
};

// 转换为扁平化的 OpenModel 集合
var models = dto.ModelToDynamicProperties(mainId: 1);

3. 转换 OpenModel 为数据库实体

// 定义实现 IDynamicPropEntity 的实体
public class DynamicProperty : IDynamicPropEntity
{
    public int Id { get; set; }
    public int DynamicPropertyId { get; set; }
    public string PropertyName { get; set; }
    public string Value { get; set; }
    // ... 其他属性
}

// 转换并保存到数据库
var entities = models.OpenModelConvertDynamicPropEntity<DynamicProperty>();
await dbContext.DynamicProperties.AddRangeAsync(entities);
await dbContext.SaveChangesAsync();

4. 映射回自定义对象

基础映射示例
基础映射示例
public class UserViewModel : ICustomPropConvert
{
    [CustomPropMap("Name")]
    public string UserName { get; set; }

    [CustomPropMap("Address", HasObject = true)]
    public AddressViewModel Address { get; set; }
}

public class AddressViewModel : ICustomPropConvert
{
    [CustomPropMap("City")]
    public string CityName { get; set; }
}

// 从数据库加载并映射回
var openModels = await dbContext.DynamicProperties
    .Where(m => m.DynamicPropertyId == 1)
    .ToListAsync();

var viewModel = new UserViewModel();
viewModel.OpenDynamicPropertryBuild(openModels);
使用 PropDetailNode 获取属性元数据
public class UserViewModel : ICustomPropConvert
{
    [CustomPropMap("Name")]
    public string UserName { get; set; }

    // PropDetailNode 会自动填充属性的元数据(标题、验证规则等)
    [CustomPropMap("Code")]
    public PropDetailNode CodeDetail { get; set; }

    [CustomPropMap("Email")]
    public PropDetailNode EmailDetail { get; set; }
}

// 从数据库加载并映射
var viewModel = new UserViewModel();
viewModel.OpenDynamicPropertryBuild(openModels);

// 访问属性值和元数据
string code = viewModel.CodeDetail.Value;              // 属性值
string title = viewModel.CodeDetail.Title;             // 显示标题
string regex = viewModel.CodeDetail.Regex;             // 验证规则
string errorMsg = viewModel.CodeDetail.ErrorMessage;   // 错误消息

// 用于前端验证展示
var validationRules = new
{
    Code = new
    {
        Value = viewModel.CodeDetail.Value,
        Title = viewModel.CodeDetail.Title,
        Regex = viewModel.CodeDetail.Regex,
        ErrorMessage = viewModel.CodeDetail.ErrorMessage
    }
};
使用索引过滤(1.3.0+ 新增)

当需要从数组中提取特定元素时,可以使用带 index 参数的重载方法:

// 场景:从包含多个地址的集合中提取第一个地址
var openModels = await dbContext.DynamicProperties
    .Where(m => m.DynamicPropertyId == 1)
    .ToListAsync();

// 只提取 Index = 0 的元素(第一个地址)
var firstAddress = new AddressViewModel();
firstAddress.OpenDynamicPropertryBuild(openModels, index: 0);

// 只提取 Index = 1 的元素(第二个地址)
var secondAddress = new AddressViewModel();
secondAddress.OpenDynamicPropertryBuild(openModels, index: 1);

注意事项

  • 使用 index 参数时,过滤逻辑会同时匹配 PropertyNameIndex
  • 不使用 index 参数时,默认匹配所有 Index 的元素
  • 嵌套对象内部的映射会自动启用索引过滤,确保父子关系正确

特性说明

特性 用途
[MainKey] 标记主键字段(使用 DynamicPropertyId
[KeyString] 标记需要 JSON 序列化的复杂类型
[PropIngore] 排除属性不参与转换
[CustomOpenConvert] 指定自定义转换器(实现 ICustomApressConverter
[CustomPropMap] 反向转换时映射属性名称

高级用法

模板同步 (Template Sync)

当模板对象新增属性后,使用 DynamicTemplateSyncExecutor 同步到数据库:

using OpenRobot.Framework.DynamicModel;

// 场景:数据库中已有部分属性
var existingEntities = await dbContext.DynamicProperties
    .Where(m => m.DynamicPropertyId == 1)
    .ToListAsync();

// 现有属性:Name (ParentID: #01), Age (ParentID: #02)

// 创建同步执行器
var syncExecutor = new DynamicTemplateSyncExecutor<UserDto, DynamicProperty>();

// 执行同步
var syncResult = syncExecutor.Sync(mainId: 1, entities: existingEntities);

// 处理结果
// 1. 新增属性(ParentID 从 #03 开始)
foreach (var newEntity in syncResult.InsertRanges)
{
    Console.WriteLine($"新增属性: {newEntity.PropertyName}, ParentID: {newEntity.ParentID}");
}

await dbContext.DynamicProperties.AddRangeAsync(syncResult.InsertRanges);

// 2. 匹配的现有属性 ID
Console.WriteLine($"匹配的属性: {string.Join(", ", syncResult.MatchRanges)}");

// 3. 删除过期属性(可选)
var obsoleteEntities = syncExecutor.FindObsoleteEntities(
    mainId: 1,
    entities: existingEntities,
    matchedIds: syncResult.MatchRanges
);

if (obsoleteEntities.Count > 0)
{
    dbContext.DynamicProperties.RemoveRange(obsoleteEntities);
}

await dbContext.SaveChangesAsync();

ParentID 编号规则

  • 现有属性保持其原始 ParentID
  • 新增属性从现有数据的最大索引继续编号(如现有最大是 #02,新增从 #03 开始)
  • 嵌套属性也遵循相同规则,在各自层级内保持编号连续性

自定义转换器

public class DateTimeConverter : ICustomApressConverter
{
    public string PropertyConvert(object? value)
    {
        if (value is DateTime dt)
            return dt.ToString("yyyy-MM-dd");
        return "";
    }

    public object? ApressConvert(IList<OpenModel> models)
    {
        // 自定义转换逻辑
        return null;
    }
}

public class EventDto : IDynamicTemplateDto
{
    [CustomOpenConvert(typeof(DateTimeConverter))]
    public DateTime EventDate { get; set; }
}

选择性属性转换

// 仅转换指定属性
var models = dto.ModelToDynamicProperties(mainId: 1, "Name", "Address");

嵌套对象转换

// 带父 ID 和属性前缀的嵌套转换
var models = dto.ModelToDynamicProperties(mainId: 1, parentId: "P01", propNamePreTag: "Parent");

数据结构

OpenModel 使用扁平化结构表示嵌套对象:

public class OpenModel : IOpenModel
{
    public int DynamicPropertyId { get; set; }  // 主实体 ID
    public string PropertyName { get; set; }     // 点号表示路径(如 "Address.City")
    public string Value { get; set; }            // 序列化值
    public string ParentID { get; set; }         // 十六进制层级路径
    public int Index { get; set; }               // 数组元素索引
    public bool IsBaseType { get; set; }         // 基元类型标志
    public bool IsArray { get; set; }            // 数组标志
    public int Level { get; set; }               // 嵌套层级深度(自动计算)
    public string Title { get; set; }            // 来自 Description 的显示名
    public string Regex { get; set; }            // 验证正则表达式
    public string ErrorMessage { get; set; }     // 验证错误消息
}

Level 字段说明

  • Level 表示属性的嵌套层级深度,根据 ParentID 自动计算
  • 例如:ParentID = "#01"Level = 1(顶层属性)
  • 例如:ParentID = "#01,#00"Level = 2(嵌套一层)
  • 例如:ParentID = "#01,#00,#02"Level = 3(嵌套两层)

版本要求

  • .NET 8.0 或更高版本

构建和发布

# 构建
dotnet build

# 打包
dotnet pack

# 发布到 NuGet
nuget push *.nupkg -Source https://api.nuget.org/v3/index.json

许可证

Copyright © OpenRobot

版本历史

1.3.7 (2026-06-23)

  • 新增 GeneralPropUpdateDtoUpdatePropItemDto:将更新操作的数据模型从 DynamicGeneralService 迁移至核心库,现在这两个类型直接位于 OpenRobot.Framework.DynamicModel 命名空间下,便于跨项目复用
  • 新增 Model/UpdatePropItemDto.cs 文件,GeneralPropUpdateDto 包含 Items(变更列表)和 DynamicPropertyId(父实体 ID)

1.3.6 (2026-06-16)

  • 修复集合元素 ParentID 计算错误导致重复新增DynamicTemplateSyncExecutor.ProcessEnumerableTypeWithExistingParentID 中复杂类型集合元素(List<T>)的 ParentID 误用 GetMaxChildIndexForParentID 计算的全局最大值而非元素循环索引 elementIndex,导致第二次同步时元素 1+ 的 ParentID 与首轮不一致,被判定为新属性从而产生多余 InsertCount。改为始终使用循环索引 elementIndex 构建 ParentID,保证幂等同步。

1.3.5 (2026-06-15)

  • **修复数组 ParentID 问题

1.3.4 (2026-05-24)

  • **修复数组 OpenModelConvertDynamicPropEntity转换方法无法添加正确的数据

1.3.3 (2026-05-12)

  • 修复数组删除后构建数量缺失问题:修正数组层级深度计算逻辑,使用 ParentID(逗号分隔)替代 PropertyName 计算数组深度,确保删除数组元素后仍能正确构建子元素数量
  • 修复过滤条件叠加错误:修正 parentID 过滤条件错误地始终包含 Index 检查的问题,改为按需叠加过滤条件

1.3.0 (2026-05-06)

  • 新增 Level 字段IOpenModelOpenModelIDynamicPropEntity 接口新增 Level 属性,自动根据 ParentID 计算嵌套层级深度
  • 索引过滤改进OpenDynamicPropertryBuild 方法新增重载,支持更精确的数组元素索引过滤
  • 模板同步增强DynamicTemplateSyncExecutor 创建实体时自动计算并设置 Level 字段

1.2.0

  • 新增 DynamicTemplateSyncExecutor 模板同步功能,支持智能 ParentID 编号

1.1.0

  • 重构架构,简化转换流程,新增 ICustomPropConvert 支持

1.0.0

  • 初始版本
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on OpenRobot.Framework.DynamicModel:

Package Downloads
OpenRobot.Framework.FormControl

基于数据驱动的 WPF 动态表单构建与渲染引擎,支持 12 种控件类型、级联下拉、条件显示、跨字段校验和多列布局

OpenRobot.Framework.DynamicGeneralService

动态属性管理服务库,提供动态属性 CRUD 操作、实体与 DTO 双向转换、嵌套属性树管理

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.3.7 193 6/23/2026
1.3.6 117 6/16/2026
1.3.5 114 6/16/2026
1.3.4 105 5/24/2026
1.3.3 109 5/12/2026
1.3.2 109 5/11/2026
1.3.1 102 5/7/2026
1.3.0 101 5/6/2026
1.2.1 109 4/24/2026
1.2.0 138 4/16/2026
1.1.0 115 4/15/2026
1.0.0 119 4/13/2026