使用反射更新实体关联对象时遇空对象赋值问题求助
反射更新实体关联对象时处理null关联的问题
我尝试用反射更新实体的关联对象,思路是先查找实体的顶层属性,若找到传入的属性名则直接操作;未找到则遍历所有关联对象继续查找,找到后返回对应的PropertyInfo与关联对象。
实体类定义如下:
class UserArea{ public int Id { get; set; } public Department Department { get; set; } public int DepartmentId { get; set; } public Area Area { get; set; } public int AreaId { get; set; } public User User { get; set; } public string UserId { get; set; } }
我通过Dictionary<string, object>接收待更新的参数及对应值,例如要更新User的fullName属性时,会收到{fullName: "userName" }格式的字典。
目前的问题不是属性查找,而是找到关联属性时,父实体中的关联对象为null,C#无法直接为其属性赋值。现有代码能正常找到关联对象中的属性,但无法将值设置回父实体对象,代码如下:
private async Task<TEntity?> EditObject<TEntity>(int key, Dictionary<string, string> properties) { if (key <= 0 || properties == null) { throw new ArgumentException("Invalid input parameters"); } var setMethod = typeof(UsafeDbContext).GetMethod(nameof(UsafeDbContext.Set), Type.EmptyTypes); var dbSet = setMethod.MakeGenericMethod(typeof(TEntity)).Invoke(_context, null); var filteredResult = ((IQueryable<TEntity>)dbSet).AsEnumerable().Where(entity => entity.GetType().GetProperty("Id").GetValue(entity).ToString() == key.ToString()); var entityObject = filteredResult.FirstOrDefault(); var entityKeys = typeof(TEntity).GetProperties(); foreach (var propKey in properties.Keys) { var propertyName = propKey.ToLower(); // Modify to handle case sensitivity if desired var (propertyInfo, relatedObject) = FindProperty(entityKeys, propertyName, entityObject).Value; if (propertyInfo != null) { try { var propertyType = propertyInfo.PropertyType; var propertyValue = Convert.ChangeType(properties[propKey], propertyType); if (propertyType.IsEnum) { propertyValue = Enum.Parse(propertyType, properties[propKey]); } propertyInfo.SetValue(entityObject, propertyValue); } catch (Exception ex) { // Properly handle or log the exception Console.WriteLine($"{ex.Message}\r\n {ex.StackTrace}"); } } else if (relatedObject != null) { var relatedPropertyInfo = relatedObject.GetType().GetProperty(propertyName); if (relatedPropertyInfo != null) { try { var propertyType = relatedPropertyInfo.PropertyType; var propertyValue = Convert.ChangeType(properties[propKey], propertyType); if (propertyType.IsEnum) { propertyValue = Enum.Parse(propertyType, properties[propKey]); } relatedPropertyInfo.SetValue(relatedObject, propertyValue); } catch (Exception ex) { // Properly handle or log the exception Console.WriteLine($"{ex.Message}\r\n {ex.StackTrace}"); } } } } return entityObject; } private (PropertyInfo PropertyInfo, object RelatedObject)? FindProperty(PropertyInfo[] entityKeys, string propertyName, object entityObject) { var topProps = entityKeys .FirstOrDefault(x => x.Name.ToLower() == propertyName && x.GetCustomAttribute(typeof(IgnorePropertyAttribute)) is null); if (topProps is not null) return (topProps, null); var relatedObjects = entityKeys .Where(p => p.PropertyType.IsClass && p.PropertyType.Namespace == "usafe.Models") .ToList(); foreach (var relatedObject in relatedObjects) { var relatedEntity = relatedObject.PropertyType.GetProperties().FirstOrDefault(x => x.Name.ToLower() == propertyName); if (relatedEntity != null) { if (relatedEntity.GetCustomAttribute(typeof(IgnorePropertyAttribute)) is null) { var relatedPropertyValue = relatedObject.GetValue(entityObject); return (relatedEntity, relatedPropertyValue); } } } return null; }
解决方法
核心问题是关联对象为null时,必须先实例化该对象,再给其属性赋值,最后将实例化后的对象设置回父实体的关联属性。需要调整代码逻辑:
1. 修改FindProperty方法,返回父实体的关联属性
调整返回值,增加父实体上的关联属性信息,以便后续设置实例化后的对象:
private (PropertyInfo TargetProperty, object RelatedObject, PropertyInfo ParentRelatedProperty)? FindProperty(PropertyInfo[] entityKeys, string propertyName, object entityObject) { // 优先查找顶层属性 var topProps = entityKeys .FirstOrDefault(x => x.Name.ToLower() == propertyName && x.GetCustomAttribute(typeof(IgnorePropertyAttribute)) is null); if (topProps != null) return (topProps, null, null); // 遍历所有关联对象类型 var relatedObjects = entityKeys .Where(p => p.PropertyType.IsClass && p.PropertyType.Namespace == "usafe.Models") .ToList(); foreach (var parentRelatedProp in relatedObjects) { var relatedType = parentRelatedProp.PropertyType; // 在关联对象中查找目标属性 var targetProp = relatedType.GetProperties().FirstOrDefault(x => x.Name.ToLower() == propertyName && x.GetCustomAttribute(typeof(IgnorePropertyAttribute)) is null); if (targetProp != null) { var relatedObject = parentRelatedProp.GetValue(entityObject); return (targetProp, relatedObject, parentRelatedProp); } } return null; }
2. 修改EditObject方法,处理null关联对象
在处理关联属性时,先判断关联对象是否为null,为空则实例化并设置回父实体,再赋值目标属性:
private async Task<TEntity?> EditObject<TEntity>(int key, Dictionary<string, string> properties) { if (key <= 0 || properties == null) { throw new ArgumentException("Invalid input parameters"); } var setMethod = typeof(UsafeDbContext).GetMethod(nameof(UsafeDbContext.Set), Type.EmptyTypes); var dbSet = setMethod.MakeGenericMethod(typeof(TEntity)).Invoke(_context, null); var filteredResult = ((IQueryable<TEntity>)dbSet).AsEnumerable().Where(entity => entity.GetType().GetProperty("Id").GetValue(entity).ToString() == key.ToString()); var entityObject = filteredResult.FirstOrDefault(); if (entityObject == null) return null; var entityKeys = typeof(TEntity).GetProperties(); foreach (var propKey in properties.Keys) { var propertyName = propKey.ToLower(); var findResult = FindProperty(entityKeys, propertyName, entityObject); if (!findResult.HasValue) continue; var (targetProp, relatedObject, parentRelatedProp) = findResult.Value; if (parentRelatedProp == null) { // 顶层属性直接赋值 try { var propertyType = targetProp.PropertyType; var propertyValue = Convert.ChangeType(properties[propKey], propertyType); if (propertyType.IsEnum) { propertyValue = Enum.Parse(propertyType, properties[propKey]); } targetProp.SetValue(entityObject, propertyValue); } catch (Exception ex) { Console.WriteLine($"{ex.Message}\r\n {ex.StackTrace}"); } } else { // 处理关联属性 object targetRelatedObject = relatedObject; if (targetRelatedObject == null) { // 实例化关联对象(要求关联类有无参构造函数) targetRelatedObject = Activator.CreateInstance(parentRelatedProp.PropertyType); // 将实例设置回父实体的关联属性 parentRelatedProp.SetValue(entityObject, targetRelatedObject); } try { var propertyType = targetProp.PropertyType; var propertyValue = Convert.ChangeType(properties[propKey], propertyType); if (propertyType.IsEnum) { propertyValue = Enum.Parse(propertyType, properties[propKey]); } targetProp.SetValue(targetRelatedObject, propertyValue); } catch (Exception ex) { Console.WriteLine($"{ex.Message}\r\n {ex.StackTrace}"); } } } // 若需保存到数据库,添加以下代码 // await _context.SaveChangesAsync(); return entityObject; }
注意事项
- 若关联类没有无参构造函数,
Activator.CreateInstance会抛出异常,此时需要手动处理构造参数(例如查找合适的构造函数并传入参数)。 - 如果使用EF Core,更新关联对象后需要调用
SaveChangesAsync才能将变更持久化到数据库。
内容的提问来源于stack exchange,提问作者Ch Usman
相关产品推荐
相关产品推荐

