數據庫擴展表設計過程記錄

  這兩天須要實現一個動態表單設計,面對着屬性的不肯定,要可以容納不一樣的屬性進來,以前也接觸過這方面的設計,可是沒有設計好,致使問題太多,這一次參考一些前輩們的經驗後,再次嘗試一番,經過動態設計表結構,以達到任務要求。前端

 

1、經常使用動態表結構設計方式

  一、動態修改表,適應變化。java

  二、預留字段實現動態表結構(僞動態)。數據庫

  三、將動態屬性所有保存在一個字段中,xml或是json格式保存(版本號+通用列)。json

  四、表結構和表數據分離,xml形式分別保存表結構和表數據。async

  五、橫向錶轉縱向表(屬性字段行存儲)。數據庫設計

  對於這幾種方式,或許不一樣的選用適用不一樣的形式,我選擇了最後一種來實現我現有的設計,這種方式我的感受更加靈活,能夠更方便的擴展屬性(適合的纔是最好的)。ide

 

2、橫向錶轉縱向表初步設計

  首先看下橫向表的設計,若是採用橫向表,由於業務的須要,要容納好幾種行業的信息進來,這樣一來整張表的字段數將會很是多,從設計或是維護角度來說,這都是一個棘手的芋頭,所以傳統的橫向表設計不能知足現有的需求了。優化

  

  按照橫向錶轉縱向表的思路對錶結構進行更改,經過設置成鍵值對形式,獲得以下表結構。ui

  

   在這裏可能有一個狀況得想清楚了,咱們每一次增長一條記錄的時候,記錄內的信息是做爲同一批添加進來的,反過來,當我從數據庫中取出數據時,也應該須要把同一批的記錄信息取出來,所以在上面的設計中再加入一個GroupId用來區分同一批次的數據,而至於屬性的重複量很大,以後將進行優化處理。lua

   

  如今看到這個結構時,對於動態擴充屬性來說,已是達到了個人預期了,對於數據庫設計時,將檢測指標及限值均設置成字符串的,分組號我採用時間戳的形式進行存儲,固然也能夠採用其它更爲穩妥的方式,如Guid或是自定義ID等。

  對於好多檢測指標名稱出現重複狀況,我將這部分單獨抽出來一張表用於存儲檢測指標屬性,須要注意的是此處的名稱須要在某個檢測項目編號下惟一,該部分屬性先在界面上呈現,其次呈現對應的數據,若是某列增長或刪除了,對應展現行也就空着了一個數據或是消失了一個數據單元,而對於改了指標名稱或是對外的展現名稱,都不會影響數據的存儲,經過默認值可使得有些經常使用值不要再二次輸入,減小工做量。

   

  在具體檢測限值中完成對檢測指標的關聯,加入一列完成外鍵關聯,同時對原有表內存在的列能夠進行優化,由於這些信息都在檢測指標中存在了,沒必要要的數據冗餘仍是不存在爲好。單從如今的表結構來看,當咱們按照在增長一些額外的屬性時,能夠作到不要去修改表結構,而只須要對錶內數據進行管理便可。

   

 

3、代碼實現過程

  此處我採用Asp.Net Core MVC並利用Razor語法,更爲方便的完成表單展現工做,固然對於這部分工做,採用js或模板等等都是能夠快速完成的。

  一、首先對於界面添加檢測指標的設計,遵循普通的表單設計方式便可,此處增長了兩個隱藏元素,爲適用於編輯場景而存在,此處快速略過提交到後臺並保存到數據庫的過程,可能須要在後臺驗證提交的名稱的惟一性。

<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>

   二、對於增長具體的檢測記錄,須要先讀取到整個檢測項目下的全部檢測指標,而後實現生成表單的過程,按照以下的思路一步一步實現:

   

  對於第一步,從數據庫獲取指定檢測項目的檢測指標,該步能夠直接利用提供的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);
        }
    }
}

   三、完成檢測記錄的表格展現,此處須要遵循一個原則,就是先展現檢測指標,也就是先展現屬性列,其次展現數據值,只有相應的屬性列存在,展現的數據值纔有意義,經過指定的編號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;
}

 

4、設計實現效果

  一、實現動態增長屬性列,儘管沒有太豐富的功能,可是已經知足我現有的需求了,或許還能依據此獲得更復雜的表單屬性列設計。

  

  二、增長表單具體值,依據增長的屬性列完成相應值填寫。

  

  三、再次增長屬性列後增長表單具體值,實現屬性列的增長刪除和修改後,仍然能夠適用而無需手動修改表結構。

  

  至此,動態表單的簡單設計工做已經完成,過程較爲簡單,沒有融入更多的好比多選,單選、數值型的設計,純字符類型設計工做。

 

2019-05-27,望技術有成後能回來看見本身的腳步
相關文章
相關標籤/搜索