有没有一种方法可以从动态生成的字段(字段名称、字段类型)创建SQL表



我正在开发一个用户不知道内容的应用程序。所以我使用的是动态内容。我为我的控件创建了一个名为"控件细节"的模型

public class ControlDetails
{
public string ControlName{ get; set; }
public string Type { get; set; }
public string Label { get; set; }
public bool IsRequired { get; set; }

}

用户填充数据库,我将这些数据作为表单呈现到我的Blazor/Razor UI中。但是,我想将表单中填写的数据提交到表中。

模型中的一个示例记录是

ControlName => Lastname;
Type => Textbox;
Label => Enter Firstname ;
IsRequired => Yes;

这是由用户动态创建的

如何动态创建对应的SQL表,当填写UI表单时,数据将提交到该表?例如

create table [Table Name]
(
ControlName Type
ControlName Type
ControlName Type
)

我一直在开发一个带有动态内容的Blazor项目,使管理员用户能够控制存储的内容。我的设置是在PHP API和MySQL上,以节省成本,但我认为这个想法也应该适用于您。

您希望避免让用户直接在数据库中创建表,而我所做的是让数据库存储动态内容的元数据。

因此,在数据库中,我制作了这样的表格:TableMetaData-用于存储动态表元数据的表ColumnsMetaData-一个表,用于存储不同列的元数据、它们的数据类型等TableColumnsMap-一个控制每个表使用哪些列的映射表,使不同的动态表可以共享列类型DataRow-一个存储动态表中所有数据行的表。

