using System.Collections.Concurrent; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Reflection; namespace TWASys_App.Dapper.AExtentions { public class PropertiesCache { private static readonly ConcurrentDictionary> KeyProperties = new(); private static readonly ConcurrentDictionary> FKProperties = new(); private static readonly ConcurrentDictionary> TypeProperties = new(); private static readonly ConcurrentDictionary> ComputedProperties = new(); private static readonly ConcurrentDictionary> ColumnNames = new(); public static List TypePropertiesCache(Type type) { if (TypeProperties.TryGetValue(type.TypeHandle, out var cachedProps)) { return cachedProps.ToList(); } var properties = type.GetProperties().Where(ValidateProperty).ToList(); TypeProperties[type.TypeHandle] = properties; ColumnNames[type.TypeHandle] = GetColumnNames(properties); return properties.ToList(); } public static IReadOnlyDictionary GetColumnNamesCache(Type type) { if (ColumnNames.TryGetValue(type.TypeHandle, out var cachedProps)) { return cachedProps; } var properties = type.GetProperties().Where(ValidateProperty).ToList(); TypeProperties[type.TypeHandle] = properties; ColumnNames[type.TypeHandle] = GetColumnNames(properties); return ColumnNames[type.TypeHandle]; } public static bool ValidateProperty(PropertyInfo prop) { var result = prop.CanWrite; result = result && (prop.GetSetMethod(true)?.IsPublic ?? false); result = result && (!prop.PropertyType.IsClass || prop.PropertyType == typeof(string) || prop.PropertyType == typeof(byte[])); result = result && prop.GetCustomAttributes(true).All(a => a.GetType().Name != "NotMappedAttribute"); var writeAttribute = prop.GetCustomAttributes(true).FirstOrDefault(x => x.GetType().Name == "WriteAttribute"); if (writeAttribute != null) { var writeProperty = writeAttribute.GetType().GetProperty("Write"); if (writeProperty != null && writeProperty.PropertyType == typeof(bool)) { result = result && (bool)writeProperty.GetValue(writeAttribute); } } 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 KeyPropertiesCache(Type type) { if (KeyProperties.TryGetValue(type.TypeHandle, out var cachedProps)) { return cachedProps.ToList(); } var allProperties = TypePropertiesCache(type); var keyProperties = allProperties.Where(IsPkAutoIncrement).ToList(); KeyProperties[type.TypeHandle] = keyProperties; // ghi cache khi miss return keyProperties; } public static List FKPropertiesCache(Type type) { if (FKProperties.TryGetValue(type.TypeHandle, out var cachedProps)) { 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() != 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) { var val = attr.GetType().GetProperty("Name")?.GetValue(attr); if (val == null && string.IsNullOrWhiteSpace(val?.ToString())) return true; } } return false; } public static List ComputedPropertiesCache(Type type) { if (ComputedProperties.TryGetValue(type.TypeHandle, out var cachedProps)) { return cachedProps.ToList(); } var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a.GetType().Name == "ComputedAttribute")).ToList(); ComputedProperties[type.TypeHandle] = computedProperties; return computedProperties; } public static IReadOnlyDictionary GetColumnNames(IEnumerable props) { var ret = new Dictionary(); foreach (var prop in props) { var columnAttr = prop.GetCustomAttributes(false).SingleOrDefault(attr => attr.GetType().Name == "ColumnAttribute") as dynamic; // if the column attribute exists, and specifies a column name, use that, otherwise fall back to the property name as the column name ret.Add(prop.Name, columnAttr != null ? (string)columnAttr.Name ?? prop.Name : prop.Name); } return ret; } } }