您的位置:首页 > 数据库

数据库扩展表设计过程记录

2019-05-27 23:16 1956 查看

  这两天需要实现一个动态表单设计,面对着属性的不确定,要能够容纳不同的属性进来,之前也接触过这方面的设计,但是没有设计好,导致问题太多,这一次参考一些前辈们的经验后,再次尝试一番,通过动态设计表结构,以达到任务要求。

 

一、常用动态表结构设计方式

  1、动态修改表,适应变化。

  2、预留字段实现动态表结构(伪动态)。

  3、将动态属性全部保存在一个字段中,xml或是json格式保存(版本号+通用列)。

  4、表结构和表数据分离,xml形式分别保存表结构和表数据。

  5、横向表转纵向表(属性字段行存储)。

  对于这几种方式,或许不同的选用适用不同的形式,我选择了最后一种来实现我现有的设计,这种方式个人感觉更加灵活,可以更方便的扩展属性(适合的才是最好的)。

 

二、横向表转纵向表初步设计

  首先看下横向表的设计,如果采用横向表,因为业务的需要,要容纳好几种行业的信息进来,这样一来整张表的字段数将会非常多,从设计或是维护角度来讲,这都是一个棘手的芋头,因此传统的横向表设计不能满足现有的需求了。

  

  按照横向表转纵向表的思路对表结构进行更改,通过设置成键值对形式,得到如下表结构。

  

   在这里可能有一个情况得想清楚了,我们每一次增加一条记录的时候,记录内的信息是作为同一批添加进来的,反过来,当我从数据库中取出数据时,也应该需要把同一批的记录信息取出来,因此在上面的设计中再加入一个GroupId用来区分同一批次的数据,而至于属性的重复量很大,之后将进行优化处理。

   

  现在看到这个结构时,对于动态扩充属性来讲,已经是达到了我的预期了,对于数据库设计时,将检测指标及限值均设置成字符串的,分组号我采用时间戳的形式进行存储,当然也可以采用其它更为稳妥的方式,如Guid或是自定义ID等。

  对于好多检测指标名称出现重复情况,我将这部分单独抽出来一张表用于存储检测指标属性,需要注意的是此处的名称需要在某个检测项目编号下唯一,该部分属性先在界面上呈现,其次呈现对应的数据,如果某列增加或删除了,对应展示行也就空着了一个数据或是消失了一个数据单元,而对于改了指标名称或是对外的展示名称,都不会影响数据的存储,通过默认值可以使得有些常用值不要再二次输入,减少工作量。

   

  在具体检测限值中完成对检测指标的关联,加入一列完成外键关联,同时对原有表内存在的列可以进行优化,因为这些信息都在检测指标中存在了,不必要的数据冗余还是不存在为好。单从现在的表结构来看,当我们按照在增加一些额外的属性时,可以做到不要去修改表结构,而只需要对表内数据进行管理即可。

   

 

三、代码实现过程

  此处我采用Asp.Net Core MVC并利用Razor语法,更为方便的完成表单展示工作,当然对于这部分工作,采用js或模板等等都是可以快速完成的。

  1、首先对于界面添加检测指标的设计,遵循普通的表单设计方式即可,此处增加了两个隐藏元素,为适用于编辑场景而存在,此处快速略过提交到后台并保存到数据库的过程,可能需要在后台验证提交的名称的唯一性。

