Consultar modelos abstractos en apuesto

c# dapper inheritance table-per-hierarchy

Pregunta

Estoy usando la herencia de base de datos Table Per Hierarchy donde las columnas para todos los tipos derivados están en una sola tabla. Cada tabla derivada se identifica utilizando un campo discriminador de cadena que contiene el nombre de la clase derivada:

---------------------
| tanimal           |
---------------------
| animalid          |
| discriminator     |
| furcolour         |
| feathercolour     |
---------------------

public abstract class Animal
{
    public int AnimalId { get; set; }
    public string Discriminator { get { return GetType().Name; } }
}

public class Bird : Animal
{
    public string FeatherColour { get; set; }
}

public class Dog : Animal
{
    public string FurColour { get; set; }
}

Como era de esperar, al recuperar esto a través del método de consulta de Dapper, recibo Instances of abstract classes cannot be created . Espero que esto devuelva una lista de Animal con sus valores siendo los respectivos tipos derivados.

var animals = Connection.Query<Animal>("SELECT * FROM tanimal")

Mis intentos de agregar soporte para esto han sido infructuosos. Antes de que se llame a SqlMapper.cs :: GetTypeDeserializer () si el tipo que se pasa es una clase abstracta, entonces reemplace el tipo con el que se devuelve en el siguiente método:

static Type GetDerivedType(Type abstractType, IDataReader reader)
{
    var discriminator = abstractType.GetProperty("Discriminator");
    if (discriminator == null)
        throw new InvalidOperationException("Cannot create instance of abstract class " + abstractType.FullName + ". To allow dapper to map to a derived type, add a Discriminator field that stores the name of the derived type");

    return Type.GetType((string)reader["Discriminator"]);
}

Sin embargo, parece que en este momento el lector no se ha abierto, por lo que falla con un Invalid attempt to read when no data is present .

¿Es este el enfoque correcto para tomar? ¿Ha habido algún esfuerzo para apoyar esto en otro lugar?

Respuesta popular

Puede hacer que esto funcione, pero será menos eficiente que usar el comportamiento predeterminado de Dapper con tablas separadas.

GetDeserializer necesita ser llamado para cada fila, lo que significa que debe ocurrir dentro de while (reader.Read())

Al modificar QueryImpl<T> puede lograr el resultado que desea. Suponiendo que obtiene los resultados con:

var results = connection.Query<Animal>("SELECT * FROM tanimal");

Entonces, el comienzo del bloque try {} de QueryImpl<T> será:

try
{
cmd = command.SetupCommand(cnn, info.ParamReader);

if (wasClosed) cnn.Open();

// We can't use SequentialAccess any more - this will have a performance hit.
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
wasClosed = false; 

// You'll need to make sure your typePrefix is correct to your type's namespace
var assembly = Assembly.GetExecutingAssembly();
var typePrefix = assembly.GetName().Name + ".";

while (reader.Read())
{
    // This was already here
    if (reader.FieldCount == 0) //https://code.google.com/p/dapper-dot-net/issues/detail?id=57
        yield break;

    // This has been moved from outside the while
    int hash = GetColumnHash(reader);

    // Now we're creating a new DeserializerState for every row we read 
    // This can be made more efficient by caching and re-using for matching types
    var discriminator = reader["discriminator"].ToString();
    var convertToType = assembly.GetType(typePrefix + discriminator);

    var tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(convertToType, reader, 0, -1, false));
    if (command.AddToCache) SetQueryCache(identity, info);

    // The rest is the same as before except using our type in ChangeType
    var func = tuple.Func;

    object val = func(reader);
    if (val == null || val is T)
    {
        yield return (T)val;
    }
    else
    {
        yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
    }
}
// The rest of this method is the same

Esto hará que el método solo funcione con el campo discriminador, por lo que es posible que desee crear su propio QueryImpl<T> si necesita que esto funcione normalmente con otras consultas. Además, no puedo garantizar que esto funcione en todos los casos, solo probado con dos filas, una de cada tipo, pero este debería ser un buen punto de partida.



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é