【问题标题】:C# Comparing complex objects returning list of differencesC#比较复杂对象返回差异列表
【发布时间】:2013-12-10 09:16:16
【问题描述】:

我一直在从事一个项目,以解析 csv 文件中的条目列表并使用该数据更新数据库。

对于每个条目,我都会创建一个新的用户实例,并将其放入一个集合中。现在我想迭代该集合并将用户条目与数据库中的用户进行比较(如果存在)。我的问题是,如何将该用户(条目)对象与用户(db)对象进行比较,同时返回一个有差异的列表?

例如以下从数据库生成的类:

public class User
{
    public int ID { get; set; }
    public string EmployeeNumber { get; set; }
    public string UserName { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Nullable<int> OfficeID { get; set; }

    public virtual Office Office { get; set; }
}

public class Office
{
    public int ID { get; set; }
    public string Code { get; set; }

    public virtual ICollection<User> Users { get; set; }
}

为了将一些查询保存到数据库中,我只填写了我可以从 csv 文件中检索到的属性,因此 ID(例如)不可用于相等性检查。

有没有办法比较这些对象而不为每个属性定义规则并返回修改的属性列表?我知道这个问题似乎与之前的一些帖子相似。我已经阅读了很多,但由于我在编程方面相当缺乏经验,因此我很感激一些建议。

根据我所阅读的内容,我是否应该将“一般比较属性”与“使用数据注释忽略属性”和“返回比较结果列表”结合起来?

【问题讨论】:

  • 为了更好地理解您的问题,如果您的 CSV 文件中没有 ID,您使用什么作为用户的唯一标识符?即您何时决定不创建新条目?
  • 哦,对不起,我遗漏了一些属性以使示例稍微扁平化。我将编辑示例。唯一标识符是员工编号,这当然是决定是否在数据库中创建新用户所必需的。
  • 是的,我有,但我想知道如何处理像 Office 这样的引用类型?我应该为这些类型实现 IEquatable 接口,以便我也可以在那里使用 equals 方法吗?

标签: c# object instance equality


【解决方案1】:

有几种方法可以解决这个问题:
方法 #1 是为 CSV 文件的内容创建单独的 DTO 样式类。虽然这涉及到创建具有许多相似字段的新类,但它将 CSV 文件格式与您的数据库分离,并让您能够在不影响其他部分的情况下稍后更改它们。为了实现比较,您可以创建一个 Comparer 类。只要类几乎相同,比较就可以从 DTO 类中获取所有属性并动态实现比较(例如,通过创建和评估包含 Equal 类型的 BinaryExpression 的 Lambda 表达式)。
方法#2 避免了 DTO,但使用属性来标记作为比较一部分的属性。您需要创建一个自定义属性,将其分配给相关属性。在比较中,您分析类的所有属性并过滤掉标记有属性的那些。对于属性的比较,您可以使用与 #1 中相同的方法。这种方法的缺点是您将比较逻辑与数据类紧密耦合。如果您需要实现几个不同的比较,您会用属性将数据类弄得一团糟。
当然,#1 比#2 付出更大的努力。我知道这不是您要寻找的,但也许拥有一个单独的、强类型的比较类也是一种可以考虑的方法。
更多动态比较算法的详细信息:它基于反射来获取需要比较的属性(取决于您获取 DTO 的属性或数据类的相关属性的方法)。一旦有了属性(对于 DTO,属性应该具有相同的名称和数据类型),您可以创建一个 LamdaExpression 并动态编译和评估它。以下几行显示了代码示例的摘录:

public static bool AreEqual<TDTO, TDATA>(TDTO dto, TDATA data) 
{
    foreach(var prop in typeof(TDTO).GetProperties())
    {
        var dataProp = typeof(TDATA).GetProperty(prop.Name);
        if (dataProp == null)
            throw new InvalidOperationException(string.Format("Property {0} is missing in data class.", prop.Name));
        var compExpr = GetComparisonExpression(prop, dataProp);
        var del = compExpr.Compile();
        if (!(bool)del.DynamicInvoke(dto, data))
            return false;
    }
    return true;
}

private static LambdaExpression GetComparisonExpression(PropertyInfo dtoProp, PropertyInfo dataProp)
{
    var dtoParam = Expression.Parameter(dtoProp.DeclaringType, "dto");
    var dataParam = Expression.Parameter(dataProp.DeclaringType, "data");
    return Expression.Lambda(
        Expression.MakeBinary(ExpressionType.Equal, 
            Expression.MakeMemberAccess(
                dtoParam, dtoProp), 
            Expression.MakeMemberAccess(
                dataParam, dataProp)), dtoParam, dataParam);
}

有关完整示例,请参阅此link。请注意,这种动态方法只是一种简单的实现,它还有改进的余地(例如,没有检查属性的数据类型)。它也只检查是否相等,不收集不相等的属性;但这应该很容易转移。
虽然动态方法很容易实现,但运行时错误的风险比强类型方法更大。

【讨论】:

  • 感谢您的意见,马库斯。我从单独的 DTO 类开始,我会再看看它。您能否详细说明一下 Comparer 类,您有点把我弄丢了。
  • 感谢您的示例。我必须承认 GetComparisonExpression 部分超出了我的想象。我会玩一点,看看我能不能绕开它,我会在一段时间内发布更新。
  • 看起来比实际复杂;它组装了一个比较 DTO 和数据类的属性的表达式。 lambda 表达式接收 DTO 和数据类作为参数,并包含类似于 (dto, dataCls) => dto.PropName == dataCls.PropName 的动态表达式,其中 PropName 由 PropertyInfo 提供。由于比较的每个属性的属性名称都不同,因此它需要是动态的。在运行时编译 lambda 表达式后,以 DTO 和数据类作为参数调用它。
猜你喜欢
  • 1970-01-01
  • 2011-04-01
  • 2014-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-29
  • 1970-01-01
  • 2019-08-22
相关资源
最近更新 更多