using Dapper; using Microsoft.AspNetCore.Identity; using MySqlConnector; using System.ComponentModel.DataAnnotations.Schema; using System.Data; using System.Globalization; using System.Linq.Expressions; using System.Reflection; using System.Runtime.Intrinsics.Arm; using System.Text; namespace TWASys_App.Dapper.AExtentions { public class DataValue { public object? Data { set; get; } public string Name { set; get; } = ""; public string PKName { set; get; } = ""; } public class BatchInsert { private IDbConnection _connect; private IDbTransaction _transaction; private Dictionary _dataKey; private List _list; private StringBuilder queryBatch = new(); public int IdTable = 100; public BatchInsert(IDbConnection connect, IDbTransaction transaction) { _connect = connect; _transaction = transaction; _dataKey = new Dictionary(); _list = new List(); } public void AddRow(T obj) { var dt = new DataValue { Name = "", PKName = "", Data = obj }; _list.Add(dt); var p = DapperCommand.QueryInsert(obj, _dataKey, IdTable); IdTable++; dt.Name = p.Name; dt.PKName = p.KeyVar; if (p.IsKeyAI) { _dataKey.TryAdd(p.KeyVar, dt); } queryBatch.AppendLine(p.Query); } public string GetQuery() { string query = string.Empty; foreach (var (key, value) in _dataKey) { query += $"Select @tb{value.Name}_{key} As {key}; \n"; } queryBatch.AppendLine(query); return queryBatch.ToString(); } public async Task ExcuteQuery() { var dynamic = ToDynamicParametersSeqPrefix(_list); var str = GetQuery(); Console.WriteLine(str); using var grid = await _connect.QueryMultipleAsync(str, dynamic, _transaction); await MapAutoIncrementIdsAsync(grid, _dataKey.Values); } public static async Task MapAutoIncrementIdsAsync(SqlMapper.GridReader grid,IEnumerable items) { foreach (var dv in items) { var obj = dv?.Data; if (obj is null) continue; var (pk, isAuto) = FindPrimaryKey(obj.GetType()); if (pk is null || !isAuto) continue; // không phải auto-inc → không đọc scalar // nếu object đã có ID (không rỗng) thì bỏ qua để không lệch thứ tự result set var cur = pk.GetValue(obj); if (!IsEmptyId(cur)) continue; // đọc scalar từ SELECT kế tiếp và gán lại cho property khóa var raw = await grid.ReadSingleAsync(); var targetType = Nullable.GetUnderlyingType(pk.PropertyType) ?? pk.PropertyType; if (raw is IDictionary row) { if (!row.TryGetValue(dv?.PKName, out raw)) throw new InvalidOperationException($"Missing column {dv?.PKName}"); } // MySqlDecimal → decimal if (raw?.GetType().FullName == "MySql.Data.Types.MySqlDecimal") { var t = raw.GetType(); raw = (decimal)t.GetProperty("Value")!.GetValue(raw)!; } // Giờ o phải là số: long/ulong/int/decimal/… var target = Nullable.GetUnderlyingType(pk.PropertyType) ?? pk.PropertyType; pk.SetValue(obj, ConvertNumericTo(target, raw)); } } static object ConvertNumericTo(Type target, object? x) { // ưu tiên xử lý 2 nhánh chính if (target == typeof(ulong) || target == typeof(uint) || target == typeof(ushort) || target == typeof(byte)) { ulong u = x switch { ulong v => v, long v => v < 0 ? throw new OverflowException() : (ulong)v, int v => v < 0 ? throw new OverflowException() : (ulong)v, decimal v => v < 0 ? throw new OverflowException() : checked((ulong)v), _ => Convert.ToUInt64(x, System.Globalization.CultureInfo.InvariantCulture) }; return target == typeof(ulong) ? u : target == typeof(uint) ? checked((uint)u) : target == typeof(ushort) ? checked((ushort)u) : checked((byte)u); } else { long s = x switch { long v => v, ulong v => v > (ulong)long.MaxValue ? throw new OverflowException() : (long)v, int v => v, decimal v => checked((long)v), _ => Convert.ToInt64(x, System.Globalization.CultureInfo.InvariantCulture) }; return target == typeof(long) ? s : target == typeof(int) ? checked((int)s) : target == typeof(short) ? checked((short)s) : checked((sbyte)s); } } public static DynamicParameters ToDynamicParametersSeqPrefix( IEnumerable inputs // 0 coi là "trống" (trừ property tên Status) ) { var dp = new DynamicParameters(); var seen = new HashSet(StringComparer.Ordinal); foreach (var src in inputs) { var t = src.Data?.GetType(); var props = t?.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var p in props) { if (!p.CanRead) continue; if (IsNotMapped(p)) continue; if (IsKeyOrForeignKey(p, src.Data)) continue; var paramName = "tb" + src.Name + "_" + p.Name; var val = p.GetValue(src.Data); dp.Add("@" + paramName, val); } } return dp; } private static (PropertyInfo? Key, bool IsAutoInc) FindPrimaryKey(Type t) { PropertyInfo? key = null; bool auto = false; foreach (var p in t.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (HasAttr(p, "System.ComponentModel.DataAnnotations.KeyAttribute", "KeyAttribute", "Dapper.Contrib.Extensions.KeyAttribute")) { key = p; auto = IsDatabaseGeneratedIdentity(p); break; } if (HasAttr(p, "Dapper.Contrib.Extensions.ExplicitKeyAttribute", "ExplicitKeyAttribute")) { key = p; auto = false; break; } } // fallback theo tên "Id" nếu không có attribute key ??= t.GetProperty("Id", BindingFlags.Public | BindingFlags.Instance); // nếu không tìm được attribute, đoán auto-inc cho kiểu số if (key != null && !auto) { var k = Nullable.GetUnderlyingType(key.PropertyType) ?? key.PropertyType; auto = k == typeof(int) || k == typeof(long) || k == typeof(uint) || k == typeof(ulong); } return (key, auto); } private static bool IsDatabaseGeneratedIdentity(PropertyInfo p) { // [DatabaseGenerated(DatabaseGeneratedOption.Identity)] foreach (var ca in p.CustomAttributes) { var full = ca.AttributeType.FullName ?? ca.AttributeType.Name; if (full is "System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedAttribute" or "DatabaseGeneratedAttribute") { if (ca.ConstructorArguments.Count == 1 && ca.ConstructorArguments[0].ArgumentType.IsEnum) return Convert.ToInt32(ca.ConstructorArguments[0].Value) == 1; // Identity = 1 } } return false; } private static bool IsNotMapped(PropertyInfo p) { return HasAttr(p, "System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute", "NotMappedAttribute"); } private static bool IsKey(PropertyInfo p, object? obj) { if( HasAttr(p, "System.ComponentModel.DataAnnotations.KeyAttribute", "KeyAttribute", "Dapper.Contrib.Extensions.KeyAttribute")) { var val = p.GetValue(obj); ulong t = 0; if (val == null && string.IsNullOrWhiteSpace(val?.ToString())) { return true; } else { if (ulong.TryParse(val?.ToString(), out t) && t == 0) { return true; } } } return false; } private static bool IsKeyOrForeignKey(PropertyInfo p, object? obj) { return IsKey(p, obj) || IsForeignKeyProperty(p, obj); } private static bool HasAttr(MemberInfo m, params string[] names) { return m.CustomAttributes.Any(ca => { var full = ca.AttributeType.FullName ?? ca.AttributeType.Name; var shortName = ca.AttributeType.Name; return names.Contains(full) || names.Contains(shortName); }); } private static bool IsForeignKeyProperty(PropertyInfo p, object? obj) { // [ForeignKey] ngay trên property if (HasAttr(p, "System.ComponentModel.DataAnnotations.Schema.ForeignKeyAttribute", "ForeignKeyAttribute")) { var val = p.GetValue(obj); ulong t = 0; if (val == null && string.IsNullOrWhiteSpace(val?.ToString())) { return true; } else { if (ulong.TryParse(val?.ToString(), out t) && t == 0) { return true; } } } //// [ForeignKey("PropName")] đặt trên navigation khác, trỏ tới property này //var my = p.Name; //foreach (var nav in allProps) //{ // if (ReferenceEquals(nav, p)) continue; // var fkAttr = nav.CustomAttributes.FirstOrDefault(ca => // (ca.AttributeType.FullName ?? ca.AttributeType.Name) is // "System.ComponentModel.DataAnnotations.Schema.ForeignKeyAttribute" or "ForeignKeyAttribute"); // if (fkAttr == default) continue; // var nameArg = fkAttr.ConstructorArguments.FirstOrDefault().Value?.ToString(); // if (string.IsNullOrWhiteSpace(nameArg)) // nameArg = fkAttr.NamedArguments.FirstOrDefault(a => a.MemberName == "Name").TypedValue.Value?.ToString(); // if (string.IsNullOrWhiteSpace(nameArg)) continue; // var names = nameArg.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) // .Select(s => s.Trim()); // if (names.Any(n => string.Equals(n, my, StringComparison.OrdinalIgnoreCase))) // return true; //} return false; } private static bool IsEmptyId(object? v) { if (v is null) return true; var t = Nullable.GetUnderlyingType(v.GetType()) ?? v.GetType(); if (!t.IsValueType) return false; try { return v.Equals(Activator.CreateInstance(t)); } catch { return false; } } } public sealed class PropMeta { public required PropertyInfo Prop { get; init; } public required Func Getter { get; init; } public required Type UnderlyingType { get; init; } public required bool IsNotMapped { get; init; } public required bool IsKeyOrForeignKey { get; init; } } }