【问题标题】:How to update an object with non-NULL properties?如何更新具有非 NULL 属性的对象?
【发布时间】:2021-05-12 21:01:09
【问题描述】:

我正在创建一个 WebAPI 端点,其任务是更新数据库对象的某些属性。我将一个对象发送到仅包含那些可以更新的属性的端点。如果任何属性为 NULL,则程序应在更新期间忽略此属性。

目前,我能想到的就是使用 if 语句来检查以下字段是否为非 NULL。所以我的更新类看起来像这样:

if (DtoEntity.PropertyA != null)
   DbEntity.PropertyA = DtoEntity.PropertyA;
if (DtoEntity.PropertyB != null)
   DbEntity.PropertyB = DtoEntity.PropertyB;

但是,我不喜欢这个映射器的设计。有什么方法可以更有效地进行这种映射?

-- 编辑 如果在这种情况下可能,我想避免反思。我想让这个解决方案尽可能的轻便。

【问题讨论】:

  • 你可以使用反射...
  • 您希望清除属性作为更新的一部分吗?如果是这样,您将如何指定?
  • 即使不改变值,始终发送所有属性会更简单
  • @LasseV.Karlsen 不,我不想清除属性。
  • @CaiusJard,是的,但我不是在开发客户端。客户只会向我发送应该更新的属性。

标签: c#


【解决方案1】:

这可以通过 AutoMapper 使用条件映射来完成。如果您不想为每种类型的每个属性都指定此项,则可以使用 ForAllMembers 配置为类型上的所有成员进行设置:

using System;
using AutoMapper;

class Foo
{
    public string Prop1 { get;set; }
    public string Prop2 { get;set; }
}

class Bar {
    public string Prop1 { get;set; }
    public string Prop2 { get;set; }
}

public class Program
{
    
    public static void Main()
    {
        
        var configuration = new MapperConfiguration(
                cfg => cfg
                    .CreateMap<Foo,Bar>()
                    .ForAllMembers(x => x.Condition(
                      (src, dest, sourceValue) => sourceValue != null)));
        
        IMapper m = configuration.CreateMapper();
        
        var source = new Foo { Prop1 = null, Prop2 = "p2" };
            
        var dest = new Bar { Prop1 = "p3", Prop2 = "p4" };
            
        m.Map<Foo, Bar>(source, dest);
            
        // dest will now have Prop1 == "p3" and Prop2 == "p2"
    }
}

【讨论】:

  • 谢谢,这是一个适合我需要的解决方案
【解决方案2】:

如果你不想使用反射,也许你可以用字典来支持你的属性:

class DbEntity{

  public Dictionary<string, object> Props = new ...

  public string PropA
  {
    get => (string)Props[nameof(PropA)];
    set => Props[nameof(PropA)] = value;
  }
}

Dto 也是如此

然后在复制值时你可以 foreach:

foreach(var kvp in dbEntityInstance.Props.Where(x => x.Value != null))
  dtoInstance.Props[kvp.Key] = dbEntityInstance.Props[kvp.Key];

dto 也一样

