【问题标题】:Clean way to check if all properties, except for two, matches between two objects? [duplicate]检查两个对象之间是否所有属性(除了两个)是否匹配的干净方法? [复制]
【发布时间】:2019-01-30 11:10:47
【问题描述】:

我有一个包含大约 20 个属性的组件的数据库。为了确定是否需要更新,我想检查这两个对象的所有属性(除了 DateCreated 和 Id 之外)是否匹配。 如果全部匹配则不更新,如果不匹配,则更新db。

Component comp_InApp = new Component()
{
    Id = null,
    Description = "Commponent",
    Price = 100,
    DateCreated = "2019-01-30",
    // Twenty more prop
};

Component comp_InDb = new Component()
{
    Id = 1,
    Description = "Component",
    Price = 100,
    DateCreated = "2019-01-01",
    // Twenty more prop
};

// Check if all properties match, except DateCreated and Id.
if (comp_InApp.Description == comp_InDb.Description &&
    comp_InApp.Price == comp_InDb.Price
    // Twenty more prop
    )
{
    // Everything up to date.
}
else
{
    // Update db.
}

这可行,但它不是一个非常干净的方式,有 20 个属性。有没有更好的方法以更清洁的方式实现相同的结果?

【问题讨论】:

  • 您可以使用EqualsGetHashCode 方法,也可以使用可以为您进行比较的等式库。
  • Comparing object properties in c# 的可能重复项。用 64 票检查答案。这样的事情很容易通过一些库来完成。
  • 不要使用字符串来存储DateTime

标签: c# .net-core


【解决方案1】:

当我不想/没有时间编写自己的EqualsGetHashCode 方法时,我正在使用DeepEqual

你可以简单地从 NuGet 安装它:

Install-Package DeepEqual

并像这样使用它:

    if (comp_InApp.IsDeepEqual(comp_InDb))
    {
        // Everything up to date.
    }
    else
    {
        // Update db.
    }

但请记住,这仅适用于您想要显式比较对象时的情况,但不适用于您想要从 List 或类似的情况下删除对象的情况,当 EqualsGetHashCode 被调用。

【讨论】:

    【解决方案2】:

    一种方法,创建一个实现IEqualityComparer<Component> 的类来封装此逻辑并避免您修改类Comparer 本身(如果您不希望一直使用此Equals 逻辑)。然后,您可以将它用于两个Component 实例的简单Equals,甚至可以用于所有接受它作为附加参数的LINQ methods

    class ComponentComparer : IEqualityComparer<Component>
    {
        public bool Equals(Component x, Component y)
        {
            if (object.ReferenceEquals(x, y)) return true;
            if (x == null || y == null) return false;
            return x.Price == y.Price && x.Description == y.Description;
        }
    
        public int GetHashCode(Component obj)
        {
            unchecked 
            {
                int hash = 17;
                hash = hash * 23 + obj.Price.GetHashCode();
                hash = hash * 23 + obj.Description?.GetHashCode() ?? 0;
                // ...
                return hash;
            }
        }
    }
    

    您的简单用例:

    var comparer = new ComponentComparer();
    bool equal = comparer.Equals(comp_InApp, comp_InDb);
    

    如果您有两个集合并想知道区别,它也可以工作,例如:

    IEnumerable<Component> missingInDb = inAppList.Except( inDbList, comparer );
    

    【讨论】:

      【解决方案3】:

      这是一个反射的解决方案:

          static bool AreTwoEqual(Component inApp, Component inDb)
          {
              string[] propertiesToExclude = new string[] { "DateCreated", "Id" };
      
              PropertyInfo[] propertyInfos = typeof(Component).GetProperties()
                                                       .Where(x => !propertiesToExclude.Contains(x.Name))
                                                       .ToArray();
      
              foreach (PropertyInfo propertyInfo in propertyInfos)
              {
                  bool areSame = inApp.GetType().GetProperty(propertyInfo.Name).GetValue(inApp, null).Equals(inDb.GetType().GetProperty(propertyInfo.Name).GetValue(inDb, null));
      
                  if (!areSame)
                  {
                      return false;
                  }
              }
      
              return true;
          }
      
      

      【讨论】:

        【解决方案4】:

        您可以使用反射,但它可能会减慢您的应用程序。创建该比较器的另一种方法是使用 Linq 表达式生成它。试试这个代码:

        public static Expression<Func<T, T, bool>> CreateAreEqualExpression<T>(params string[] toExclude)
        {
            var type = typeof(T);
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(p => !toExclude.Contains(p.Name))
                .ToArray();
        
            var p1 = Expression.Parameter(type, "p1");
            var p2 = Expression.Parameter(type, "p2");
        
            Expression body = null;
            foreach (var property in props)
            {
                var pare = Expression.Equal(
                    Expression.PropertyOrField(p1, property.Name),
                    Expression.PropertyOrField(p2, property.Name)
                );
        
                body = body == null ? pare : Expression.AndAlso(body, pare);
            }
        
            if (body == null) // all properties are excluded
                body = Expression.Constant(true);
        
            var lambda = Expression.Lambda<Func<T, T, bool>>(body, p1, p2);
            return lambda;
        }
        

        它会生成一个看起来像这样的表达式

        (Component p1, Component p2) => ((p1.Description == p2.Description) && (p1.Price == p2.Price))
        

        使用简单

        var comporator = CreateAreEqualExpression<Component>("Id", "DateCreated")
            .Compile(); // save compiled comparator somewhere to use it again later
        var areEqual = comporator(comp_InApp, comp_InDb);
        

        编辑:为了使其更安全,您可以使用 lambdas 排除属性

        public static Expression<Func<T, T, bool>> CreateAreEqualExpression<T>(
          params Expression<Func<T, object>>[] toExclude)
        {
            var exclude = toExclude
                .Select(e =>
                {
                    // for properties that is value types (int, DateTime and so on)
                    var name = ((e.Body as UnaryExpression)?.Operand as MemberExpression)?.Member.Name;
                    if (name != null)
                        return name;
        
                    // for properties that is reference type
                    return (e.Body as MemberExpression)?.Member.Name;
                })
                .Where(n => n != null)
                .Distinct()            
                .ToArray();
        
            var type = typeof(T);
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(p => !exclude.Contains(p.Name))
                .ToArray();
        
            /* rest of code is unchanged */
        }
        

        现在使用它时,我们有 IntelliSense 支持:

        var comparator = CreateAreEqualExpression<Component>(
                c => c.Id,
                c => c.DateCreated)
            .Compile();
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-03-04
          • 1970-01-01
          • 2011-09-07
          • 1970-01-01
          • 1970-01-01
          • 2021-08-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多