From 1e0e09856c9f418c7b3a433afdc56f782607c311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=9F=B3=E5=A4=B4?= Date: Sat, 30 Sep 2023 18:23:28 +0800 Subject: [PATCH] =?UTF-8?q?[feat]=20=E5=BC=95=E5=85=A5BatchOption=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E6=89=B9=E9=87=8F=E6=93=8D=E4=BD=9C=E8=83=BD?= =?UTF-8?q?=E5=8A=9B=EF=BC=8C=E5=8F=AF=E6=A0=B9=E6=8D=AE=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E6=8C=87=E5=AE=9AFullInsert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- XCode/Entity/EntityExtension.cs | 257 ++++++++++++++++++++------- XCode/Entity/EntityFactory.cs | 8 +- XCode/Model/BatchOption.cs | 22 +++ XCode/Transform/EntityIdExtracter.cs | 9 +- 4 files changed, 223 insertions(+), 73 deletions(-) create mode 100644 XCode/Model/BatchOption.cs diff --git a/XCode/Entity/EntityExtension.cs b/XCode/Entity/EntityExtension.cs index 579388dbc..310a69127 100644 --- a/XCode/Entity/EntityExtension.cs +++ b/XCode/Entity/EntityExtension.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using System.Data; using NewLife; using NewLife.Data; @@ -8,6 +9,7 @@ using NewLife.Serialization; using XCode.Configuration; using XCode.DataAccessLayer; +using XCode.Model; namespace XCode; @@ -20,7 +22,7 @@ public static class EntityExtension /// 作为Value部分的字段,默认为空表示整个实体对象为值 /// //[Obsolete("将来不再支持实体列表,请改用Linq")] - public static IDictionary ToDictionary(this IEnumerable list, String valueField = null) where T : IEntity + public static IDictionary ToDictionary(this IEnumerable list, String? valueField = null) where T : IEntity { if (list == null || !list.Any()) return new Dictionary(); @@ -40,6 +42,8 @@ public static IDictionary ToDictionary(this IEnumerable list, String value // 创建字典 var dic = typeof(Dictionary<,>).MakeGenericType(ktype, type).CreateInstance() as IDictionary; + if (dic == null) throw new InvalidOperationException(); + foreach (var item in list) { var k = item[key.Name]; @@ -148,12 +152,12 @@ public static Int32 Insert(this IEnumerable list, Boolean? useTransition = foreach (var item in dic) { var ss = item.Key; - rs += BatchInsert(item.ToList(), null, ss); + rs += BatchInsert(item.ToList(), option: null, ss); } return rs; } - return BatchInsert(list, null, session2); + return BatchInsert(list, option: null, session2); } return DoAction(list, useTransition, e => e.Insert(), session2); @@ -175,7 +179,7 @@ public static Int32 Update(this IEnumerable list, Boolean? useTransition = // Oracle批量更新 return session.Dal.DbType == DatabaseType.Oracle && list.Count() > 1 - ? BatchUpdate(list.Valid(false), null, null, null, session) + ? BatchUpdate(list.Valid(false), null, session) : DoAction(list, useTransition, e => e.Update(), session); } @@ -278,18 +282,18 @@ private static Int32 BatchSave(IEntitySession session, IEnumerable list) w } list = upserts; - if (inserts.Count > 0) rs += BatchInsert(inserts, null, session); + if (inserts.Count > 0) rs += BatchInsert(inserts, option: null, session); if (updates.Count > 0) { // 只有Oracle支持批量Update if (session.Dal.DbType == DatabaseType.Oracle) - rs += BatchUpdate(updates, null, null, null, session); + rs += BatchUpdate(updates, null, session); else upserts.AddRange(upserts); } } - if (list.Any()) rs += Upsert(list, null, null, null, session); + if (list.Any()) rs += BatchUpsert(list, null, session); return rs; } @@ -319,7 +323,10 @@ public static Int32 Delete(this IEnumerable list, Boolean? useTransition = var sql = $"Delete From {session.FormatedTableName} Where "; foreach (var item in list) { - ks.Add(item[pk.Name]); + var val = item[pk.Name]; + if (val == null) continue; + + ks.Add(val); count++; // 分批执行 @@ -422,18 +429,35 @@ public static IList Valid(this IEnumerable list, Boolean isNew) where T /// 要插入的字段,默认所有字段 /// 指定会话,分表分库时必用 /// - /// Oracle:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事物) - /// MySQL:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事物) + /// Oracle:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事务) + /// MySQL:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事务) + /// + public static Int32 BatchInsert(this IEnumerable list, IDataColumn[] columns, IEntitySession? session = null) where T : IEntity + { + var option = new BatchOption { Columns = columns }; + return BatchInsert(list, option, session); + } + + /// 批量插入 + /// 实体类型 + /// 实体列表 + /// 批操作选项 + /// 指定会话,分表分库时必用 + /// + /// Oracle:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事务) + /// MySQL:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事务) /// - public static Int32 BatchInsert(this IEnumerable list, IDataColumn[]? columns = null, IEntitySession? session = null) where T : IEntity + public static Int32 BatchInsert(this IEnumerable list, BatchOption? option = null, IEntitySession? session = null) where T : IEntity { if (list == null || !list.Any()) return 0; + option ??= new BatchOption(); + var entity = list.First(); var fact = entity.GetType().AsFactory(); - if (columns == null) + if (option.Columns == null) { - columns = fact.Fields.Select(e => e.Field).ToArray(); + var columns = fact.Fields.Select(e => e.Field).ToArray(); // 第一列数据包含非零自增,表示要插入自增值 var id = columns.FirstOrDefault(e => e.Identity); @@ -448,11 +472,13 @@ public static Int32 BatchInsert(this IEnumerable list, IDataColumn[]? colu // columns = columns.Where(e => e.Nullable || dirtys.Contains(e.Name)).ToArray(); //else // columns = columns.Where(e => dirtys.Contains(e.Name)).ToArray(); - if (!fact.FullInsert) + if (!option.FullInsert && !fact.FullInsert) { var dirtys = GetDirtyColumns(fact, list.Cast()); columns = columns.Where(e => dirtys.Contains(e.Name)).ToArray(); } + + option.Columns = columns; } session ??= fact.Session; @@ -468,7 +494,7 @@ public static Int32 BatchInsert(this IEnumerable list, IDataColumn[]? colu { if (span != null && list is ICollection collection) span.Tag = $"{session.TableName}[{collection.Count}]"; - var rs = dal.Session.Insert(session.Table, columns, list.Cast()); + var rs = dal.Session.Insert(session.Table, option.Columns, list.Cast()); // 清除脏数据,避免重复提交保存 foreach (var item in list) @@ -491,18 +517,35 @@ public static Int32 BatchInsert(this IEnumerable list, IDataColumn[]? colu /// 要插入的字段,默认所有字段 /// 指定会话,分表分库时必用 /// - /// Oracle:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事物) - /// MySQL:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事物) + /// Oracle:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事务) + /// MySQL:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事务) + /// + public static Int32 BatchInsertIgnore(this IEnumerable list, IDataColumn[] columns, IEntitySession? session = null) where T : IEntity + { + var option = new BatchOption { Columns = columns }; + return BatchInsertIgnore(list, option, session); + } + + /// 批量忽略插入 + /// 实体类型 + /// 实体列表 + /// 批操作选项 + /// 指定会话,分表分库时必用 + /// + /// Oracle:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事务) + /// MySQL:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事务) /// - public static Int32 BatchInsertIgnore(this IEnumerable list, IDataColumn[]? columns = null, IEntitySession? session = null) where T : IEntity + public static Int32 BatchInsertIgnore(this IEnumerable list, BatchOption? option = null, IEntitySession? session = null) where T : IEntity { if (list == null || !list.Any()) return 0; + option ??= new BatchOption(); + var entity = list.First(); var fact = entity.GetType().AsFactory(); - if (columns == null) + if (option.Columns == null) { - columns = fact.Fields.Select(e => e.Field).ToArray(); + var columns = fact.Fields.Select(e => e.Field).ToArray(); // 第一列数据包含非零自增,表示要插入自增值 var id = columns.FirstOrDefault(e => e.Identity); @@ -512,11 +555,12 @@ public static Int32 BatchInsertIgnore(this IEnumerable list, IDataColumn[] } // 每个列要么有脏数据,要么允许空。不允许空又没有脏数据的字段插入没有意义 - if (!fact.FullInsert) + if (!option.FullInsert && !fact.FullInsert) { var dirtys = GetDirtyColumns(fact, list.Cast()); columns = columns.Where(e => dirtys.Contains(e.Name)).ToArray(); } + option.Columns = columns; } session ??= fact.Session; @@ -531,7 +575,7 @@ public static Int32 BatchInsertIgnore(this IEnumerable list, IDataColumn[] { if (span != null && list is ICollection collection) span.Tag = $"{session.TableName}[{collection.Count}]"; - var rs = dal.Session.InsertIgnore(session.Table, columns, list.Cast()); + var rs = dal.Session.InsertIgnore(session.Table, option.Columns, list.Cast()); // 清除脏数据,避免重复提交保存 foreach (var item in list) @@ -554,18 +598,35 @@ public static Int32 BatchInsertIgnore(this IEnumerable list, IDataColumn[] /// 要插入的字段,默认所有字段 /// 指定会话,分表分库时必用 /// - /// Oracle:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事物) - /// MySQL:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事物) + /// Oracle:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事务) + /// MySQL:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事务) /// - public static Int32 BatchReplace(this IEnumerable list, IDataColumn[]? columns = null, IEntitySession? session = null) where T : IEntity + public static Int32 BatchReplace(this IEnumerable list, IDataColumn[] columns, IEntitySession? session = null) where T : IEntity + { + var option = new BatchOption { Columns = columns }; + return BatchReplace(list, option, session); + } + + /// 批量替换 + /// 实体类型 + /// 实体列表 + /// 批操作选项 + /// 指定会话,分表分库时必用 + /// + /// Oracle:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事务) + /// MySQL:当批量插入操作中有一条记录无法正常写入,则本次写入的所有数据都不会被写入(可以理解为自带事务) + /// + public static Int32 BatchReplace(this IEnumerable list, BatchOption? option = null, IEntitySession? session = null) where T : IEntity { if (list == null || !list.Any()) return 0; + option ??= new BatchOption(); + var entity = list.First(); var fact = entity.GetType().AsFactory(); - if (columns == null) + if (option.Columns == null) { - columns = fact.Fields.Select(e => e.Field).ToArray(); + var columns = fact.Fields.Select(e => e.Field).ToArray(); // 第一列数据包含非零自增,表示要插入自增值 var id = columns.FirstOrDefault(e => e.Identity); @@ -575,11 +636,13 @@ public static Int32 BatchReplace(this IEnumerable list, IDataColumn[]? col } // 每个列要么有脏数据,要么允许空。不允许空又没有脏数据的字段插入没有意义 - if (!fact.FullInsert) + if (!option.FullInsert && !fact.FullInsert) { var dirtys = GetDirtyColumns(fact, list.Cast()); columns = columns.Where(e => dirtys.Contains(e.Name)).ToArray(); } + + option.Columns = columns; } session ??= fact.Session; @@ -594,7 +657,7 @@ public static Int32 BatchReplace(this IEnumerable list, IDataColumn[]? col { if (span != null && list is ICollection collection) span.Tag = $"{session.TableName}[{collection.Count}]"; - var rs = dal.Session.Replace(session.Table, columns, list.Cast()); + var rs = dal.Session.Replace(session.Table, option.Columns, list.Cast()); // 清除脏数据,避免重复提交保存 foreach (var item in list) @@ -624,24 +687,44 @@ public static Int32 BatchReplace(this IEnumerable list, IDataColumn[]? col /// 要累加更新的字段,默认累加 /// 指定会话,分表分库时必用 /// - public static Int32 BatchUpdate(this IEnumerable list, IDataColumn[]? columns = null, ICollection? updateColumns = null, ICollection? addColumns = null, IEntitySession? session = null) where T : IEntity + public static Int32 BatchUpdate(this IEnumerable list, IDataColumn[] columns, ICollection? updateColumns = null, ICollection? addColumns = null, IEntitySession? session = null) where T : IEntity + { + var option = new BatchOption { Columns = columns, UpdateColumns = updateColumns, AddColumns = addColumns }; + return BatchUpdate(list, option, session); + } + + /// 批量更新 + /// + /// 注意类似:XCode.Exceptions.XSqlException: ORA-00933: SQL 命令未正确结束 + /// [SQL:Update tablen_Name Set FieldName=:FieldName W [:FieldName=System.Int32[]]][DB:AAA/Oracle] + /// 建议是优先检查表是否存在主键,如果由于没有主键导致,即使通过try...cache 依旧无法正常保存。 + /// + /// 实体类型 + /// 实体列表 + /// 批操作选项 + /// 指定会话,分表分库时必用 + /// + public static Int32 BatchUpdate(this IEnumerable list, BatchOption? option = null, IEntitySession? session = null) where T : IEntity { if (list == null || !list.Any()) return 0; + option ??= new BatchOption(); + var entity = list.First(); var fact = entity.GetType().AsFactory(); - columns ??= fact.Fields.Select(e => e.Field).Where(e => !e.Identity).ToArray(); + option.Columns ??= fact.Fields.Select(e => e.Field).Where(e => !e.Identity).ToArray(); //if (updateColumns == null) updateColumns = entity.Dirtys.Keys; - if (updateColumns == null) + if (option.UpdateColumns == null) { // 所有实体对象的脏字段作为更新字段 var dirtys = GetDirtyColumns(fact, list.Cast()); // 创建时间等字段不参与Update dirtys = dirtys.Where(e => !e.StartsWithIgnoreCase("Create")).ToArray(); - if (dirtys.Length > 0) updateColumns = dirtys; + if (dirtys.Length > 0) option.UpdateColumns = dirtys; } - addColumns ??= fact.AdditionalFields; + var updateColumns = option.UpdateColumns; + var addColumns = option.AddColumns ??= fact.AdditionalFields; if ((updateColumns == null || updateColumns.Count <= 0) && (addColumns == null || addColumns.Count <= 0)) return 0; @@ -658,7 +741,7 @@ public static Int32 BatchUpdate(this IEnumerable list, IDataColumn[]? colu { if (span != null && list is ICollection collection) span.Tag = $"{session.TableName}[{collection.Count}]"; - var rs = dal.Session.Update(session.Table, columns, updateColumns, addColumns, list.Cast()); + var rs = dal.Session.Update(session.Table, option.Columns, updateColumns, addColumns, list.Cast()); // 清除脏数据,避免重复提交保存 foreach (var item in list) @@ -689,20 +772,40 @@ public static Int32 BatchUpdate(this IEnumerable list, IDataColumn[]? colu /// 简单来说:对于一行记录,如果Insert 成功则返回1,如果需要执行的是update 则返回2 /// Oracle返回值:无论是插入还是更新返回的都始终为-1 /// - public static Int32 Upsert(this IEnumerable list, IDataColumn[]? columns = null, ICollection? updateColumns = null, ICollection? addColumns = null, IEntitySession? session = null) where T : IEntity + public static Int32 Upsert(this IEnumerable list, IDataColumn[] columns, ICollection? updateColumns = null, ICollection? addColumns = null, IEntitySession? session = null) where T : IEntity + { + var option = new BatchOption { Columns = columns, UpdateColumns = updateColumns, AddColumns = addColumns }; + return BatchUpsert(list, option, session); + } + + /// 批量插入或更新 + /// 实体类型 + /// 实体列表 + /// 批操作选项 + /// 指定会话,分表分库时必用 + /// + /// MySQL返回值:返回值相当于流程执行次数,及时insert失败也会累计一次执行(所以不建议通过该返回值确定操作记录数) + /// do insert success = 1次; + /// do update success =2次(insert 1次+update 1次), + /// 简单来说:对于一行记录,如果Insert 成功则返回1,如果需要执行的是update 则返回2 + /// Oracle返回值:无论是插入还是更新返回的都始终为-1 + /// + public static Int32 BatchUpsert(this IEnumerable list, BatchOption? option = null, IEntitySession? session = null) where T : IEntity { if (list == null || !list.Any()) return 0; + option ??= new BatchOption(); + var entity = list.First(); var fact = entity.GetType().AsFactory(); session ??= fact.Session; // 批量Upsert需要主键参与,哪怕是自增,构建update的where时用到主键 - if (columns == null) + if (option.Columns == null) { - columns = fact.Fields.Select(e => e.Field).ToArray(); + var columns = fact.Fields.Select(e => e.Field).ToArray(); - if (!fact.FullInsert) + if (!option.FullInsert && !fact.FullInsert) { var dirtys = GetDirtyColumns(fact, list.Cast()); columns = columns.Where(e => e.PrimaryKey || dirtys.Contains(e.Name)).ToArray(); @@ -715,12 +818,12 @@ public static Int32 Upsert(this IEnumerable list, IDataColumn[]? columns = // 如果所有自增字段都是0,则不参与批量Upsert if (list.All(e => e[uk.Name].ToLong() == 0)) columns = columns.Where(e => !e.Identity).ToArray(); - else if (list.All(e => e[uk.Name].ToLong() != 0)) - { } - else + else if (list.Any(e => e[uk.Name].ToLong() == 0)) throw new NotSupportedException($"Upsert遇到自增字段,且部分为0部分不为0,无法同时支持Insert和Update"); } + option.Columns = columns; + //var dbt = session.Dal.DbType; //if (dbt is DatabaseType.SqlServer or DatabaseType.Oracle) // columns = fact.Fields.Select(e => e.Field).Where(e => !e.Identity || e.PrimaryKey).ToArray(); @@ -750,16 +853,17 @@ public static Int32 Upsert(this IEnumerable list, IDataColumn[]? columns = // columns = columns.Where(e => dirtys.Contains(e.Name)).ToArray(); } //if (updateColumns == null) updateColumns = entity.Dirtys.Keys; - if (updateColumns == null) + if (option.UpdateColumns == null) { // 所有实体对象的脏字段作为更新字段 var dirtys = GetDirtyColumns(fact, list.Cast()); // 创建时间等字段不参与Update dirtys = dirtys.Where(e => !e.StartsWithIgnoreCase("Create")).ToArray(); - if (dirtys.Length > 0) updateColumns = dirtys; + if (dirtys.Length > 0) option.UpdateColumns = dirtys; } - addColumns ??= fact.AdditionalFields; + var updateColumns = option.UpdateColumns; + var addColumns = option.AddColumns ??= fact.AdditionalFields; // 没有任何数据变更则直接返回0 if ((updateColumns == null || updateColumns.Count <= 0) && (addColumns == null || addColumns.Count <= 0)) return 0; @@ -776,7 +880,7 @@ public static Int32 Upsert(this IEnumerable list, IDataColumn[]? columns = { if (span != null && list is ICollection collection) span.Tag = $"{session.TableName}[{collection.Count}]"; - var rs = dal.Session.Upsert(session.Table, columns, updateColumns, addColumns, list.Cast()); + var rs = dal.Session.Upsert(session.Table, option.Columns, updateColumns, addColumns, list.Cast()); // 清除脏数据,避免重复提交保存 foreach (var item in list) @@ -805,12 +909,30 @@ public static Int32 Upsert(this IEnumerable list, IDataColumn[]? columns = /// do update success =2次(insert 1次+update 1次), /// 简单来说:如果Insert 成功则返回1,如果需要执行的是update 则返回2, /// - public static Int32 Upsert(this IEntity entity, IDataColumn[]? columns = null, ICollection? updateColumns = null, ICollection? addColumns = null, IEntitySession? session = null) + public static Int32 Upsert(this IEntity entity, IDataColumn[] columns, ICollection? updateColumns = null, ICollection? addColumns = null, IEntitySession? session = null) { + var option = new BatchOption { Columns = columns, UpdateColumns = updateColumns, AddColumns = addColumns }; + return Upsert(entity, option, session); + } + + /// 批量插入或更新 + /// 实体对象 + /// 批操作选项 + /// 指定会话,分表分库时必用 + /// + /// MySQL返回值:返回值相当于流程执行次数,及时insert失败也会累计一次执行(所以不建议通过该返回值确定操作记录数) + /// do insert success = 1次; + /// do update success =2次(insert 1次+update 1次), + /// 简单来说:如果Insert 成功则返回1,如果需要执行的是update 则返回2, + /// + public static Int32 Upsert(this IEntity entity, BatchOption? option = null, IEntitySession? session = null) + { + option ??= new BatchOption(); + var fact = entity.GetType().AsFactory(); - if (columns == null) + if (option.Columns == null) { - columns = fact.Fields.Select(e => e.Field).Where(e => !e.Identity).ToArray(); + var columns = fact.Fields.Select(e => e.Field).Where(e => !e.Identity).ToArray(); // 每个列要么有脏数据,要么允许空。不允许空又没有脏数据的字段插入没有意义 //var dirtys = GetDirtyColumns(fact, new[] { entity }); @@ -818,14 +940,15 @@ public static Int32 Upsert(this IEntity entity, IDataColumn[]? columns = null, I // columns = columns.Where(e => e.Nullable || dirtys.Contains(e.Name)).ToArray(); //else // columns = columns.Where(e => dirtys.Contains(e.Name)).ToArray(); - if (!fact.FullInsert) + if (!option.FullInsert && !fact.FullInsert) { var dirtys = GetDirtyColumns(fact, new[] { entity }); columns = columns.Where(e => e.PrimaryKey || dirtys.Contains(e.Name)).ToArray(); } + option.Columns = columns; } - updateColumns ??= entity.Dirtys.Where(e => !e.StartsWithIgnoreCase("Create")).Distinct().ToArray(); - addColumns ??= fact.AdditionalFields; + option.UpdateColumns ??= entity.Dirtys.Where(e => !e.StartsWithIgnoreCase("Create")).Distinct().ToArray(); + option.AddColumns ??= fact.AdditionalFields; session ??= fact.Session; session.InitData(); @@ -840,7 +963,7 @@ public static Int32 Upsert(this IEntity entity, IDataColumn[]? columns = null, I { if (span != null) span.Tag = $"{session.TableName}[{entity}]"; - return dal.Session.Upsert(session.Table, columns, updateColumns, addColumns, new[] { entity as IModel }); + return dal.Session.Upsert(session.Table, option.Columns, option.UpdateColumns, option.AddColumns, new[] { entity as IModel }); } catch (Exception ex) { @@ -880,9 +1003,9 @@ private static String[] GetDirtyColumns(IEntityFactory fact, IEnumerable(this IEnumerable list) where T : IEntity { var entity = list.FirstOrDefault(); - if (entity == null) return null; + //if (entity == null) return null; - var fact = entity.GetType().AsFactory(); + var fact = (entity?.GetType() ?? typeof(T)).AsFactory(); var fs = fact.Fields; var count = fs.Length; @@ -890,7 +1013,7 @@ public static DbTable ToTable(this IEnumerable list) where T : IEntity { Columns = new String[count], Types = new Type[count], - Rows = new List(), + Rows = new List(), }; for (var i = 0; i < fs.Length; i++) { @@ -901,7 +1024,7 @@ public static DbTable ToTable(this IEnumerable list) where T : IEntity foreach (var item in list) { - var dr = new Object[count]; + var dr = new Object?[count]; for (var i = 0; i < fs.Length; i++) { var fi = fs[i]; @@ -922,9 +1045,9 @@ public static Int64 Write(this IEnumerable list, Stream stream) where T : if (list == null) return 0; var p = stream.Position; - foreach (var item in list) + foreach (var entity in list) { - (item as IAccessor).Write(stream, null); + if (entity is IAccessor acc) acc.Write(stream, null); } return stream.Position - p; @@ -941,9 +1064,9 @@ public static Int64 SaveFile(this IEnumerable list, String file) where T : var compressed = file.EndsWithIgnoreCase(".gz"); return file.AsFile().OpenWrite(compressed, fs => { - foreach (var item in list) + foreach (var entity in list) { - (item as IAccessor).Write(fs, null); + if (entity is IAccessor acc) acc.Write(fs, null); } }); } @@ -1027,7 +1150,7 @@ public static IList Read(this IList list, Stream stream) where T : IEnt while (stream.Position < stream.Length) { var entity = (T)fact.Create(); - (entity as IAccessor).Read(stream, null); + if (entity is IAccessor acc) acc.Read(stream, null); list.Add(entity); } @@ -1047,7 +1170,7 @@ public static IEnumerable ReadEnumerable(this IList list, Stream stream while (stream.Position < stream.Length) { var entity = (T)fact.Create(); - (entity as IAccessor).Read(stream, null); + if (entity is IAccessor acc) acc.Read(stream, null); list.Add(entity); @@ -1072,7 +1195,7 @@ public static IList LoadFile(this IList list, String file) where T : IE while (fs.Position < fs.Length) { var entity = (T)fact.Create(); - (entity as IAccessor).Read(fs, null); + if (entity is IAccessor acc) acc.Read(fs, null); list.Add(entity); } @@ -1092,6 +1215,8 @@ public static IList LoadCsv(this IList list, Stream stream) where T : I // 匹配字段 var names = csv.ReadLine(); + if (names == null || names.Length == 0) return list; + var fields = new FieldItem[names.Length]; for (var i = 0; i < names.Length; i++) { @@ -1140,12 +1265,12 @@ public static IList LoadCsv(this IList list, String file) where T : IEn /// public static DataTable ToDataTable(this IEnumerable list) where T : IEntity { + var dt = new DataTable(); var entity = list.FirstOrDefault(); - if (entity == null) return null; + if (entity == null) return dt; var fact = entity.GetType().AsFactory(); - var dt = new DataTable(); foreach (var fi in fact.Fields) { var dc = new DataColumn diff --git a/XCode/Entity/EntityFactory.cs b/XCode/Entity/EntityFactory.cs index ab5090d65..0ab1b55b4 100644 --- a/XCode/Entity/EntityFactory.cs +++ b/XCode/Entity/EntityFactory.cs @@ -184,13 +184,13 @@ public static void InitAll() /// 连接名 public static void InitConnection(String connName) => Init(connName, null); - private static void Init(String connName, IList types) + private static void Init(String connName, IList? types) { using var span = DefaultTracer.Instance?.NewSpan($"db:{connName}:InitConnection", connName); try { // 加载所有实体类 - if (types == null) types = typeof(IEntity).GetAllSubclasses().Where(e => e.BaseType.IsGenericType).ToList(); + types ??= typeof(IEntity).GetAllSubclasses().Where(e => e.BaseType.IsGenericType).ToList(); // 初始化工厂 var facts = new List(); @@ -216,9 +216,9 @@ private static void Init(String connName, IList types) { // 克隆一份,防止修改 var table = item.Table.DataTable; - table = table.Clone() as IDataTable; + table = (table.Clone() as IDataTable)!; - if (table != null && table.TableName != item.TableName) + if (/*table != null &&*/ table.TableName != item.TableName) { // 表名去掉前缀 var name = item.TableName; diff --git a/XCode/Model/BatchOption.cs b/XCode/Model/BatchOption.cs new file mode 100644 index 000000000..6279fb1fe --- /dev/null +++ b/XCode/Model/BatchOption.cs @@ -0,0 +1,22 @@ +using XCode.DataAccessLayer; + +namespace XCode.Model; + +/// 批操作选项 +public class BatchOption +{ + ///// 指定会话,分表分库时必用 + //public IEntitySession? Session { get; set; } + + /// 字段集合。为空时表示使用所有字段 + public IDataColumn[]? Columns { get; set; } + + /// 要更新的字段。用于Update/Upsert,默认脏数据 + public ICollection? UpdateColumns { get; set; } + + /// 要累加更新的字段。用于Update/Upsert,默认累加 + public ICollection? AddColumns { get; set; } + + /// 是否完全插入所有字段。用于Insert/Upsert,默认false表示不插入没有脏数据的字段 + public Boolean FullInsert { get; set; } +} diff --git a/XCode/Transform/EntityIdExtracter.cs b/XCode/Transform/EntityIdExtracter.cs index d5050c035..0fb188d1a 100644 --- a/XCode/Transform/EntityIdExtracter.cs +++ b/XCode/Transform/EntityIdExtracter.cs @@ -23,8 +23,8 @@ public class EntityIdExtracter #endregion #region 构造 - /// 实例化数据抽取器 - public EntityIdExtracter() { } + ///// 实例化数据抽取器 + //public EntityIdExtracter() { } /// 实例化数据抽取器 /// @@ -59,7 +59,10 @@ public virtual IEnumerable> Fetch() if (list.Count < BatchSize) break; // 自增分割时,取最后一行 - Row = (Int64)list.Last()[IdField.Name]; + var rs = list.Last()[IdField.Name]; + if (rs == null) break; + + Row = (Int64)rs; } } #endregion