Files
TWA-App/TWASys-App/Dapper.AExtentions/BatchInsert.cs

330 lines
12 KiB
C#

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<string, DataValue> _dataKey;
private List<DataValue> _list;
private StringBuilder queryBatch = new();
public int IdTable = 100;
public BatchInsert(IDbConnection connect, IDbTransaction transaction)
{
_connect = connect;
_transaction = transaction;
_dataKey = new Dictionary<string, DataValue>();
_list = new List<DataValue>();
}
public void AddRow<T>(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<DataValue> 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<object>();
var targetType = Nullable.GetUnderlyingType(pk.PropertyType) ?? pk.PropertyType;
if (raw is IDictionary<string, object> 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<DataValue> inputs // 0 coi là "trống" (trừ property tên Status)
)
{
var dp = new DynamicParameters();
var seen = new HashSet<string>(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<object, object?> Getter { get; init; }
public required Type UnderlyingType { get; init; }
public required bool IsNotMapped { get; init; }
public required bool IsKeyOrForeignKey { get; init; }
}
}