DataRow表的表定义:

  • 表:DataRow
  • DatarowId-int(主键部分(
  • TableId-int(主键部分(
  • ReadOnly-bool(是否应为在UI中可编辑(
  • UpdatedBy-userid(如果出现问题可能对
  • LastUpdate-date(主键部分(每次更新都会在此表中创建一个新行;lastupdate";日期时间
  • 活动-bool(这是为了改善数据流,我只让最新的更新是活动的,这是为了避免必须检查";最大lastupdate"(
  • IsDeleted-bool(允许删除和还原列。如果最新的活动行版本已删除,其已删除。(

最后一张表是:

  • DataCell-用于存储行的reach列中的数据的表。DataCell表的列
  • DatarowId-int-数据行的ID,它是主键部分
  • TableId-int-该数据行所属的表,它是第二个主键部分。它指的是表元数据表
  • ColumnId-int-它所属的列,此Id指的是columnmetadata表
  • CellValue-字符串-存储的数据。根据列元数据及其定义的数据类型,我将根据情况进行不同的强制转换
  • UpdatedBy-userid-对用户数据表的引用
  • LastUpdate-date-最后一个主要的关键部分,这是存储这个单元格发生的事情的完整历史记录
  • Active-bool-与数据行表相同,只有最新版本设置为活动
  • IsDeleted-bool-与数据行表相同,如果单元格已被删除,则为true

这是我在Blazor中用于显示此数据的代码示例:

<table class="table" style="table-layout: auto; height: auto; padding:0px; border-collapse:collapse; border-spacing: 0px;">
<thead style="padding: 0px;">
<tr style="padding: 3px;">
@foreach (DataColumn col in tableView.Table.Columns)
{
<th @onclick="@(
() =>
{
Console.WriteLine(col.ColumnName + " ASC");
if(tableView.Sort == col.ColumnName + " ASC")
tableView.Sort = col.ColumnName + " DESC";
else
tableView.Sort = col.ColumnName + " ASC";
EventManager.AddRenderAction(() => StateHasChanged());
}
)" style="padding: 3px; cursor: s-resize;">@col.ColumnName</th>
}
</tr>
</thead>
<tbody>
@{
isReadOnly = false; // ApiService.IsAdmin ? false : true;
int rowindex = 0;
}
@foreach (DataRowView rowView in tableView) // for (int r = 0; r < tableView.Table.Rows.Count; r++)
{
if (OnlyShowChanges && !rowView.Row.GetChangedColumns().Where(c => c.ColumnName != "Vælg").Any())
continue;
DataRow row = rowView.Row;
int gid = groupId;
if (pageSize * pageIndex > rowindex || (pageSize * (pageIndex + 1) - 1) < rowindex)
{
rowindex++;
continue;
}
rowindex++;
if (groupId != 1 && ApiService.mUserGroups.Where(g => groupName != ((TableDropDownColumn)row.ItemArray[2]).SelectedItem).Any())
continue;
int r = tableData.Rows.IndexOf(row);
<tr bgcolor="white">
@for (int c = 0; c < tableView.Table.Rows[r].ItemArray.Length; c++)
{
var cell = tableView.Table.Rows[r].ItemArray[c];
var col = tableView.Table.Columns[c];
string cellvalue = Convert.ToString(tableView.Table.Rows[r].ItemArray[c]);
Type cellType = tableView.Table.Columns[c].DataType;
isReadOnly = col.ReadOnly;
//cellType = BusDataProps.GetValueOrDefault(tableView.Table.Columns[c].ColumnName);
bool nullable = false;
if (cellType.IsGenericType && cellType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
nullable = true;
cellType = Nullable.GetUnderlyingType(cellType);
}
if (cellType == typeof(TableDropDownColumn))
{
string selectedDropDownValue = "Tom";
int colno = c;
if (tableView.Table.Rows[r].ItemArray.Any())
selectedDropDownValue = ((TableDropDownColumn)tableView.Table.Rows[r].ItemArray[c]).SelectedItem;
<td style="padding: 0px; border-collapse:collapse;">
<select NAME='@col.ColumnName' readonly="@isReadOnly" @onchange="@((ChangeEventArgs __e) => UpdateDropDown(__e,r,colno))" style="background-color:transparent;" value=@selectedDropDownValue>
@foreach (var item in ((TableDropDownColumn)tableView.Table.Rows[r].ItemArray[c]).DropDownItems)
{
if (@isReadOnly && item != selectedDropDownValue)
continue;
<option value=@item>@item</option>
@*<option value="lightblue" style="background-color:lightblue;">Blå</option>
<option value="yellow" style="background-color:yellow;">Gul</option>
<option value="white" style="background-color:whitesmoke;">Hvid</option>*@
}
@if (!tableView.Table.Rows[r].ItemArray.Any())
{
<option value="Tom">Tom</option>
}
</select>
</td>
}
else if (cellType == typeof(string))
{
p_rows = @cellvalue.Trim().Length > 30 ? @cellvalue.Trim().Length / 30 : p_rows = 1;
s_rows = @cellvalue.Trim().Length > 30 ? @cellvalue.Trim().Length / 30 : s_rows = 1;
<td style="padding: 0px; border-collapse:collapse;">
<textarea cols="10" rows="@p_rows" @onchange="@((ChangeEventArgs __e) => row.SetField<string>(col, __e.Value.ToString()))" value="@cellvalue"
readonly="@isReadOnly" style="background-color:transparent; resize:both; min-height:22px; overflow: hidden; padding: 3px; border:none;" wrap="hard" />
</td>
}
else if (cellType == typeof(int))
{
<td style="background-color:transparent; padding:0px">
<input type="number" pattern="/^-?d+.?d*$/" readonly="@isReadOnly" onKeyPress="if(this.value.length==4) return false;" style="background-color:transparent; border:none; padding: 3px; width: 4em" value="@cellvalue"
@onchange="@((ChangeEventArgs __e) => row.SetField<int>(col, int.Parse(__e.Value.ToString())) )" size="4" max="9999" id="number" />
</td>
}
else if (cellType == typeof(DateTime))
{
<td style="background-color:transparent; padding:0px">
@if ((nullable && ((DateTime?)cell).HasValue && ((DateTime?)cell).Value.ToString("yyyy") != "0001"))
{
<input type="date" readonly="@isReadOnly" style="background-color:transparent; border:none; padding: 3px; width:10em" value="@(((DateTime?)cell).Value.ToString("yyyy-MM-dd"))"
@onchange="@((ChangeEventArgs __e) =>row.SetField<DateTime?>(col,(DateTime?)__e.Value))" />
}
else if (!nullable && !string.IsNullOrWhiteSpace(cellvalue))
{
<input type="date" readonly="@isReadOnly" style="background-color:transparent; border:none; padding: 3px; width:10em" value="@(((DateTime)cell).ToString("yyyy-MM-dd"))"
@onchange="@((ChangeEventArgs __e) =>row.SetField<DateTime>(col,(DateTime)__e.Value))" />
}
else
{
<input type="date" readonly="@isReadOnly" style="background-color:lightyellow; border:none; padding: 3px; width:10em" value="@DateTime.Today.ToString("yyyy-MM-dd")"
@onchange="@((ChangeEventArgs __e) =>ChangeDate(row, col, __e))" />//
}
</td>
}
else if (cellType == typeof(bool))
{
<td style="padding: 0px;">
@if (!string.IsNullOrWhiteSpace(cellvalue) && Convert.ToBoolean(tableView.Table.Rows[r].ItemArray[c]))
{
<input type="checkbox" style="background-color:transparent; border:none; width:6em; padding: 3px" checked
@onchange="@((ChangeEventArgs __e) =>row.SetField<bool>(col,(bool)__e.Value))" />
}
else
{
<input type="checkbox" style="background-color:transparent; border:none; width:6em; padding: 3px"
@onchange="@((ChangeEventArgs __e) =>row.SetField<bool>(col,(bool)__e.Value))" />
}
</td>
}
else
{
<td style="padding: 0px;">
<textarea cols="40" rows="@p_rows" value="No value found type is @cellType.ToString()"
readonly="@isReadOnly" style="background-color:transparent; resize:both; min-height:22px; overflow: hidden; padding: 3px; border:none;" wrap="hard" />
</td>
}
}
</tr>
}
</tbody>
</table>

因此,我将所有的表数据存储在C#数据表中,但以自己的方式使用它。这是显示数据的地方,也是我制作列编辑器的另一个地方,用户可以在这里创建自己的列。一列类型用于下拉菜单。