update TWA Management v0.0.1
This commit is contained in:
329
TWASys-App/Dapper.AExtentions/BatchInsert.cs
Normal file
329
TWASys-App/Dapper.AExtentions/BatchInsert.cs
Normal file
@ -0,0 +1,329 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,80 @@
|
||||
using Dapper;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Net.WebSockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace TWASys_App.Dapper.AExtentions
|
||||
{
|
||||
public static class DapperCommand
|
||||
{
|
||||
public static async Task Insert<T>(this DbConnection connection, T obj, bool identityInsert = true)
|
||||
|
||||
public static dynamic QueryInsert<T>(T obj)
|
||||
{
|
||||
return QueryInsert(obj, new Dictionary<string, DataValue>());
|
||||
}
|
||||
|
||||
public static dynamic QueryInsert<T>(T obj, Dictionary<string, DataValue> data, int IdTable = 100)
|
||||
{
|
||||
var type = typeof(T);
|
||||
var tableName = TableMapper.GetTableName(type);
|
||||
var allProperties = PropertiesCache.TypePropertiesCache(type);
|
||||
var keyProperties = PropertiesCache.KeyPropertiesCache(type);
|
||||
var fkProperties = PropertiesCache.FKPropertiesCache(type);
|
||||
var computedProperties = PropertiesCache.ComputedPropertiesCache(type);
|
||||
var columns = PropertiesCache.GetColumnNamesCache(type);
|
||||
|
||||
var insertProperties = allProperties.Except(computedProperties).Except(fkProperties).ToList();
|
||||
var insertPropertiesString = string.Empty;
|
||||
var stringKeyProperty = string.Empty;
|
||||
var keyAI = string.Empty;
|
||||
var flag = false;
|
||||
var q = string.Empty;
|
||||
if (keyProperties.Count > 0)
|
||||
{
|
||||
flag = true;
|
||||
insertProperties = insertProperties.Except(keyProperties).ToList();
|
||||
stringKeyProperty = GetColumnsString(keyProperties.ToList(), columns);
|
||||
keyAI = $@"SET @{GetColumnsString(keyProperties.ToList(), columns, IdTable.ToString(), "")} := LAST_INSERT_ID();";
|
||||
}
|
||||
insertPropertiesString = GetColumnsString(insertProperties, columns);
|
||||
var fkPropertiesString = string.Empty;
|
||||
var fkValuesString = string.Empty;
|
||||
if (data.Count > 0)
|
||||
{
|
||||
fkPropertiesString = GetColumnsString(fkProperties, columns);
|
||||
|
||||
|
||||
fkValuesString = string.Join(", ", fkProperties.Select(u => {
|
||||
var ok = data.TryGetValue(u.Name, out var v);
|
||||
if (ok)
|
||||
{
|
||||
return $"@tb{v?.Name}_{u.Name}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"@tb{IdTable}_{u.Name}";
|
||||
}
|
||||
}));
|
||||
fkPropertiesString += (string.IsNullOrEmpty(fkPropertiesString))?"": ", ";
|
||||
fkValuesString += (string.IsNullOrEmpty(fkValuesString)) ? "" : ", ";
|
||||
}
|
||||
q = $@"INSERT INTO {FormatTableName(tableName)} ({fkPropertiesString + insertPropertiesString}) VALUES ({fkValuesString + GetColumnsString(insertProperties, columns, IdTable.ToString(), "@")});
|
||||
{keyAI}";
|
||||
|
||||
return new
|
||||
{
|
||||
IsKeyAI = flag,
|
||||
Query = q,
|
||||
Name = IdTable.ToString(),
|
||||
KeyVar = stringKeyProperty
|
||||
};
|
||||
}
|
||||
public static async Task Insert<T>(this DbConnection connection, T obj, IDbTransaction? tx = null)
|
||||
{
|
||||
var type = typeof(T);
|
||||
var tableName = TableMapper.GetTableName(type);
|
||||
@ -17,13 +85,14 @@ namespace TWASys_App.Dapper.AExtentions
|
||||
|
||||
var insertProperties = allProperties.Except(computedProperties).ToList();
|
||||
var insertPropertiesString = string.Empty;
|
||||
if (identityInsert)
|
||||
|
||||
if (keyProperties.Count > 0)
|
||||
{
|
||||
insertProperties = insertProperties.Except(keyProperties).ToList();
|
||||
insertPropertiesString = GetColumnsString(insertProperties, columns);
|
||||
var insertedId = await connection.QueryFirstAsync<int>($@"
|
||||
INSERT INTO {FormatTableName(tableName)} ({insertPropertiesString}) VALUES ({GetColumnsString(insertProperties, columns, "@")});
|
||||
SELECT LAST_INSERT_ID()", obj);
|
||||
SELECT LAST_INSERT_ID()", obj, tx);
|
||||
keyProperties[0].SetValue(obj, insertedId);
|
||||
|
||||
}
|
||||
@ -32,7 +101,7 @@ namespace TWASys_App.Dapper.AExtentions
|
||||
insertPropertiesString = GetColumnsString(insertProperties, columns);
|
||||
await connection.QueryFirstAsync($@"
|
||||
INSERT INTO {FormatTableName(tableName)}({insertPropertiesString})
|
||||
VALUES ({GetColumnsString(insertProperties, columns, "@")})", obj);
|
||||
VALUES ({GetColumnsString(insertProperties, columns, "@")})", obj, tx);
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,17 +140,24 @@ namespace TWASys_App.Dapper.AExtentions
|
||||
{
|
||||
return string.Join(prefix, properties.Select(p => $"{columnNames[p.Name]} = @{p.Name}"));
|
||||
}
|
||||
private static string GetColumnsString(IEnumerable<PropertyInfo> properties, IReadOnlyDictionary<string, string> columnNames, string tablePrefix = null)
|
||||
|
||||
|
||||
private static string GetColumnsString(IEnumerable<PropertyInfo> properties, IReadOnlyDictionary<string, string> columnNames, string tbName, string tablePrefix = "")
|
||||
{
|
||||
if (tbName != string.Empty) tbName = "tb" + tbName + "_";
|
||||
if (tablePrefix == "target.")
|
||||
{
|
||||
return string.Join(", ", properties.Select(property => $"{tablePrefix}{columnNames[property.Name]} as {property.Name}"));
|
||||
}
|
||||
else if(tablePrefix == "@")
|
||||
{
|
||||
return string.Join(", ", properties.Select(property => $"{tablePrefix}{property.Name}"));
|
||||
return string.Join(", ", properties.Select(property => $"{tablePrefix}{tbName + property.Name}"));
|
||||
}
|
||||
return string.Join(", ", properties.Select(property => $"{tablePrefix}{columnNames[property.Name]}"));
|
||||
return string.Join(", ", properties.Select(property => $"{tablePrefix}{tbName + columnNames[property.Name].Replace("__", "")}"));
|
||||
}
|
||||
private static string GetColumnsString(IEnumerable<PropertyInfo> properties, IReadOnlyDictionary<string, string> columnNames, string tablePrefix = null)
|
||||
{
|
||||
return GetColumnsString(properties, columnNames, "", tablePrefix);
|
||||
}
|
||||
private static string FormatTableName(string table)
|
||||
{
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
@ -7,10 +9,12 @@ namespace TWASys_App.Dapper.AExtentions
|
||||
public class PropertiesCache
|
||||
{
|
||||
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> KeyProperties = new();
|
||||
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> FKProperties = new();
|
||||
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> TypeProperties = new();
|
||||
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> ComputedProperties = new();
|
||||
private static readonly ConcurrentDictionary<RuntimeTypeHandle, IReadOnlyDictionary<string, string>> ColumnNames = new();
|
||||
|
||||
|
||||
public static List<PropertyInfo> TypePropertiesCache(Type type)
|
||||
{
|
||||
if (TypeProperties.TryGetValue(type.TypeHandle, out var cachedProps))
|
||||
@ -60,6 +64,28 @@ namespace TWASys_App.Dapper.AExtentions
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool IsPkAutoIncrement(PropertyInfo p)
|
||||
{
|
||||
|
||||
// ===== Fallback by-name (nếu không thể tham chiếu typed)
|
||||
var attrs = p.GetCustomAttributes(true);
|
||||
bool hasKeyByName = attrs.Any(a => a.GetType().Name is "KeyAttribute");
|
||||
|
||||
var dbgenByName = attrs.FirstOrDefault(a => a.GetType().Name is "DatabaseGeneratedAttribute");
|
||||
bool isIdentityByName = false;
|
||||
if (dbgenByName != null)
|
||||
{
|
||||
// Tìm property "DatabaseGeneratedOption" (enum) và đọc giá trị "Identity"
|
||||
var prop = dbgenByName.GetType().GetProperty("DatabaseGeneratedOption");
|
||||
var val = prop?.GetValue(dbgenByName)?.ToString(); // ví dụ "Identity"
|
||||
isIdentityByName = string.Equals(val, "Identity", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (hasKeyByName && isIdentityByName) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<PropertyInfo> KeyPropertiesCache(Type type)
|
||||
{
|
||||
if (KeyProperties.TryGetValue(type.TypeHandle, out var cachedProps))
|
||||
@ -68,19 +94,42 @@ namespace TWASys_App.Dapper.AExtentions
|
||||
}
|
||||
|
||||
var allProperties = TypePropertiesCache(type);
|
||||
var keyProperties = allProperties.Where(p => p.GetCustomAttributes(true).Any(a => a.GetType().Name == "KeyAttribute")).ToList();
|
||||
var keyProperties = allProperties.Where(IsPkAutoIncrement).ToList();
|
||||
KeyProperties[type.TypeHandle] = keyProperties; // ghi cache khi miss
|
||||
return keyProperties;
|
||||
}
|
||||
|
||||
if (keyProperties.Count == 0)
|
||||
|
||||
public static List<PropertyInfo> FKPropertiesCache(Type type)
|
||||
{
|
||||
if (FKProperties.TryGetValue(type.TypeHandle, out var cachedProps))
|
||||
{
|
||||
var idProp = allProperties.Find(p => string.Equals(p.Name, "id", StringComparison.CurrentCultureIgnoreCase));
|
||||
if (idProp != null)
|
||||
return cachedProps.ToList();
|
||||
}
|
||||
var allProperties = TypePropertiesCache(type);
|
||||
var keyProperties = allProperties.Where(HasForeignKeyAttribute).ToList();
|
||||
FKProperties[type.TypeHandle] = keyProperties;
|
||||
return keyProperties;
|
||||
}
|
||||
static bool HasForeignKeyAttribute(PropertyInfo p)
|
||||
{
|
||||
if (p.GetCustomAttribute<ForeignKeyAttribute>() != null) return true;
|
||||
|
||||
// FK đặt trên navigation và chỉ ra tên prop FK
|
||||
var t = p.DeclaringType!;
|
||||
foreach (var nav in t.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
|
||||
// Fallback by-name nếu không tham chiếu DataAnnotations (tùy dự án)
|
||||
var attr = nav.GetCustomAttributes(true).FirstOrDefault(a => a.GetType().Name == "ForeignKeyAttribute");
|
||||
if (attr != null)
|
||||
{
|
||||
keyProperties.Add(idProp);
|
||||
var val = attr.GetType().GetProperty("Name")?.GetValue(attr);
|
||||
if (val == null && string.IsNullOrWhiteSpace(val?.ToString()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
KeyProperties[type.TypeHandle] = keyProperties;
|
||||
return keyProperties;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<PropertyInfo> ComputedPropertiesCache(Type type)
|
||||
@ -95,7 +144,7 @@ namespace TWASys_App.Dapper.AExtentions
|
||||
return computedProperties;
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, string> GetColumnNames(IEnumerable<PropertyInfo> props)
|
||||
public static IReadOnlyDictionary<string, string> GetColumnNames(IEnumerable<PropertyInfo> props)
|
||||
{
|
||||
var ret = new Dictionary<string, string>();
|
||||
foreach (var prop in props)
|
||||
|
||||
Reference in New Issue
Block a user