You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
11 KiB
309 lines
11 KiB
using Apewer.Internals;
|
|
using Apewer;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
|
|
namespace Apewer.Source
|
|
{
|
|
|
|
/// <summary>表结构。</summary>
|
|
[Serializable]
|
|
public sealed class TableStructure
|
|
{
|
|
|
|
#region Instance
|
|
|
|
Type _model = null;
|
|
TableAttribute _table = null;
|
|
ColumnAttribute _key = null;
|
|
ColumnAttribute _flag = null;
|
|
ColumnAttribute[] _columns = null;
|
|
ColumnAttribute[] _fillable = null;
|
|
|
|
private TableStructure() { }
|
|
|
|
/// <summary>使用此结构的记录模型。</summary>
|
|
public Type Model { get => _model; }
|
|
|
|
/// <summary>表特性。</summary>
|
|
public TableAttribute Table { get => _table; }
|
|
|
|
/// <summary>列信息。</summary>
|
|
public ColumnAttribute[] Columns { get => _columns; }
|
|
|
|
/// <summary>可填充的列信息。</summary>
|
|
public ColumnAttribute[] Fillable { get => _fillable; }
|
|
|
|
/// <summary>主键。</summary>
|
|
public ColumnAttribute Key { get => _key; }
|
|
|
|
/// <summary>记录标记。</summary>
|
|
public ColumnAttribute Flag { get => _flag; }
|
|
|
|
/// <summary>从 <see cref="TableStructure"/> 到 Boolean 的隐式转换,判断 <see cref="TableStructure"/> 有效。</summary>
|
|
/// <remarks>True:存在有效结构时;<br />False:不存在有效结构。</remarks>
|
|
public static implicit operator bool(TableStructure instance)
|
|
{
|
|
if (instance == null) return false;
|
|
if (instance._model == null) return false;
|
|
if (instance._columns == null) return false;
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Obsolete
|
|
|
|
/// <summary>不依赖 Record 公共属性。</summary>
|
|
/// <remarks>注意:此属性即将弃用,应改为使用 Table.Independent 属性。</remarks>
|
|
public bool Independent { get => _table == null ? false : _table.Independent; }
|
|
|
|
/// <summary>表的说明信息。</summary>
|
|
/// <remarks>注意:此属性即将弃用,应改为使用 Table.Description 属性。</remarks>
|
|
public string Description { get => _table == null ? null : _table.Description; }
|
|
|
|
/// <summary>表特性。</summary>
|
|
/// <remarks>注意:此属性即将弃用,应改为使用 Table 属性。</remarks>
|
|
public TableAttribute Attribute { get => _table; }
|
|
|
|
/// <summary>使用模型的所有属性,对缺少 Column 特性的属性使用默认参数的 Column 特性。</summary>
|
|
/// <remarks>注意:此属性即将弃用,应改为使用 Table.AllProperties 属性。</remarks>
|
|
public bool AllProperties { get => _table == null ? false : _table.AllProperties; }
|
|
|
|
/// <summary>表名。</summary>
|
|
/// <remarks>注意:此属性即将弃用,应改为使用 Table.Name 属性。</remarks>
|
|
public string TableName { get => _table == null ? null : _table.Name; }
|
|
|
|
#endregion
|
|
|
|
#region TableStructure
|
|
|
|
private static Dictionary<string, TableStructure> _tsc = new Dictionary<string, TableStructure>();
|
|
|
|
/// <summary>解析表结构。</summary>
|
|
public static TableStructure Parse<T>(bool useCache = true, bool force = false) where T : IRecord => Parse(typeof(T), useCache, force);
|
|
|
|
/// <summary>解析表结构。</summary>
|
|
/// <returns>表结构。类型不可用于表结构时返回 NULL 值。</returns>
|
|
public static TableStructure Parse(Type model, bool useCache = true, bool force = false)
|
|
{
|
|
var type = model;
|
|
if (type == null || !type.IsClass || type.IsAbstract) return null;
|
|
|
|
// 使用缓存。
|
|
var cacheKey = type.FullName;
|
|
if (force) cacheKey = "[force] " + cacheKey;
|
|
if (useCache)
|
|
{
|
|
lock (_tsc)
|
|
{
|
|
TableStructure cached;
|
|
if (_tsc.TryGetValue(cacheKey, out cached)) return cached;
|
|
}
|
|
}
|
|
|
|
// 解析 TableAttribute。
|
|
var ta = TableAttribute.Parse(type, useCache, force);
|
|
if (!ta && !force) return null;
|
|
|
|
// 类型。
|
|
var isRecord = RuntimeUtility.IsInherits(type, typeof(Record));
|
|
var properties = type.GetProperties();
|
|
var total = properties.Length;
|
|
|
|
// 解析 ColumnAttribute。
|
|
var key = null as ColumnAttribute;
|
|
var flag = null as ColumnAttribute;
|
|
var columns = new ColumnAttribute[total];
|
|
var columnsCount = 0;
|
|
var fillable = new List<ColumnAttribute>(total);
|
|
if (total > 0)
|
|
{
|
|
var caForce = force || (ta ? ta.AllProperties : false);
|
|
var addedFields = new List<string>(total);
|
|
foreach (var property in properties)
|
|
{
|
|
var ca = ColumnAttribute.Parse(property, caForce);
|
|
if (ca != null)
|
|
{
|
|
// 检查 field 重复,只保留第一个。
|
|
var field = ca.Field;
|
|
if (!addedFields.Contains(field))
|
|
{
|
|
addedFields.Add(field);
|
|
columns[columnsCount] = ca;
|
|
columnsCount += 1;
|
|
|
|
if (isRecord)
|
|
{
|
|
if (property.Name == "Key")
|
|
{
|
|
key = ca;
|
|
if (ta != null && ta.PrimaryKey) ca.SetPrimaryKey();
|
|
}
|
|
else if (property.Name == "Flag")
|
|
{
|
|
flag = ca;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 可查询的列。
|
|
fillable.Add(ca);
|
|
continue;
|
|
}
|
|
|
|
// 可查询的列。
|
|
if (!caForce)
|
|
{
|
|
ca = ColumnAttribute.Parse(property, caForce);
|
|
if (ca) fillable.Add(ca);
|
|
}
|
|
}
|
|
}
|
|
if (columnsCount > 0 && columnsCount != columns.Length) columns = columns.Slice(0, columnsCount);
|
|
|
|
// 排序,将 Key 和 Flag 排在最前。
|
|
columns = ColumnAttribute.Sort(columns);
|
|
|
|
// 返回结果。
|
|
var ts = new TableStructure();
|
|
ts._table = ta;
|
|
ts._key = key;
|
|
ts._flag = flag;
|
|
ts._columns = columns;
|
|
ts._model = model;
|
|
ts._fillable = fillable.ToArray();
|
|
|
|
// 加入缓存。
|
|
if (useCache)
|
|
{
|
|
lock (_tsc)
|
|
{
|
|
if (!_tsc.ContainsKey(cacheKey)) _tsc.Add(cacheKey, ts);
|
|
}
|
|
}
|
|
|
|
return ts;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region TableAttribute
|
|
|
|
// 限定表名称/列名称。
|
|
static string RestrictName(string name, bool underline = false, bool english = false)
|
|
{
|
|
if (string.IsNullOrEmpty(name)) return null;
|
|
var str = name;
|
|
|
|
// 限定名称仅使用英文和数字。
|
|
if (english)
|
|
{
|
|
const string available = "_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
var chars = new ArrayBuilder<char>();
|
|
var strChars = str.ToCharArray();
|
|
var strLength = strChars.Length > 255 ? 255 : strChars.Length;
|
|
for (var i = 0; i < strLength; i++)
|
|
{
|
|
if (available.IndexOf(strChars[i]) > -1) chars.Add(strChars[i]);
|
|
}
|
|
str = new string(chars.Export());
|
|
}
|
|
|
|
// 以下划线开始。
|
|
if (underline)
|
|
{
|
|
if (!str.StartsWith("_")) str = TextUtility.Merge("_", str);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
static IDataParameter CreateParameter(object record, ColumnAttribute ca, Func<Parameter, IDataParameter> callback)
|
|
{
|
|
var property = ca.Property;
|
|
if (property == null) return null;
|
|
|
|
var getter = property.GetGetMethod();
|
|
if (getter == null) return null;
|
|
|
|
var value = getter.Invoke(record, null);
|
|
|
|
if (ca.Type == ColumnType.Bytes || ca.Type == ColumnType.Integer || ca.Type == ColumnType.Float)
|
|
{
|
|
return callback(new Parameter(ca.Field, value, ca.Type, ca.Length));
|
|
}
|
|
|
|
if (ca.Type == ColumnType.DateTime)
|
|
{
|
|
return callback(new Parameter(ca.Field, value, ca.Type, 0));
|
|
}
|
|
|
|
if (property.PropertyType.Equals(typeof(String)))
|
|
{
|
|
var text = value as string;
|
|
if (text == null) text = "";
|
|
if (ca.Length > 0)
|
|
{
|
|
switch (ca.Type)
|
|
{
|
|
case ColumnType.VarChar:
|
|
case ColumnType.NVarChar:
|
|
text = TextUtility.Left(text, ca.Length);
|
|
break;
|
|
case ColumnType.VarChar191:
|
|
case ColumnType.NVarChar191:
|
|
text = TextUtility.Left(text, 191);
|
|
break;
|
|
}
|
|
}
|
|
return callback(new Parameter(ca.Field, text, ca.Type, ca.Length));
|
|
}
|
|
|
|
var defaultText = (value == null) ? TextUtility.Empty : value.ToString();
|
|
return callback(new Parameter(ca.Field, defaultText, ca.Type, ca.Length));
|
|
}
|
|
|
|
/// <summary>生成 IDataParameter 列表,用于 Insert 和 Update 方法。</summary>
|
|
public IDataParameter[] CreateParameters(object record, Func<Parameter, IDataParameter> callback, IEnumerable<string> excludeds)
|
|
{
|
|
if (record == null || callback == null) return null;
|
|
|
|
var list = new List<IDataParameter>(_columns.Length);
|
|
foreach (var ca in Columns)
|
|
{
|
|
if (ca == null) continue;
|
|
|
|
var parameter = CreateParameter(record, ca, callback);
|
|
if (parameter == null) continue;
|
|
|
|
var add = true;
|
|
if (excludeds != null)
|
|
{
|
|
var lower = parameter.ParameterName.ToLower();
|
|
foreach (var excluded in excludeds)
|
|
{
|
|
if (string.IsNullOrEmpty(excluded)) continue;
|
|
if (lower == excluded.ToLower())
|
|
{
|
|
add = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (add) list.Add(parameter);
|
|
}
|
|
|
|
return list.ToArray();
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|