2017-08-12 2 views
4

je la classe suivante que je suis en train d'hydrater à:Dapper - Gestion de cartographie personnalisée pour l'entité ddd avec des champs en lecture seule par le constructeur

public class Product 
{ 
    public readonly Sku Sku; 
    public string Name { get; private set; } 
    public string Description { get; private set; } 
    public bool IsArchived { get; private set; } 

    public Product(Sku sku, string name, string description, bool isArchived) 
    { 
     Sku = sku; 
     Name = name; 
     Description = description; 
     IsArchived = isArchived; 
    } 
} 

qui utilise les classes ci-dessous qui mettent en œuvre les concepts de mon entité DDD modèle de domaine (code non pertinent enlevé pour conserver le code court, mis en lecture seule pour rendre immuable une fois construit):

public class Sku 
{ 
    public readonly VendorId VendorId; 
    public readonly string SkuValue; 

    public Sku(VendorId vendorId, string skuValue) 
    { 
     VendorId = vendorId; 
     SkuValue = skuValue; 
    } 
} 

public class VendorId 
{ 
    public readonly string VendorShortname; 

    public VendorId(string vendorShortname) 
    { 
     VendorShortname = vendorShortname; 
    } 
} 

J'essaie et exécuter la requête paramétrées qui hydrate pour un objet produit:

using (connection) 
{ 
    connection.Open(); 
    return connection.QueryFirst<Product>(ReadQuery, new { VendorId = sku.VendorId.VendorShortname, SkuValue = sku.SkuValue }); 
} 

Il jette l'exception suivante car il ne sait pas comment traiter avec le type Sku dans le constructeur:

System.InvalidOperationException: « Un constructeur par défaut parameterless ou une signature correspondant (système. chaîne VendorId, System.String SkuValue, Nom System.String, description System.String, System.UInt64 IsArchived) est nécessaire pour Domain.Model.Products.Product matérialisation »

J'ai regardé en utilisant un SqlMapper.TypeHandler<Product> personnalisé mais le Parse(object value) passe seulement jamais dans une seule valeur analysée de la colonne de base de données VendorId (si elle a passé dans un tableau de valeurs ici, je pourrais faire la cartographie moi-même).

est-il un moyen de personnaliser la gestion de l'objet afin que je puisse passer tous les paramètres au constructeur comme ci-dessous:

using (connection) 
{ 
    var command = connection.CreateCommand(); 
    command.CommandText = "SELECT VendorShortname, SkuValue, Name, Description, IsArchived FROM Products WHERE [email protected] AND [email protected]"; 
    command.Parameters.AddWithValue("@VendorShortname", sku.VendorId.VendorShortname); 
    command.Parameters.AddWithValue("@SkuValue", sku.SkuValue); 
    connection.Open(); 

    var reader = command.ExecuteReader(); 
    if (reader.HasRows==false) 
     return null; 

    reader.Read(); 

    return new Product(
     new Sku(new VendorId(reader.GetString("VendorId")),reader.GetString("SkuValue")), 
     reader.GetString("Name"), 
     reader.GetString("Description"), 
     reader.GetBoolean("IsArchived")); 
} 

Je suppose que je pourrais créer un constructeur spécifique avec Product(string VendorShortname, string SkuValue, string Name, string Description, UInt64 IsArchived) mais je préférerait (doit) avoir cette préoccupation dans le code de mappage plutôt que dans mon modèle de domaine.

En passant par-dessus un pseudo-code, ce que je pourrais faire est de lancer mon propre ORM, mais je voudrais plutôt faire similaire via Dapper.

  1. Obtenir tous les constructeurs pour objet par réflexion
  2. Si des paramètres dans le constructeur est un type, obtenez ses constructeurs
  3. Pour chaque constructeur (y compris les paramètres), mapper le nom du constructeur à la colonne de lecteur SQL (et Type)

Cela équivaudrait à VendorShortname utilisé pour VendorId(string vendorShortname) et Name, Description, isArchived utilisé pour Product(Sku sku, string name, string description, bool isArchived) publique ... quelque chose est Simi lièrement fait par MongoDB selon ma réponse affichée à l'adresse suivante, un équivalent de cartographie manuelle Dapper serait génial MongoDB Composite Key: InvalidOperationException: {document}.Identity is not supported

+0

'mais je préfère (doit) avoir cette préoccupation dans le code de mappage plutôt que dans mon modèle de domaine. Pouvez-vous nous parler pourquoi vous ** devez ** l'avoir comme ça? – mjwills

+0

Je n'ai pas de problèmes de persistance dans mon modèle de domaine pour d'autres référentiels (MongoDB). Si possible, je voudrais utiliser SQL/Dapper pour garder les choses plus simples. Mettra à jour mon message sur ce qui devrait être fait, pourrait rouler le mien, mais je ne veux pas réinventer la roue, ni écrire du code aussi bon que – g18c

Répondre

3

Execute a query and map it to a list of dynamic objects

public static IEnumerable<dynamic> Query (
    this IDbConnection cnn, 
    string sql, 
    object param = null, 
    SqlTransaction transaction = null, 
    bool buffered = true 
) 

alors vous construire le modèle souhaité à l'aide de la liste des objets dynamiques.

Donc, en utilisant l'exemple du message original, la requête paramétrées serait changé de ...

using (connection) 
{ 
    var command = connection.CreateCommand(); 
    command.CommandText = "SELECT VendorShortname, SkuValue, Name, Description, IsArchived FROM Products WHERE [email protected] AND [email protected]"; 
    command.Parameters.AddWithValue("@VendorShortname", sku.VendorId.VendorShortname); 
    command.Parameters.AddWithValue("@SkuValue", sku.SkuValue); 
    connection.Open(); 

    var reader = command.ExecuteReader(); 
    if (reader.HasRows==false) 
     return null; 

    reader.Read(); 

    return new Product(
     new Sku(new VendorId(reader.GetString("VendorId")),reader.GetString("SkuValue")), 
     reader.GetString("Name"), 
     reader.GetString("Description"), 
     reader.GetBoolean("IsArchived")); 
} 

Pour ...

var ReadQuery = "SELECT VendorShortname, SkuValue, Name, Description, IsArchived FROM Products WHERE [email protected] AND [email protected]"; 
using (connection) { 
    connection.Open(); 
    return connection.Query(ReadQuery, new { VendorShortname = sku.VendorId.VendorShortname, SkuValue = sku.SkuValue }) 
      .Select(row => new Product(
       new Sku(new VendorId(row.VendorShortname), row.SkuValue), 
       row.Name, 
       row.Description, 
       row.IsArchived) 
      ); 
} 

Quel est le but recherché du cadre. Assurez-vous simplement que les propriétés utilisées correspondent directement aux champs renvoyés par la requête.

Cela peut sembler intensif mais c'est une solution viable étant donné la nature complexe du constructeur de l'objet cible.

1

Vous pouvez également envisager une option de découplage de vos modèles "domaine" de la persistance et de les construire ailleurs. Par exemple:

  • Créer une classe par Base de données: ProductRecord
  • Créer une usine: ProductFactory
  • Obtenir les données: var productRecords = connection.Query<ProductRecord>("select * from products").AsList();
  • construire le produit: factory.Build(productRecords)

Quelques avantages: Séparation des préoccupations, flexibilité, convient aux grands projets

Certains con s: Plus de code, un over-kill pour les petits projets

+0

Dapper Merci, oui ont fait similaire avec un enregistrement POCO et en utilisant automapper, mais peut devenir douloureux avec des classes doubles. Certainement une option valable à signaler. – g18c