Interroger des modèles abstraits dans Dapper

c# dapper inheritance table-per-hierarchy

Question

J'utilise l'héritage de base de données de la table par hiérarchie où les colonnes de tous les types dérivés se trouvent dans une seule table. Chaque table dérivée est identifiée à l'aide d'un champ Chaîne discriminante contenant le nom de la classe dérivée:

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

Comme prévu, lors de la récupération via la méthode de requête de Dapper, je Instances of abstract classes cannot be created . J'espère que cela retournerait une liste d'animaux avec leurs valeurs étant les types dérivés respectifs.

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

Mes tentatives pour ajouter un support pour cela ont été infructueuses. Avant d'appeler SqlMapper.cs :: GetTypeDeserializer () si le type transmis est une classe abstraite, je remplace le type par celui renvoyé dans la méthode suivante:

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"]);
}

Quoi qu'il en soit, à ce stade, le lecteur n'a pas été ouvert, de sorte qu'il échoue avec la Invalid attempt to read when no data is present .

Est-ce la bonne approche à prendre? Y a-t-il eu des efforts pour soutenir cela ailleurs?

Réponse populaire

Vous pouvez faire cela mais ce sera moins efficace que d'utiliser le comportement par défaut de Dapper avec des tables séparées.

GetDeserializer doit être appelé pour chaque ligne, ce qui signifie qu’il doit se produire à l’intérieur du while (reader.Read())

En modifiant QueryImpl<T> vous pouvez obtenir le résultat souhaité. En supposant que vous obtenez les résultats avec:

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

Alors le début du bloc try {} de QueryImpl<T> sera:

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

Cela permettra à la méthode de fonctionner uniquement avec le champ discriminateur, vous pouvez donc créer votre propre QueryImpl<T> si vous en avez besoin pour fonctionner normalement avec d'autres requêtes. En outre, je ne peux pas garantir que cela fonctionnera dans tous les cas, seulement testé avec deux lignes, une de chaque type - mais cela devrait être un bon point de départ.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi