diff --git a/Apewer.Source/Source/MySql.cs b/Apewer.Source/Source/MySql.cs index 1105d75..ce4f9eb 100644 --- a/Apewer.Source/Source/MySql.cs +++ b/Apewer.Source/Source/MySql.cs @@ -400,7 +400,7 @@ namespace Apewer.Source var columns = new List(structure.Columns.Length); var columnsAdded = 0; - var primarykey = null as string; + var primarykeys = new List(); foreach (var column in structure.Columns) { // 检查 Independent 特性。 @@ -409,11 +409,14 @@ namespace Apewer.Source // 字段。 var type = Declaration(column); if (type.IsEmpty()) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); + if (!column.Independent) + { + if (column.PrimaryKey) primarykeys.Add(column.Field); + if (column.Incremental) type += " auto_increment"; + } + columns.Add(type); columnsAdded++; - - // 主键。 - if (column.Property.Name == "Key") primarykey = column.Field; } columns.Capacity = columnsAdded; var table = structure.Name; @@ -421,14 +424,8 @@ namespace Apewer.Source // 设置主键。 string sql; - if (!structure.Independent && !string.IsNullOrEmpty(primarykey)) - { - sql = TextUtility.Merge("create table `", table, "`(", joined, ", primary key (", primarykey, ") ) engine=innodb default charset=utf8mb4; "); - } - else - { - sql = TextUtility.Merge("create table `", table, "`(", joined, ") engine=innodb default charset=utf8mb4; "); - } + var sqlPrimaryKey = primarykeys.Count < 1 ? "" : (", primary key (" + string.Join(", ", primarykeys.ToArray()) + ")"); + sql = TextUtility.Merge("create table `", table, "`(", joined, sqlPrimaryKey, ") engine=innodb default charset=utf8mb4; "); return sql; } @@ -487,7 +484,15 @@ namespace Apewer.Source if (string.IsNullOrEmpty(table)) table = structure.Name; if (string.IsNullOrEmpty(table)) return "表名称无效。"; - var ps = structure.CreateParameters(record, Parameter); + // 排除字段。 + var excluded = new List(); + foreach (var ca in structure.Columns) + { + // 排除不使用 ORM 的属性。 + if (ca.Independent || ca.Incremental) excluded.Add(ca.Field); + } + + var ps = structure.CreateParameters(record, Parameter, excluded); var psc = ps.Length; if (psc < 1) return "数据模型不包含字段。"; @@ -522,7 +527,16 @@ namespace Apewer.Source if (string.IsNullOrEmpty(table)) table = structure.Name; if (string.IsNullOrEmpty(table)) return "表名称无效。"; - var ps = structure.CreateParameters(record, Parameter, "_key"); + // 排除字段。 + var excluded = new List(); + if (structure.Key != null) excluded.Add(structure.Key.Field); + foreach (var ca in structure.Columns) + { + // 排除不使用 ORM 的属性、自增属性和主键属性。 + if (ca.Independent || ca.Incremental || ca.PrimaryKey) excluded.Add(ca.Field); + } + + var ps = structure.CreateParameters(record, Parameter, excluded); var psc = ps.Length; if (psc < 1) return "数据模型不包含字段。"; diff --git a/Apewer.Source/Source/SqlClient.cs b/Apewer.Source/Source/SqlClient.cs index 24e0190..995381b 100644 --- a/Apewer.Source/Source/SqlClient.cs +++ b/Apewer.Source/Source/SqlClient.cs @@ -1,4 +1,4 @@ -/* 2021.12.01 */ +/* 2021.12.07 */ using Apewer; using System; @@ -311,6 +311,39 @@ namespace Apewer.Source } } + /// 批量插入,必须在 DataTable 中指定表名,或指定 tableName 参数。 + /// + /// + public void BulkCopy(DataTable table, string tableName = null) + { + // 检查 table 参数。 + if (table == null) throw new ArgumentNullException(nameof(table)); + if (table.Rows.Count < 1) return; + + // 检查 tableName 参数。 + if (tableName.IsEmpty()) tableName = table.TableName; + if (tableName.IsEmpty()) throw new ArgumentException("未指定表名。"); + + // 连接数据库。 + var connect = Connect(); + if (connect.NotEmpty()) throw new Exception(connect); + + // 批量插入。 + var bc = null as SqlBulkCopy; + try + { + bc = new SqlBulkCopy(_db); + bc.DestinationTableName = tableName; + bc.BatchSize = table.Rows.Count; + bc.WriteToServer(table); + } + catch (Exception ex) + { + try { bc.Close(); } catch { } + throw ex; + } + } + #endregion #region ORM @@ -566,11 +599,16 @@ COLLATE Chinese_PRC_CI_AS if (structure.Independent && column.Independent) continue; var type = Declaration(column); - if (!column.Independent && column.Property.Name == "Key") type = type + " primary key"; + if (!column.Independent) + { + if (column.PrimaryKey) type = type + " primary key"; + if (column.Incremental) type += " identity"; + } if (string.IsNullOrEmpty(type)) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); sqlcolumns.Add(type); } + if (sqlcolumns.Count < 1) return $"无法对类型 {model.FullName} 创建表:没有定义任何字段。"; var sql = TextUtility.Merge("create table [", structure.Name, "](", string.Join(", ", sqlcolumns.ToArray()), "); "); var execute = Execute(sql); if (execute.Success) return TextUtility.Empty; @@ -589,7 +627,15 @@ COLLATE Chinese_PRC_CI_AS if (string.IsNullOrEmpty(table)) table = structure.Name; if (string.IsNullOrEmpty(table)) return "表名称无效。"; - var ps = structure.CreateParameters(record, Parameter); + // 排除字段。 + var excluded = new List(); + foreach (var ca in structure.Columns) + { + // 排除不使用 ORM 的属性。 + if (ca.Independent || ca.Incremental) excluded.Add(ca.Field); + } + + var ps = structure.CreateParameters(record, Parameter, excluded); var psc = ps.Length; if (psc < 1) return "数据模型不包含字段。"; @@ -625,7 +671,16 @@ COLLATE Chinese_PRC_CI_AS if (string.IsNullOrEmpty(table)) table = structure.Name; if (string.IsNullOrEmpty(table)) return "表名称无效。"; - var ps = structure.CreateParameters(record, Parameter, "_key"); + // 排除字段。 + var excluded = new List(); + if (structure.Key != null) excluded.Add(structure.Key.Field); + foreach (var ca in structure.Columns) + { + // 排除不使用 ORM 的属性、自增属性和主键属性。 + if (ca.Independent || ca.Incremental || ca.PrimaryKey) excluded.Add(ca.Field); + } + + var ps = structure.CreateParameters(record, Parameter, excluded); var psc = ps.Length; if (psc < 1) return "数据模型不包含字段。"; @@ -756,6 +811,9 @@ COLLATE Chinese_PRC_CI_AS var vtype = SqlDbType.BigInt; switch (type) { + case ColumnType.Boolean: + vtype = SqlDbType.Bit; + break; case ColumnType.Bytes: vtype = SqlDbType.Image; break; @@ -853,6 +911,9 @@ COLLATE Chinese_PRC_CI_AS var length = Math.Max(0, vcolumn.Length); switch (vcolumn.Type) { + case ColumnType.Boolean: + type = "bit"; + break; case ColumnType.Integer: type = "bigint"; break; diff --git a/Apewer.Source/Source/SqlClientThin.cs b/Apewer.Source/Source/SqlClientThin.cs index 7776c14..1e2ba9f 100644 --- a/Apewer.Source/Source/SqlClientThin.cs +++ b/Apewer.Source/Source/SqlClientThin.cs @@ -194,7 +194,7 @@ namespace Apewer.Source if (string.IsNullOrEmpty(table)) table = structure.Name; if (string.IsNullOrEmpty(table)) return "表名称无效。"; - var ps = structure.CreateParameters(record, Parameter); + var ps = structure.CreateParameters(record, Parameter, null); var psc = ps.Length; if (psc < 1) return "数据模型不包含字段。"; @@ -230,7 +230,7 @@ namespace Apewer.Source if (string.IsNullOrEmpty(table)) table = structure.Name; if (string.IsNullOrEmpty(table)) return "表名称无效。"; - var ps = structure.CreateParameters(record, Parameter, "_key"); + var ps = structure.CreateParameters(record, Parameter, null); var psc = ps.Length; if (psc < 1) return "数据模型不包含字段。"; diff --git a/Apewer.Source/Source/Sqlite.cs b/Apewer.Source/Source/Sqlite.cs index 86982a9..e623691 100644 --- a/Apewer.Source/Source/Sqlite.cs +++ b/Apewer.Source/Source/Sqlite.cs @@ -409,6 +409,12 @@ namespace Apewer.Source { return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); } + + if (!column.Independent) + { + if (column.PrimaryKey) type = type + " primary key"; + } + sqlcolumns.Add(type); } var sql = TextUtility.Merge("create table [", structure.Name, "](", TextUtility.Join(", ", sqlcolumns), "); "); @@ -430,8 +436,16 @@ namespace Apewer.Source if (string.IsNullOrEmpty(table)) table = structure.Name; if (string.IsNullOrEmpty(table)) return "表名称无效。"; + // 排除字段。 + var excluded = new List(); + foreach (var ca in structure.Columns) + { + // 排除不使用 ORM 的属性。 + if (ca.Independent || ca.Incremental) excluded.Add(ca.Field); + } + // 准备参数。 - var ps = structure.CreateParameters(record, Parameter); + var ps = structure.CreateParameters(record, Parameter, excluded); var psc = ps.Length; if (psc < 1) return "数据模型不包含字段。"; @@ -476,8 +490,17 @@ namespace Apewer.Source if (string.IsNullOrEmpty(table)) table = structure.Name; if (string.IsNullOrEmpty(table)) return "表名称无效。"; + // 排除字段。 + var excluded = new List(); + if (structure.Key != null) excluded.Add(structure.Key.Field); + foreach (var ca in structure.Columns) + { + // 排除不使用 ORM 的属性、自增属性和主键属性。 + if (ca.Independent || ca.Incremental || ca.PrimaryKey) excluded.Add(ca.Field); + } + // 准备参数。 - var ps = structure.CreateParameters(record, Parameter, "_key"); + var ps = structure.CreateParameters(record, Parameter, excluded); var psc = ps.Length; if (psc < 1) return "数据模型不包含字段。"; diff --git a/Apewer/Apewer.props b/Apewer/Apewer.props index 3c33bdc..607d0a0 100644 --- a/Apewer/Apewer.props +++ b/Apewer/Apewer.props @@ -9,7 +9,7 @@ Apewer Apewer Libraries - 6.5.9 + 6.5.10 diff --git a/Apewer/NumberUtility.cs b/Apewer/NumberUtility.cs index 25c1301..6dc0d78 100644 --- a/Apewer/NumberUtility.cs +++ b/Apewer/NumberUtility.cs @@ -297,6 +297,7 @@ namespace Apewer /// 获取布尔对象。 public static bool Boolean(object any) { + if (any.IsNull()) return false; if (any is bool _bool) return _bool; if (any is byte _byte) return _byte == 1; if (any is sbyte _sbyte) return _sbyte == 1; @@ -306,10 +307,13 @@ namespace Apewer if (any is uint _uint) return _uint == 1; if (any is long _long) return _long == 1; if (any is ulong _ulong) return _ulong == 1; + if (any is float _float) return _float == 1; + if (any is double _double) return _double == 1; + if (any is decimal _decimal) return _decimal == 1; if (any is string _string) { - _string = TextUtility.Lower(_string); - if (_string == "true" || _string == "yes" || _string == "y") return true; + var lower = TextUtility.Lower(_string); + if (lower == "true" || lower == "yes" || lower == "y" || lower == "1") return true; } return false; } diff --git a/Apewer/ObjectSet.cs b/Apewer/ObjectSet.cs index a3fad56..682ea66 100644 --- a/Apewer/ObjectSet.cs +++ b/Apewer/ObjectSet.cs @@ -105,6 +105,11 @@ namespace Apewer public partial class ObjectSet : DynamicObject { + public override IEnumerable GetDynamicMemberNames() + { + return new List(_origin.Keys).ToArray(); + } + /// public override bool TryGetMember(GetMemberBinder binder, out object result) { diff --git a/Apewer/RuntimeUtility.cs b/Apewer/RuntimeUtility.cs index 59688b1..a24795c 100644 --- a/Apewer/RuntimeUtility.cs +++ b/Apewer/RuntimeUtility.cs @@ -332,14 +332,20 @@ namespace Apewer { if (attributes == null) return false; if (attributes.Length < 1) return false; - return true; + var type = typeof(T); + foreach (var attribute in attributes) + { + if (type.Equals(attribute.GetType())) return true; + } + return false; } /// 判断指定类型具有特性。 public static bool Contains(ICustomAttributeProvider model, bool inherit = false) where T : Attribute { if (model == null) return false; - return Contains(model.GetCustomAttributes(typeof(T), inherit)); + var attributes = model.GetCustomAttributes(inherit); + return Contains(attributes); } /// 判断基类。 diff --git a/Apewer/Source/ColumnAttribute.cs b/Apewer/Source/ColumnAttribute.cs index ed2cb9f..f67712e 100644 --- a/Apewer/Source/ColumnAttribute.cs +++ b/Apewer/Source/ColumnAttribute.cs @@ -20,9 +20,12 @@ namespace Apewer.Source private string _field = null; private int _length = 0; private ColumnType _type; + private bool _incremental = false; + private bool _primarykey = false; private bool _independent = false; private bool _valid = true; + private bool _nullable = false; private void Init(string field, ColumnType type, int length) { @@ -66,12 +69,21 @@ namespace Apewer.Source /// 此特性有效。 public bool Valid { get => _valid; } - /// Independent 特性。 + /// 此列不被管理,Insert、Update 和 Create Table 将忽略此列。 public bool Independent { get => _independent; } /// 使用此特性的属性。 public PropertyInfo Property { get => _property; } + /// 属性对应的字段是主键。 + public bool PrimaryKey { get => _primarykey; } + + /// 属性对应的字段是自动增长的整数。 + public bool Incremental { get => _incremental; } + + /// 属性可为 NULL 值。 + public bool Nullale { get => _nullable; } + #endregion /// 解析列特性。 @@ -101,12 +113,20 @@ namespace Apewer.Source if (getter == null || getter.IsStatic) return null; if (setter == null || setter.IsStatic) return null; + // 检查主键特性和自增特性。 + var pt = property.PropertyType; + if (!ca._incremental && RuntimeUtility.Contains(property)) + { + if (pt.Equals(typeof(int)) || pt.Equals(typeof(long))) ca._incremental = true; + } + if (!ca._primarykey && RuntimeUtility.Contains(property)) ca._primarykey = true; + // 检查列名称。 if (TextUtility.IsBlank(ca.Field)) ca._field = property.Name; // 类型兼容。 - var pt = property.PropertyType; - if (pt.Equals(typeof(byte[]).FullName)) ca._type = ColumnType.Bytes; + if (pt.Equals(typeof(byte[]))) ca._type = ColumnType.Bytes; + else if (pt.Equals(typeof(bool))) ca._type = ColumnType.Boolean; 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; @@ -132,6 +152,20 @@ namespace Apewer.Source break; } } +#if !NET20 + else if (pt.Equals(typeof(Nullable))) { ca._type = ColumnType.Boolean; ca._nullable = true; } + else if (pt.Equals(typeof(Nullable))) { ca._type = ColumnType.Integer; ca._nullable = true; } + else if (pt.Equals(typeof(Nullable))) { ca._type = ColumnType.Integer; ca._nullable = true; } + else if (pt.Equals(typeof(Nullable))) { ca._type = ColumnType.Integer; ca._nullable = true; } + else if (pt.Equals(typeof(Nullable))) { ca._type = ColumnType.Integer; ca._nullable = true; } + else if (pt.Equals(typeof(Nullable))) { ca._type = ColumnType.Integer; ca._nullable = true; } + else if (pt.Equals(typeof(Nullable))) { ca._type = ColumnType.Integer; ca._nullable = true; } + else if (pt.Equals(typeof(Nullable))) { ca._type = ColumnType.Integer; ca._nullable = true; } + else if (pt.Equals(typeof(Nullable))) { ca._type = ColumnType.Float; ca._nullable = true; } + else if (pt.Equals(typeof(Nullable))) { ca._type = ColumnType.Float; ca._nullable = true; } + else if (pt.Equals(typeof(Nullable))) { ca._type = ColumnType.Float; ca._nullable = true; } + else if (pt.Equals(typeof(Nullable))) { ca._type = ColumnType.DateTime; ca._nullable = true; } +#endif else { ca._type = ColumnType.NVarChar191; diff --git a/Apewer/Source/ColumnType.cs b/Apewer/Source/ColumnType.cs index 12ee808..751cbc4 100644 --- a/Apewer/Source/ColumnType.cs +++ b/Apewer/Source/ColumnType.cs @@ -20,7 +20,7 @@ namespace Apewer.Source Bytes, /// 日期和时间类型(System.DateTime)。 - DateTime, + DateTime, /// 可变长度的字符串(System.String),必须指定长度。 VarChar, @@ -44,7 +44,10 @@ namespace Apewer.Source NVarCharMax, /// 可变长度的字符串(System.String)。 - NText + NText, + + /// 布尔类型(System.Boolean)。 + Boolean } diff --git a/Apewer/Source/IncrementalAttribute.cs b/Apewer/Source/IncrementalAttribute.cs new file mode 100644 index 0000000..4a25300 --- /dev/null +++ b/Apewer/Source/IncrementalAttribute.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Source +{ + + /// 表示此属性在支持的数据库中是自增字段。 + [AttributeUsage(AttributeTargets.Property)] + public sealed class IncrementalAttribute : Attribute { } + +} diff --git a/Apewer/Source/PrimaryKeyAttribute.cs b/Apewer/Source/PrimaryKeyAttribute.cs new file mode 100644 index 0000000..4b4b31b --- /dev/null +++ b/Apewer/Source/PrimaryKeyAttribute.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Source +{ + + /// 表示此属性对应的字段是主键。 + [AttributeUsage(AttributeTargets.Property)] + public sealed class PrimaryKeyAttribute : Attribute { } + +} diff --git a/Apewer/Source/SourceUtility.cs b/Apewer/Source/SourceUtility.cs index f4b52af..fe960b6 100644 --- a/Apewer/Source/SourceUtility.cs +++ b/Apewer/Source/SourceUtility.cs @@ -164,6 +164,7 @@ namespace Apewer.Source else if (pt.Equals(typeof(string))) setter.Invoke(record, new object[] { value.ToString() }); else if (pt.Equals(typeof(DateTime))) setter.Invoke(record, new object[] { value }); + else if (pt.Equals(typeof(bool))) setter.Invoke(record, new object[] { Boolean(value) }); else if (pt.Equals(typeof(byte))) setter.Invoke(record, new object[] { Byte(value) }); else if (pt.Equals(typeof(sbyte))) setter.Invoke(record, new object[] { SByte(value) }); else if (pt.Equals(typeof(short))) setter.Invoke(record, new object[] { Int16(value) }); @@ -176,7 +177,9 @@ namespace Apewer.Source else if (pt.Equals(typeof(double))) setter.Invoke(record, new object[] { Double(value) }); else if (pt.Equals(typeof(decimal))) setter.Invoke(record, new object[] { Decimal(value) }); +#if !NET20 else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable((DateTime)value) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Boolean(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Byte(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(SByte(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Int16(value)) }); @@ -188,6 +191,7 @@ namespace Apewer.Source else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Single(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Double(value)) }); else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Decimal(value)) }); +#endif else { @@ -527,6 +531,61 @@ namespace Apewer.Source #endregion + #region DataTable + + /// 将多个实体元素转换为 DataTable。 + /// 实体元素的类型。 + /// 实体元素。 + /// + public static DataTable DataTable(this IEnumerable items, string tableName = null) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + // 解析表结构。 + var it = typeof(T); + var ts = TableStructure.Parse(it); + var cas = ts.Columns; + var width = cas.Length; + + // 初始化列。 + var table = new DataTable(); + var gs = new MethodInfo[width]; + var pts = new Type[width]; + for (var i = 0; i < width; i++) + { + var ca = cas[i]; + var p = ca.Property; + var pt = p.PropertyType; + var g = p.GetGetMethod(); + if (g != null) + { + gs[i] = g; + pts[i] = pt; + table.Columns.Add(ca.Field, pt); + } + } + + // 添加行。 + foreach (var item in items) + { + if (item == null) continue; + var values = new ArrayBuilder(width); + for (var i = 0; i < width; i++) + { + if (gs[i] == null) continue; + + var v = gs[i].Invoke(item, null); + values.Add(v); + } + table.Rows.Add(values.Export()); + } + + if (tableName.NotEmpty()) table.TableName = tableName; + return table; + } + + #endregion + } } diff --git a/Apewer/Source/TableStructure.cs b/Apewer/Source/TableStructure.cs index afa03e2..29636b5 100644 --- a/Apewer/Source/TableStructure.cs +++ b/Apewer/Source/TableStructure.cs @@ -221,7 +221,7 @@ namespace Apewer.Source } /// 生成 IDataParameter 列表,用于 Insert 和 Update 方法。 - public IDataParameter[] CreateParameters(object record, Func callback, params string[] excludeds) + public IDataParameter[] CreateParameters(object record, Func callback, IEnumerable excludeds) { if (record == null || callback == null) return null; diff --git a/ChangeLog.md b/ChangeLog.md index af7bc37..373f6ee 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,14 @@  ### 最新提交 +### 6.5.10 +- Source:新增 Incremental 特性,用于标记自动增长字段; +- Source:新增 PrimaryKey 特性,用于标记主键字段; +- Source:添加 IEnumerable.DataTable() 扩展方法; +- Source:SqlClient 支持 Bit 类型; +- Source:SqlClient 新增 BulkCopy 方法; +- ObjectSet:实现 GetDynamicMemberNames 的重写。 + ### 6.5.9 - Source:修正 Query 填充无特性类型时的报错; - Source:新增 Query.ToArray 方法,支持 dynamic。