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.

247 lines
8.5 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
TableAttribute _attribute = null;
bool _independent = false;
string _name = null;
string _description = null;
bool _allprops = false;
ColumnAttribute _key = null;
ColumnAttribute _flag = null;
ColumnAttribute[] _columns = null;
Type _model = null;
private TableStructure() { }
/// <summary>不依赖 Record 公共属性。</summary>
public bool Independent { get => _independent; }
/// <summary>表名称。</summary>
public string Name { get => _name; }
/// <summary>表的说明信息。</summary>
public string Description { get => _description; }
/// <summary>使用模型的所有属性,自动对属性添加缺少的 Column 特性。</summary>
public bool AllProperties { get => _allprops; }
/// <summary>此结构的特性。</summary>
public TableAttribute Attribute { get => _attribute; }
/// <summary>使用此结构的记录模型。</summary>
public Type Model { get => _model; }
/// <summary>主键。</summary>
public ColumnAttribute Key { get => _key; }
/// <summary>列信息。</summary>
public ColumnAttribute[] Columns { get => _columns; }
#endregion
#region TableStructure
private static Dictionary<string, TableStructure> _tsc = new Dictionary<string, TableStructure>();
/// <summary>解析表结构。</summary>
public static TableStructure Parse<T>(bool useCache = true) where T : IRecord => Parse(typeof(T), useCache);
/// <summary>解析表结构。</summary>
public static TableStructure Parse(Type model, bool useCache = true)
{
var type = model;
if (type == null || !type.IsClass || type.IsAbstract) return null;
// 使用缓存。
var cacheKey = type.FullName;
if (useCache)
{
lock (_tsc)
{
TableStructure cached;
if (_tsc.TryGetValue(cacheKey, out cached)) return cached;
}
}
// 获取 Table Attribute。
var ta = TableAttribute.Parse(type);
// 遍历所有属性。
var properties = type.GetProperties();
var key = null as ColumnAttribute;
var flag = null as ColumnAttribute;
var columns = new ColumnAttribute[properties.Length];
var columnsCount = 0;
if (properties.Length > 0)
{
var addedFields = new List<string>(properties.Length);
foreach (var property in properties)
{
// 解析 ColumnAttribute,抛弃无效。
var ca = ColumnAttribute.Parse(type, property, ta);
if (ca == null) continue;
// 检查 field 重复,只保留第一个。
var field = ca.Field;
if (addedFields.Contains(field)) continue;
addedFields.Add(field);
if (property.Name == "Key") key = ca;
if (property.Name == "Flag") flag = ca;
columns[columnsCount] = ca;
columnsCount += 1;
}
}
if (columnsCount > 0 && columnsCount != columns.Length) columns = columns.Slice(0, columnsCount);
// 排序,将 Key 和 Flag 排在最前。
columns = ColumnAttribute.Sort(columns);
// 返回结果。
var ts = new TableStructure();
ts._attribute = ta;
ts._key = key;
ts._flag = flag;
ts._name = ta.Name;
ts._description = ta.Description;
ts._allprops = ta.AllProperties;
ts._independent = ta.Independent;
ts._columns = columns;
ts._model = model;
// 加入缓存。
if (useCache)
{
lock (_tsc)
{
if (!_tsc.ContainsKey(cacheKey)) _tsc.Add(cacheKey, ts);
}
}
return ts;
}
#endregion
#region TableAttribute
/// <summary>限定表名称/列名称。</summary>
/// <param name="name">名称。</param>
/// <param name="startWithUnderline">名称以下划线开始。</param>
internal static string RestrictName(string name, bool startWithUnderline)
{
if (name == null || name == Constant.EmptyString) return Constant.EmptyString;
var lower = name.ToLower();
var available = TextUtility.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 (startWithUnderline && !lower.StartsWith("_")) lower = TextUtility.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;
}
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, params 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
}
}