<div class="layui-fluid">
<form class="layui-form" lay-filter="layuiadmin-form-evaluationIndex" style="padding: 15px 0 0 0;">
<input type="hidden" name="id" value="@Model.Id" />
<input type="hidden" name="evaluationStandardId" value="@Model.EvaluationStandardId" />
<div class="layui-form-item">
<label class="layui-form-label">名称</label>
<div class="layui-input-block">
<input type="text" name="name" lay-verify="required" placeholder="请输入名称" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">显示名称</label>
<div class="layui-input-block">
<input type="text" name="displayName" lay-verify="required" placeholder="请输入显示名称" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">默认值</label>
<div class="layui-input-block">
<input type="text" name="defaultValue" placeholder="请输入默认值" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-hide" lay-submit lay-filter="LAY-evaluationIndex-front-submit" id="LAY-evaluationIndex-front-submit">立即提交</button>
</div>
</div>
</form>
</div>

   2、对于增加具体的检测记录,需要先读取到整个检测项目下的所有检测指标,然后实现生成表单的过程,按照如下的思路一步一步实现:

   

  对于第一步,从数据库获取指定检测项目的检测指标,该步可以直接利用提供的id做一次查询即可得到相应的指标集合。然后在前端循环输出时,利用Razor语法完成动态渲染Html,生成label和input元素,依照之前设计检测指标时的name唯一,可以在此处设计表单时指定name属性。

<div class="layui-fluid">
<form class="layui-form" lay-filter="layuiadmin-form-evaluationLimitValue" style="padding: 15px 0 0 0;">
@foreach (var item in Model)
{
<div class="layui-form-item">
<label class="layui-form-label">@item.DisplayName</label>
<div class="layui-input-block">
<input type="text" name="@item.Name" value="@item.DefaultValue" placeholder="@("请输入"+item.DisplayName)" autocomplete="off" class="layui-input">
</div>
</div>
}
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-hide" lay-submit lay-filter="LAY-evaluationLimitValue-front-submit" id="LAY-evaluationLimitValue-front-submit">立即提交</button>
</div>
</div>
</form>
</div>

   接下来可以完成表单的输入工作了,并提交到后台完成保存到数据库中,如我此处,新增记录时,保存到数据库前,先生成分组号,以此来区分这些指标下的数据是一个批次的,然后完成保存到数据库的过程。

public async Task ConvertTableToEvaluationLimitValues(TestItemCode_EvaluationStandardSubItem assignTestItemCode, Dictionary<string, string> evaluationLimitValues)
{
var groupId = DateTimeHelper.GetTimeStamp();
var evaluationIndexes = assignTestItemCode.EvaluationStandardSubItem.EvaluationStandard.EvaluationIndexes.ToList();

foreach (var item in evaluationLimitValues)
{
var evaluationIndex = evaluationIndexes.Where(e => e.Name == item.Key).FirstOrDefault();
if (evaluationIndex == null) continue;

var evaluationLimitValue = new EvaluationLimitValue(evaluationIndex.Id, assignTestItemCode.Id, item.Value, groupId);
await _evaluationLimitValueRepository.InsertAsync(evaluationLimitValue);
}
}

   对于这部分的设计,做一点更改也适用于更新操作,但是得注意到,更新表单时,表单上展示的检测指标可能存在增加或是删除的情形,因此对于存在的记录我们可以展示出来,不存在的则留空,当提交到数据库时,需要做一次比对过程,对那部分增加的检测指标需要保存到数据库中,当然对于已有的检测指标也存在变更的可能,因此需要做一次判断,当有变更时更新,没有时不处理。

