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.
456 lines
17 KiB
456 lines
17 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
|
|
{
|
|
|
|
private bool _locked = false;
|
|
|
|
private string _tablename = Constant.EmptyString;
|
|
private bool _independent = false;
|
|
|
|
private Dictionary<string, ColumnAttribute> _columns = new Dictionary<string, ColumnAttribute>();
|
|
|
|
internal TableStructure() { }
|
|
|
|
/// <summary>不依赖 Record 公共属性。</summary>
|
|
public bool Independent
|
|
{
|
|
get { return _independent; }
|
|
private set { _independent = value; }
|
|
}
|
|
|
|
/// <summary>表名称。</summary>
|
|
public string Table
|
|
{
|
|
get { return _tablename; }
|
|
private set { _tablename = value ?? ""; }
|
|
}
|
|
|
|
/// <summary>列信息。</summary>
|
|
public Dictionary<string, ColumnAttribute> Columns
|
|
{
|
|
get
|
|
{
|
|
if (_locked)
|
|
{
|
|
var copy = new Dictionary<string, ColumnAttribute>(_columns.Count);
|
|
foreach (var c in _columns) copy.Add(c.Key, c.Value);
|
|
return copy;
|
|
}
|
|
return _columns;
|
|
}
|
|
private set { _columns = value; }
|
|
}
|
|
|
|
/// <summary>锁定属性,阻止修改。</summary>
|
|
public void Lock()
|
|
{
|
|
_locked = true;
|
|
foreach (var c in _columns) c.Value.Lock();
|
|
}
|
|
|
|
#region cache
|
|
|
|
private static Dictionary<string, TableStructure> _tsc = new Dictionary<string, TableStructure>();
|
|
|
|
private static Dictionary<string, TableAttribute> _tac = new Dictionary<string, TableAttribute>();
|
|
|
|
#endregion
|
|
|
|
#region static
|
|
|
|
/// <summary></summary>
|
|
/// <exception cref="System.Exception"></exception>
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
|
public static TableStructure ParseModel(object entity, bool useCache = true)
|
|
{
|
|
if (entity == null) throw new ArgumentNullException("参数无效");
|
|
var type = entity.GetType();
|
|
var result = ParseModel(type, useCache);
|
|
return result;
|
|
}
|
|
|
|
/// <summary></summary>
|
|
/// <exception cref="System.Exception"></exception>
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
|
public static TableStructure ParseModel<T>(bool useCache = true) where T : IRecord
|
|
{
|
|
return ParseModel(typeof(T), useCache);
|
|
}
|
|
|
|
/// <summary></summary>
|
|
/// <exception cref="System.Exception"></exception>
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
|
public static TableStructure ParseModel(Type model, bool useCache = true)
|
|
{
|
|
var type = model;
|
|
if (type == null) throw new ArgumentNullException("参数无效");
|
|
|
|
// 使用缓存。
|
|
var cacheKey = type.FullName;
|
|
if (useCache)
|
|
{
|
|
var hint = null as TableStructure;
|
|
lock (_tsc)
|
|
{
|
|
if (_tsc.ContainsKey(cacheKey))
|
|
{
|
|
hint = _tsc[cacheKey];
|
|
}
|
|
}
|
|
if (hint != null) return hint;
|
|
}
|
|
|
|
// 检查基类。
|
|
// if (type.BaseType.FullName.Equals(typeof(DatabaseRecord).FullName) == false) return "基类不受支持。";
|
|
|
|
// 检查 Attribute。
|
|
var ta = ParseTable(type);
|
|
|
|
// 获取所有属性。
|
|
var properties = type.GetProperties();
|
|
if (properties.LongLength < 1L) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 不包含属性。"));
|
|
|
|
// Record 根类属性名。
|
|
var roots = GetRootProperties();
|
|
|
|
// 检查字段定义。键:属性名称。
|
|
var columns = new Dictionary<string, ColumnAttribute>();
|
|
foreach (var property in properties)
|
|
{
|
|
var ca = ParseColumn(type, property);
|
|
if (ca == null) continue;
|
|
|
|
// 检查冗余。
|
|
foreach (var column in columns)
|
|
{
|
|
if (column.Value.Field == ca.Field)
|
|
{
|
|
throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 的列名称存在冗余。"));
|
|
}
|
|
}
|
|
|
|
// 检查基类。
|
|
if (roots.Contains(ca.Property.Name)) ca.Independent = true;
|
|
|
|
columns.Add(property.Name, ca);
|
|
}
|
|
// if (columns.Count < 1) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 不包含可用的列。"));
|
|
|
|
// 排序。
|
|
columns = SortColumns(columns);
|
|
|
|
// 返回结果。
|
|
var ts = new TableStructure();
|
|
ts.Table = ta.Name;
|
|
ts.Independent = ta.Independent;
|
|
ts.Columns = columns;
|
|
|
|
// 锁定属性。
|
|
ts.Lock();
|
|
|
|
// 加入缓存。
|
|
if (useCache)
|
|
{
|
|
lock (_tsc)
|
|
{
|
|
if (!_tsc.ContainsKey(cacheKey))
|
|
{
|
|
_tsc.Add(cacheKey, ts);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ts;
|
|
}
|
|
|
|
/// <summary></summary>
|
|
/// <param name="type"></param>
|
|
/// <exception cref="Exception"></exception>"
|
|
public static TableAttribute ParseTable<T>(bool useCache = true) where T : IRecord
|
|
{
|
|
return ParseTable(typeof(T), useCache);
|
|
}
|
|
|
|
/// <summary></summary>
|
|
/// <param name="type"></param>
|
|
/// <exception cref="Exception"></exception>"
|
|
public static TableAttribute ParseTable(Type type, bool useCache = true)
|
|
{
|
|
// 使用缓存。
|
|
var cacheKey = type.FullName;
|
|
if (useCache)
|
|
{
|
|
var hint = null as TableAttribute;
|
|
lock (_tac)
|
|
{
|
|
if (_tac.ContainsKey(cacheKey))
|
|
{
|
|
hint = _tac[cacheKey];
|
|
}
|
|
}
|
|
if (hint != null) return hint;
|
|
}
|
|
|
|
var tas = type.GetCustomAttributes(typeof(TableAttribute), false);
|
|
if (tas.LongLength < 1L) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 不包含 ", typeof(TableAttribute).FullName, "。"));
|
|
if (tas.LongLength > 1L) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 包含多个 ", typeof(TableAttribute).FullName, "。"));
|
|
|
|
var ta = (TableAttribute)tas[0];
|
|
if (TextVerifier.IsBlank(ta.Name))
|
|
{
|
|
ta = new TableAttribute("_" + type.Name);
|
|
if (TextVerifier.IsBlank(ta.Name)) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 的表名称无效。"));
|
|
}
|
|
|
|
ta.Independent = ClassUtility.ContainsAttribute<IndependentAttribute>(type, true);
|
|
|
|
// 锁定属性。
|
|
ta.Lock();
|
|
|
|
// 加入缓存。
|
|
if (useCache)
|
|
{
|
|
lock (_tac)
|
|
{
|
|
if (!_tac.ContainsKey(cacheKey))
|
|
{
|
|
_tac.Add(cacheKey, ta);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ta;
|
|
}
|
|
|
|
/// <summary></summary>
|
|
/// <exception cref="Exception">Exception</exception>"
|
|
public static ColumnAttribute ParseColumn(Type type, PropertyInfo property)
|
|
{
|
|
// 检查 Attributes。
|
|
var cas = property.GetCustomAttributes(typeof(ColumnAttribute), false);
|
|
if (cas.LongLength < 1L) return null;
|
|
if (cas.LongLength > 1L) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 包含多个 ", typeof(ColumnAttribute).FullName, "。"));
|
|
var ca = (ColumnAttribute)cas[0];
|
|
|
|
// 检查列名称。
|
|
if (TextVerifier.IsBlank(ca.Field))
|
|
{
|
|
ca = new ColumnAttribute("_" + property.Name, ca.Type, ca.Length, true);
|
|
if (TextVerifier.IsBlank(ca.Field)) throw new Exception(TextGenerator.Merge("类 ", type.FullName, "中,属性 ", property.Name, " 的列名称无效。"));
|
|
}
|
|
|
|
// 检查属性方法。
|
|
var getter = property.GetGetMethod(false);
|
|
var setter = property.GetSetMethod(false);
|
|
if (getter == null) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 不支持获取。"));
|
|
if (setter == null) throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 不支持设置。"));
|
|
|
|
// 类型兼容。
|
|
var pt = property.PropertyType;
|
|
if (pt.Equals(typeof(byte[]).FullName)) ca.Type = ColumnType.Binary;
|
|
else if (pt.Equals(typeof(Byte))) ca.Type = ColumnType.Integer;
|
|
else if (pt.Equals(typeof(SByte))) ca.Type = ColumnType.Integer;
|
|
else if (pt.Equals(typeof(Int16))) ca.Type = ColumnType.Integer;
|
|
else if (pt.Equals(typeof(UInt16))) ca.Type = ColumnType.Integer;
|
|
else if (pt.Equals(typeof(Int32))) ca.Type = ColumnType.Integer;
|
|
else if (pt.Equals(typeof(UInt32))) ca.Type = ColumnType.Integer;
|
|
else if (pt.Equals(typeof(Int64))) ca.Type = ColumnType.Integer;
|
|
else if (pt.Equals(typeof(Single))) ca.Type = ColumnType.Float;
|
|
else if (pt.Equals(typeof(Double))) ca.Type = ColumnType.Float;
|
|
else if (pt.Equals(typeof(Decimal))) ca.Type = ColumnType.Float;
|
|
else if (pt.Equals(typeof(DateTime))) ca.Type = ColumnType.DateTime;
|
|
else if (pt.Equals(typeof(String)))
|
|
{
|
|
switch (ca.Type)
|
|
{
|
|
case ColumnType.Binary:
|
|
case ColumnType.Integer:
|
|
case ColumnType.Float:
|
|
case ColumnType.DateTime:
|
|
//throw new Exception(TextGenerator.Merge("类 ", type.FullName, " 中,属性 ", property.Name, " 的类型不受支持。"));
|
|
ca.Type = ColumnType.NText;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ca.Type = ColumnType.NText;
|
|
}
|
|
|
|
ca.Property = property;
|
|
|
|
// 锁定属性。
|
|
ca.Lock();
|
|
|
|
return ca;
|
|
}
|
|
|
|
/// <summary>排序。</summary>
|
|
public static Dictionary<string, ColumnAttribute> SortColumns(Dictionary<string, ColumnAttribute> columns)
|
|
{
|
|
// if (type.BaseType.FullName.Equals(typeof(Record).FullName)) // 仅当使用基类时排序。
|
|
|
|
var sorted = new Dictionary<string, ColumnAttribute>();
|
|
|
|
if (columns.ContainsKey("Created")) sorted.Add("Created", columns["Created"]);
|
|
if (columns.ContainsKey("Updated")) sorted.Add("Updated", columns["Updated"]);
|
|
if (columns.ContainsKey("Flag")) sorted.Add("Flag", columns["Flag"]);
|
|
if (columns.ContainsKey("Remark")) sorted.Add("Remark", columns["Remark"]);
|
|
if (columns.ContainsKey("Key")) sorted.Add("Key", columns["Key"]);
|
|
|
|
foreach (var property in columns.Keys)
|
|
{
|
|
if (property == "Created") continue;
|
|
if (property == "Updated") continue;
|
|
if (property == "Flag") continue;
|
|
if (property == "Remark") continue;
|
|
if (property == "Key") continue;
|
|
|
|
sorted.Add(property, columns[property]);
|
|
}
|
|
|
|
return sorted;
|
|
}
|
|
|
|
/// <summary>限定表名称/列名称。</summary>
|
|
public static string RestrictName(string name, bool underline)
|
|
{
|
|
if (name == null || name == Constant.EmptyString) return Constant.EmptyString;
|
|
var lower = name.ToLower();
|
|
var available = TextGenerator.Merge("_", Constant.NumberCollection, Constant.LowerCollection);
|
|
var sb = new StringBuilder();
|
|
foreach (var c in lower)
|
|
{
|
|
if (available.IndexOf(c) >= 0) sb.Append(c);
|
|
}
|
|
lower = sb.ToString();
|
|
if (underline && !lower.StartsWith("_")) lower = TextGenerator.Merge("_", lower);
|
|
while (lower.Length > 2 && lower.StartsWith("__")) lower = lower.Substring(1);
|
|
if (lower == "_" || lower == Constant.EmptyString) return Constant.EmptyString;
|
|
if (lower.Length > 255) lower = lower.Substring(0, 255);
|
|
return lower;
|
|
}
|
|
|
|
private static IDataParameter GenerateDataParameter(Record entity, ColumnAttribute attribute, CreateDataParameterCallback callback)
|
|
{
|
|
var property = attribute.Property;
|
|
if (property == null) return null;
|
|
|
|
var getter = property.GetGetMethod();
|
|
if (getter == null) return null;
|
|
|
|
var parameter = null as IDataParameter;
|
|
var value = getter.Invoke(entity, null);
|
|
|
|
//
|
|
if (attribute.Type == ColumnType.Binary || attribute.Type == ColumnType.Integer || attribute.Type == ColumnType.Float)
|
|
{
|
|
var temp = value;
|
|
if (property.PropertyType.FullName == typeof(Decimal).FullName)
|
|
{
|
|
temp = TextUtility.GetDouble(temp.ToString());
|
|
}
|
|
parameter = callback(new Parameter(attribute.Field, temp, attribute.Type, attribute.Length));
|
|
}
|
|
else if (attribute.Type == ColumnType.DateTime)
|
|
{
|
|
parameter = callback(new Parameter(attribute.Field, value, attribute.Type, 0));
|
|
}
|
|
else if (property.PropertyType.Equals(typeof(String)))
|
|
{
|
|
var text = value as string;
|
|
if (text == null) text = "";
|
|
if (attribute.Length > 0)
|
|
{
|
|
switch (attribute.Type)
|
|
{
|
|
case ColumnType.VarChar:
|
|
case ColumnType.NVarChar:
|
|
text = TextUtility.RestrictLength(text, attribute.Length);
|
|
break;
|
|
case ColumnType.VarChar255:
|
|
case ColumnType.NVarChar255:
|
|
text = TextUtility.RestrictLength(text, 255);
|
|
break;
|
|
}
|
|
}
|
|
parameter = callback(new Parameter(attribute.Field, text, attribute.Type, attribute.Length));
|
|
}
|
|
else
|
|
{
|
|
var text = (value == null) ? TextUtility.EmptyString : value.ToString();
|
|
parameter = callback(new Parameter(attribute.Field, text, attribute.Type, attribute.Length));
|
|
}
|
|
return parameter;
|
|
}
|
|
|
|
/// <summary>生成 IDataParameter 列表,用于 Insert 或 Update。</summary>
|
|
/// <exception cref="ArgumentNullException"></exception>
|
|
public List<IDataParameter> CreateDataParameters(Record entity, CreateDataParameterCallback callback, params string[] excluded)
|
|
{
|
|
if (entity == null) throw new ArgumentNullException("argEntity");
|
|
if (callback == null) throw new ArgumentNullException("argCallback");
|
|
|
|
entity.FixProperties();
|
|
|
|
var list = new List<IDataParameter>();
|
|
foreach (var column in Columns)
|
|
{
|
|
var attribute = column.Value;
|
|
if (ParseTable(entity.GetType()).Independent && attribute.Independent) continue;
|
|
|
|
var parameter = GenerateDataParameter(entity, attribute, callback);
|
|
if (parameter == null) continue;
|
|
|
|
var add = true;
|
|
foreach (var exclude in excluded)
|
|
{
|
|
var lower = parameter.ParameterName.ToLower();
|
|
if (lower == exclude.ToLower())
|
|
{
|
|
add = false;
|
|
}
|
|
}
|
|
if (add) list.Add(parameter);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region
|
|
|
|
/// <summary>获取 Record 根类中的属性名称。</summary>
|
|
private static List<string> GetRootProperties()
|
|
{
|
|
var list = new List<string>();
|
|
var type = typeof(Record);
|
|
var properties = type.GetProperties();
|
|
foreach (var property in properties)
|
|
{
|
|
if (ClassUtility.ContainsAttribute<ColumnAttribute>(property, false))
|
|
{
|
|
list.Add(property.Name);
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|
|
|