Dapper mapeo de múltiples uniones a un solo objeto que no devuelve ningún registro

asp.net-core c# dapper mysql

Pregunta

Estoy tratando de usar Dapper en una aplicación ASP.Net Core para asignar varias tablas a un objeto con otros objetos como sus propiedades.

Mis tablas son las siguientes (solo resumen básico):

  • tabla de usuario
  • tabla de direcciones (no hay identificadores de usuario almacenados en esta tabla, esta tabla es solo registros de direcciones)
  • Tabla de dirección_ tipo (tabla de búsqueda)

  • tabla phone_number (no hay ID de usuario almacenados en esta tabla, esta tabla solo es registros telefónicos)

  • phone_number _type table (tabla de búsqueda)
  • tabla user_has_address - esta tabla tiene un user_id, address_id y address_type_id
  • user_has_phone_number table - esta tabla tiene un user_id, phone_number _id y phone_number _type_id

Básicamente, lo que sucede es que los resultados se devuelven correctamente si todos los usuarios no tienen registros de direcciones o de teléfono o si solo el primer usuario de la lista tiene registros de direcciones / teléfonos. Lo que quiero es que, si un usuario tiene una dirección / número de teléfono, ese diccionario esté ocupado, de lo contrario, todavía quiero toda la información del usuario, pero ese diccionario de dirección / número de teléfono estará vacío.

Mis objetos se ven así:

public class User
{
  public uint id { get; set; }
  public DateTime modified_date { get; set; }
  public uint modified_by { get; set; }
  public string user_name { get; set; }
  public uint company_code { get; set; }
  public string email { get; set; }
  public bool active { get; set; }
  public string first_name { get; set; }
  public string last_name { get; set; }        
  public Dictionary<uint, Address.Address> addresses { get; set; }
  public Dictionary<uint, Address.PhoneNumber> phone_numbers { get; set; }
}

public class Address
{
  public uint address_id { get; set; }
  public AddressType address_type { get; set; }
  public string address_line1 { get; set; }
  public string address_line2 { get; set; }
  public string address_line3 { get; set; }
  public string city { get; set; }
  public string state { get; set; }
  public string country_code { get; set; }
  public string postal_code { get; set; }
  public sbyte is_po_box { get; set; }
}

public class AddressType
{
  public uint id { get; set; }
  public string name { get; set; }
}

public class PhoneNumber
{
  public uint id { get; set; }
  public PhoneNumberType phone_number_type { get; set; }
  public string phone_number { get; set; }
  public string phone_ext { get; set; }
}

public class PhoneNumberType
{
  public uint id { get; set; }
  public string name { get; set; }
}

Aquí está mi función donde trato de usar Dapper para mapear a la clase User:

public List<User> GetUsersByStatus(uint companyCode, string status)
    {           
        if (companyCode == 0)
            throw new ArgumentOutOfRangeException("companyID", "The Company ID cannot be 0.");

        List<User> Users = new List<User>();
        try
        {
            string sql = @"SELECT u.*, ad.*, adt.*, p.*, pt.* 
                FROM master.user u
                LEFT JOIN master.user_has_address AS uha ON uha.user_id = u.id                    
                LEFT JOIN master.address AS ad ON ad.id = uha.address_id
                LEFT JOIN master.lookup_address_type adt ON adt.id = uha.address_type_id
                LEFT JOIN master.user_has_phone_number AS uhp ON uhp.user_id = u.id                    
                LEFT JOIN master.phone_number AS p ON p.id = uhp.phone_number_id
                LEFT JOIN master.lookup_phone_number_type pt ON pt.id = uhp.phone_number_type_id
                WHERE u.company_code = " + companyCode;

            switch (status)
            {
                case "1":
                    // Active Status.
                    sql = sql + " AND (u.active = TRUE)";
                    break;
                case "2":
                    // Retired Status.
                    sql = sql + " AND (u.active = FALSE)";
                    break;
            }
            sql = sql + " ORDER BY u.user_name";

            using (var conn = new MySqlConnection(connectionString))
            {
                conn.Open();

                var userDictionary = new Dictionary<uint, User>();

                conn.Query<User, Address, AddressType, PhoneNumber, PhoneNumberType, User>(sql, (u, ad, adt, p, pt) =>
                {
                    User user;
                    if (!userDictionary.TryGetValue(u.id, out user))
                        userDictionary.Add(u.id, user = u);

                    if (ad != null && adt != null)
                    {
                        Address address = ad;
                        address.address_type = new AddressType() { id = adt.id, name = adt.name };

                        if (user.addresses == null)
                            user.addresses = new Dictionary<uint, Address>();

                        if (!user.addresses.ContainsKey(adt.id))
                            user.addresses.Add(adt.id, address);
                    }

                    if (p != null && pt != null)
                    {
                        PhoneNumber phone = p;
                        phone.phone_number_type = new PhoneNumberType() { id = pt.id, name = pt.name };

                        if (user.phone_numbers == null)
                            user.phone_numbers = new Dictionary<uint, PhoneNumber>();

                        if (!user.phone_numbers.ContainsKey(pt.id))
                            user.phone_numbers.Add(pt.id, phone);
                    }

                    return user;
                },
                splitOn: "id,id,id,id").AsQueryable();

                Users = userDictionary.Values.ToList();
            }
        }
        catch(Exception ex)
        {
            //TO DO: log exception
        }

        return Users;
    }

He intentado rastrearlo y, en los casos en que el tercer usuario (por ejemplo) tiene registros de dirección / teléfono, parece captar bien a los dos primeros usuarios pero luego salta directamente de la porción de consulta cuando llega al registro con la dirección / números de teléfono y luego devuelve una lista de usuarios vacía.

¿Alguien tiene alguna idea de lo que estoy haciendo mal?

Respuesta aceptada

Resulta que el problema principal fue el hecho de que dejé el parámetro address_id en la clase de dirección con el nombre que tenía, debería haber sido solo 'id'. Lo actualicé y funciona ahora, también actualicé mi código de acuerdo con la sugerencia de Palle Due, por lo que puede haber contribuido también.


Respuesta popular

No estoy seguro de que esta sea la solución, pero tengo un problema con este código:

User user;
if (!userDictionary.TryGetValue(u.id, out user))
    userDictionary.Add(u.id, user = u);

Recuerde que u es el parámetro de la lambda, y se cambiará durante la ejecución. Yo haría esto:

User user;
if (!userDictionary.TryGetValue(u.id, out user))
{
    user = new User(u); // Make a new instance of user 
    userDictionary.Add(u.id, user);
}

También definitivamente deberías usar parámetros para tu consulta:

 WHERE u.company_code = @CompanyCode";

Y, finalmente, no creo que deba ser responsabilidad de este código construir diccionarios para almacenar direcciones y números telefónicos. El constructor del usuario debe encargarse de eso.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué