Ayer mismo estaba intentando analizar unos data_types para completar una exportación al backend. La idea era sencilla, buscar para cada usuario la ultima información disponible en una tabla que representaba actualizaciones sobre su perfil.
Como a su perfil podías “añadir”, “actualizar” y “eliminar” cosas, pues existian los tres tipos en la tabla. Para que la imaginemos mejor sería tal que así:
user_id | favorite_stuff | operation | metadata |
---|---|---|---|
A | Chocolate | Add | … |
A | Chocolate | Update | … |
B | Milk | Remove | … |
B | Cornflakes | Add | … |
De tal manera que habría que combinar todos los eventos para saber cual es el perfil actual del usuario y aplicar cierta lógica. Sin embargo los eventos que habían llegado realmente eran así:
user_id | favorite_stuff | operation | metadata |
---|---|---|---|
A | Chocolate | Add | … |
A | Chocolate | Update | … |
A | Chocolate | Remove | … |
B | Milk | Add | … |
B | Milk | Update | … |
B | Milk | Remove | … |
B | Cornflakes | Add | … |
B | Cornflakes | Update | … |
B | Cornflakes | Remove | … |
Y así para toda combinación de user_id / favorite_stuff.
Y como buen “golifión” que soy, me puse a intentar encontrar el problema en backend porque el comportamiento era realmente anómalo. Está hecho en C# y aún me acuerdo de algo :-)
Y encontré el método que buscaba.
(El except es el equivalente a la resta de conjuntos, el minus, vaya).
private IEnumerable<DataEvent> PrepareUpdatesEventsToSend(IEnumerable<FavoriteStuff> favoriteStuffUpdate, IEnumerable<FavoriteStuff> previousFavoriteStuff)
{
var events = new List<DataEvent>();
var addedElements = favoriteStuffUpdate.Except(previousFavoriteStuff).ToList();
addedElements.ForEach(x => events.Add(CreateFavoriteStuffUpdateEvent(x, "Add")));
var removedElements = previousFavoriteStuff.Except(favoriteStuffUpdate).ToList();
addedElements.ForEach(x => events.Add(CreateFavoriteStuffUpdateEvent(x, "Remove")));
var updatedElements = favoriteStuffUpdate.Except(addedElements).ToList();
addedElements.ForEach(x => events.Add(CreateFavoriteStuffUpdateEvent(x, "Update")));
return events;
}
La lógica es bastante buena y la idea promete. Sin embargo (aunque aquí no se pueda determinar), este código no funciona a menos de que añadamos algo más, ¿por qué? Con números simples la idea funciona perfecta…
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var a = new List<int>(){1,2,3,4};
var b = new List<int>(){2,3,4};
var c = a.Except(b);
Console.WriteLine(String.Join(" ", c));
}
}
1
Pero aquí estamos comparando dos enumerables de FavoriteStuff usando el comparador por defecto, que en este caso es el equals y busca la identidad de los objetos.
De hecho con un ejemplo muy simple:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var a = new List<MyFakeInt>(){new MyFakeInt(){MyNumber = 1},new MyFakeInt(){MyNumber = 2},new MyFakeInt(){MyNumber = 3},new MyFakeInt(){MyNumber = 4}};
var b = new List<MyFakeInt>(){new MyFakeInt(){MyNumber = 2},new MyFakeInt(){MyNumber = 3},new MyFakeInt(){MyNumber = 4}};
var c = a.Except(b);
Console.WriteLine(String.Join(" ", c));
}
}
class MyFakeInt {
public int MyNumber {get; set;}
}
MyFakeInt, MyFakeInt, MyFakeInt, MyFakeInt
Nos devuelve toda la lista de a porque sus identidades son diferentes.
Ya está reportado y se arreglará, pero siempre es curioso ver comos las bases son vitales para encontrar problemas :)