public async Task UpdateEvaluationLimitValues(TestItemCode_EvaluationStandardSubItem assignTestItemCode, string groupId, Dictionary<string, string> evaluationLimitValues)
{
//获取当前的检测指标
var evaluationIndexes = assignTestItemCode.EvaluationStandardSubItem.EvaluationStandard.EvaluationIndexes.ToList();

//目标分组已存在的检测限值
var results = await _evaluationLimitValueRepository.GetAll()
.Where(e => e.TestItemCode_EvaluationStandardSubItemId == assignTestItemCode.Id && e.GroupId == groupId)
.Include(e => e.EvaluationIndex).ToListAsync();

//已存在的检测限值对应于检测指标名称列表
var existedEvaluationLimitValueNameList = results.Select(r => r.EvaluationIndex.Name).ToList();

//需新增的检测限值记录
var addEvaluationLimitValueList = evaluationLimitValues.Keys.Except(existedEvaluationLimitValueNameList).ToList();
foreach (var key in addEvaluationLimitValueList)
{
var evaluationIndexId = evaluationIndexes.Where(e => e.Name == key).FirstOrDefault().Id;
var evaluationLimitValue = new EvaluationLimitValue(evaluationIndexId, assignTestItemCode.Id, evaluationLimitValues[key], groupId);
await _evaluationLimitValueRepository.InsertAsync(evaluationLimitValue);

//移除记录
evaluationLimitValues.Remove(key);
}

//更新已有检测限值记录值
foreach (var key in evaluationLimitValues.Keys)
{
var editEvaluationLimitValues = results.Where(r => r.EvaluationIndex.Name == key && r.LimitValue != evaluationLimitValues[key]).FirstOrDefault();
if (editEvaluationLimitValues != null)
{
editEvaluationLimitValues.LimitValue = evaluationLimitValues[key];
await _evaluationLimitValueRepository.UpdateAsync(editEvaluationLimitValues);
}
}
}

   3、完成检测记录的表格展示,此处需要遵循一个原则,就是先展示检测指标,也就是先展示属性列,其次展示数据值,只有相应的属性列存在,展示的数据值才有意义,通过指定的编号Id获取相应的属性集合并展示在前端,利用Razor语法循环输出th元素,来产生表格行头。

<div class="layui-col-xs12">
<table class="layui-table"
lay-data="{height: 'full-100', id:'mainList'}"
lay-filter="list" lay-size="xs">
<thead>
<tr>
<th lay-data="{checkbox:true, fixed: true}"></th>
@foreach (var item in Model)
{
<th lay-data="{field:'@item.Name'}">@Html.Raw(item.DisplayName)</th>
}
<th lay-data="{fixed:'right', width:240, align:'center', toolbar: '#barList'}"></th></tr>
</thead>
</table>
</div>

   表格展示完毕时便是数据开始呈现的时机,通过获取检测限值中的记录,注意这里的记录会有多条存在的,我们需要将纵向表结构转换成横向的json格式,用于前端读取,注意这里得把分组号加入进来,这是属于同一批次的标识。

  

  通过纵向转横向,可以得到字典类型的list集合,然后再返回前序列化成json格式,便是前端需要的格式。

public async Task<List<Dictionary<string, string>>> ConvertEvaluationLimitValuesToTable(Guid assignedTestItemCodeId)
{
//分组后的评价限值
var results = await _evaluationLimitValueRepository.GetAll()
.Where(e => e.TestItemCode_EvaluationStandardSubItemId == assignedTestItemCodeId)
.Include(e => e.EvaluationIndex)
.GroupBy(e => e.GroupId).ToListAsync();

List<Dictionary<string, string>> convertResultList = new List<Dictionary<string, string>>();

foreach (var result in results)
{
Dictionary<string, string> tempResultList = new Dictionary<string, string>
{
{ EvaluationLimitValue.GetGroupIdName(), result.ElementAt(0).GroupId }//增加分组号GroupId
};

foreach (var item in result)
{
tempResultList.Add(item.EvaluationIndex.Name, item.LimitValue);
}

convertResultList.Add(tempResultList);
}

return convertResultList;
}

 

四、设计实现效果

  1、实现动态增加属性列,尽管没有太丰富的功能,但是已经满足我现有的需求了,或许还能依据此得到更复杂的表单属性列设计。

  

  2、增加表单具体值,依据增加的属性列完成相应值填写。

  

  3、再次增加属性列后增加表单具体值,实现属性列的增加删除和修改后,仍然可以适用而无需手动修改表结构。

  

  至此,动态表单的简单设计工作已经完成,过程较为简单,没有融入更多的比如多选,单选、数值型的设计,纯字符类型设计工作。

 

2019-05-27,望技术有成后能回来看见自己的脚步
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