You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用反射更新实体关联对象时遇空对象赋值问题求助

反射更新实体关联对象时处理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 21:10:56