【讨论】:

    【解决方案3】:

    null-coalescing operator ?? 可用于使代码更轻:

    DbEntity.PropertyA = DtoEntity.PropertyA ?? DbEntity.PropertyA;
    

    【讨论】:

      【解决方案4】:

      对于这种情况,您可以使用自动映射器,例如 https://github.com/AutoMapper/AutoMapper 该库支持将 dto 对象映射到数据库模型对象。

      配置完成后可以编写如下代码

      var myDatabaseObject = mapper.Map<MyDatabaseObject>(objectFromWebRequest);
      

      反之亦然

      var myDtoObject = mapper.Map<MyDtoObject>(objectFromDatabase);
      

      【讨论】:

        【解决方案5】:

        我知道你说过你不想使用反射,但是在 C# 中使用反射就像(非常未优化的代码)一样简单:

        public static void UpdateActualFromDto(object actual, object dto) {
        
            var dtoProps = dto.GetType().GetProperties();
            var actualProps = actual.GetType().GetProperties();
            var sameProperties = dtoProps.Where(x => actualProps.Select(a => a.Name).Contains(x.Name)).ToDictionary(x => x, x => actualProps.First(p => p.Name == x.Name));
            foreach(var (dtoProp, actualProp) in sameProperties) {
                var val = dtoProp.GetValue(dto);
                if(val != null) actualProp.SetValue(actual, val);
            }
        }
        

        只是为了好玩,我对其进行了基准测试(同样,这几乎没有优化,您甚至可以拥有 PropertyInfo 的缓存并使其更快):

        BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
        Intel Core i7-9700K CPU 3.60GHz (Coffee Lake), 1 CPU, 8 logical and 8 physical cores
        .NET Core SDK=5.0.102
          [Host]     : .NET Core 3.1.11 (CoreCLR 4.700.20.56602, CoreFX 4.700.20.56604), X64 RyuJIT  [AttachedDebugger]
          DefaultJob : .NET Core 3.1.11 (CoreCLR 4.700.20.56602, CoreFX 4.700.20.56604), X64 RyuJIT
        
        Method Mean Error StdDev
        CopyNonNull 899.7 ns 3.24 ns 2.71 ns

        基准代码是:

        
        public class Dto {
            public string PropertyA { get; set; }
            public int? PropertyB { get; set; }
        }
        
        public class Actual {
            public string PropertyA { get; set; }
            public int PropertyB { get; set; }
        }
        
        public class CopyDtoBenchmark
        {
            [Benchmark]
            public void CopyNonNull()
            {
                UpdateActualFromDto(new Actual { PropertyA = "bar", PropertyB = 5 }, new Dto { PropertyA = "foo" });
            }
        
            // UpdateActualFromDto method above
        }
        

        同样,没有缓存,基准是创建和分配对象(实际对象和 dto)。

        只是为了好玩,我添加了一些缓存来匹配 dto/entity 类型,并且时间减少了(对于这个始终使用相同类型的特定基准测试):

        Method Mean Error StdDev
        CopyNonNull 322.8 ns 1.28 ns 1.20 ns

        更新代码:

        
        private Dictionary<(Type, Type), Dictionary<PropertyInfo, PropertyInfo>> _typeMatchCache =
            new();
        
        
        public void UpdateActualFromDto(object actual, object dto)
        {
            var dtoType = dto.GetType();
            var actualType = actual.GetType();
        
            if (!_typeMatchCache.TryGetValue((dtoType, actualType), out var sameProperties))
            {
                var dtoProps = dtoType.GetProperties();
                var actualProps = actualType.GetProperties();
                sameProperties = dtoProps.Where(x => actualProps.Select(a => a.Name).Contains(x.Name)).ToDictionary(x => x, x => actualProps.First(p => p.Name == x.Name));
                _typeMatchCache.Add((dtoType, actualType), sameProperties);
            }
            foreach(var (dtoProp, actualProp) in sameProperties) {
                var val = dtoProp.GetValue(dto);
                if(val != null) actualProp.SetValue(actual, val);
            }
        }
        

        同样,这只是一个有趣的实验,如果您要使用这样的代码,您可能需要更好的检查(检查类型是否相同等),但我不会放弃反射说它是如果性能足够好以及使用它的 CPU 成本与编程成本(更重要的是,每次涉及的任何类发生变化时维护),非通用解决方案是否值得.

        顺便说一句,我不建议使用它,我可能会选择 Automapper 之类的东西(但会使用反射)

        【讨论】:

        • 那是一次AM,执行计划是compiled
        • @LucianBargaoanu 是的,我知道,是在谈论“不使用反射”,因为 OP 明确表示他不想这样做。我肯定会推荐 Automapper 来完成这项任务(但这已经在其他答案中了)
        猜你喜欢
        • 2014-10-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-08
        • 1970-01-01
        • 2016-11-14
        • 1970-01-01
        相关资源
        最近更新 更多