5 votes

Comment vérifier si toutes les propriétés, à l'exception de deux, correspondent entre deux objets ?

J'ai une base de données contenant des composants avec environ 20 propriétés. Pour savoir si une mise à jour est nécessaire, je veux vérifier si toutes les propriétés des deux objets, à l'exception de DateCreated et Id, correspondent. Si toutes les propriétés correspondent, pas de mise à jour, sinon, mise à jour de la base de données.

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.
}

Cela fonctionne, mais ce n'est pas une méthode très propre avec 20 propriétés. Existe-t-il un meilleur moyen d'obtenir le même résultat d'une manière plus propre ?

3voto

meJustAndrew Points 2566

J'utilise DeepEqual lorsque je ne veux pas/ne dispose pas du temps nécessaire pour écrire moi-même Equals y GetHashCode des méthodes.

Vous pouvez l'installer simplement à partir de NuGet avec :

Install-Package DeepEqual

et l'utiliser comme :

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

Mais gardez à l'esprit que cela ne fonctionnera que dans le cas où vous souhaitez comparer explicitement des objets, mais pas dans le cas où vous souhaitez supprimer un objet d'un fichier List ou dans des cas comme celui-ci, lorsque Equals y GetHashCode sont invoquées.

2voto

Tim Schmelter Points 163781

D'une part, créer une classe qui implémente IEqualityComparer<Component> pour encapsuler cette logique et éviter de modifier la classe Comparer lui-même (si vous ne voulez pas de ce Equals logique de tous les temps). Vous pouvez ensuite l'utiliser pour un simple Equals de deux instances de Component et même pour tous Méthodes LINQ qui l'accepte comme argument supplémentaire.

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;
        }
    }
}

Votre cas d'utilisation simple :

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

Cela fonctionne également si vous avez deux collections et que vous voulez connaître la différence, par exemple :

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

1voto

Merhat Pandzharov Points 175

Voici une solution avec Reflection :

    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;
    }

0voto

Aleks Andreev Points 3913

Vous pouvez utiliser une réflexion, mais cela risque de ralentir votre application. Une autre façon de créer ce comparateur est de le générer avec des expressions Linq. Essayez ce code :

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;
}

il génère une expression qui ressemble à

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

L'utilisation est simple

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

EDIT Pour plus de sécurité, vous pouvez exclure des propriétés à l'aide de 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 */
}

Désormais, lors de son utilisation, nous disposons d'un support IntelliSense :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X