10 votes

Stocker les objets ayant une classe de base commune dans la base de données

Disons que j'ai une classe/interface de base commune.

  interface ICommand
  {
    void Execute();
  }

Il y a ensuite quelques commandes qui héritent de cette interface.

  class CommandA : ICommand
  {
    int x;
    int y;
    public CommandA(int x, int y)
    {  ...  }
    public void Execute () { ... }
  }
  class CommandB : ICommand
  {
    string name;
    public CommandB(string name)
    {  ...  }
    public void Execute () { ... }
  }

Je veux maintenant stocker ces commandes dans une base de données, avec une méthode commune, puis les charger ultérieurement depuis la base de données dans un fichier de type List<ICommand> et exécuter la méthode Execute de ceux-ci.

Pour l'instant, je n'ai qu'une seule table dans la base de données, appelée commandes, dans laquelle je stocke la sérialisation de l'objet sous forme de chaîne. En fait, les colonnes de la table sont les suivantes id|commandType|commaSeparatedListOfParameters . Bien que cela soit très facile et fonctionne bien pour chargement de tous les commandes, je ne peux pas requête les commandes facilement sans utiliser substring et autres méthodes obscures. J'aimerais avoir un moyen simple de SELECT id,x,y FROM commandA_commands WHERE x=... et en même temps avoir un moyen générique de charger toutes les commandes de la table des commandes (je suppose que ce serait une sorte d'UNION/JOIN de commandA_commands, commandB_commands, etc).

Il est important que l'ajout d'une nouvelle commande ne nécessite pas beaucoup de manipulations manuelles dans la base de données, ou la création manuelle de méthodes de sérialisation/parse. J'en ai des tonnes et de nouvelles sont ajoutées et supprimées en permanence. Cela ne me dérange pas de créer un outil de génération de commandes, de tables et de requêtes si cela s'avère nécessaire pour obtenir la meilleure solution.

Le mieux que je puisse faire est de créer une table commune du type id|commandType|param1|param2|param3|etc.. ce qui n'est pas beaucoup mieux (en fait, pire ?) que ma solution actuelle, car de nombreuses commandes vont avoir besoin de paramètres nuls et le type de données va varier, ce qui m'oblige à recourir à nouveau à la conversion de chaîne de caractères commune et à dimensionner chaque champ suffisamment grand pour la plus grande commande.

La base de données est SQL Server 2008

Edit : J'ai trouvé une question similaire ici Conception d'une base de données SQL pour représenter la hiérarchie des classes OO

13voto

Eranga Points 21853

Vous pouvez utiliser un ORM pour faire correspondre l'héritage des commandes à la base de données. Par exemple, vous pouvez utiliser la technique "Table per Hierarchy" fournie par les ORM (ex : Entity Framework, nHibernate, etc). L'ORM instanciera la sous-classe correcte lorsque vous les récupérerez.

Voici un exemple pour le faire dans Entity Framework Code first

abstract class Command : ICommand
{
   public int Id {get;set;}

   public abstract void Execute();
}

class CommandA : Command
{
   public int X {get;set;}
   public int Y {get;set;}

   public override void Execute () { ... }
}
class CommandB : Command
{
   public string Name {get;set;}

   public override void Execute () { ... }
}

Référez-vous à EF 4.1 Premier pas dans le code pour configurer ce modèle avec EF.

Si vos commandes prennent un ensemble de paramètres radicalement différents, vous pouvez envisager d'utiliser le modèle d'héritage "Table per Type". Dans ce cas, vous devrez payer une pénalité de performance significative en raison des nombreuses Unions et jointures de tables impliquées.

Une autre approche consisterait à stocker les paramètres de la commande dans une configuration XML que vous (dé)sérialisez manuellement. De cette façon, vous pouvez conserver toutes vos commandes dans une seule table sans sacrifier les performances. Là encore, cette approche présente un inconvénient : vous ne pouvez pas filtrer à l'aide des paramètres de commande.

Chaque approche a ses avantages et ses inconvénients. Vous pouvez choisir la stratégie qui correspond à vos besoins.

5voto

SWeko Points 17524

Ce problème est assez courant, et je n'ai pas vu de solution qui soit sans inconvénient. La seule option pour stocker exactement une hiérarchie d'objets dans une base de données est d'utiliser une base de données NoSQL.

Toutefois, si une base de données relationnelle est imposée, j'opte généralement pour cette approche :

  • une table pour la classe/interface de base, qui stocke les données communes à tous les types.
  • une table par classe descendante, qui utilise exactement la même clé primaire que la table de base (c'est un cas d'utilisation intéressant pour les séquences SQL Server 2011, entre autres)
  • une table qui contient les types qui seront stockés
  • une vue qui réunit toutes ces tables, afin de faciliter le chargement et l'interrogation des objets.

Dans votre cas, je créerais :

  • Table CommandBase
    • ID (int ou guid) - l'ID de la commande
    • TypeID (int) - ID du type de commande
  • Table CommandA
    • ID (int ou guid) - le même ID que celui de la table CommandBase.
    • X (int)
    • Y (int)
  • Table CommandB
    • ID (int ou guid) - le même ID que celui de la table CommandBase.
    • Nom (nvarchar)
  • Tableau CommandTypes
    • ID (int) - ID du type de commande
    • Name (nvarchar) - Nom du type de commande ("CommandA", "CommandB",...)
    • TableName (nvarchar) - Nom de la table qui stocke le type - utile si un sql dynamique est nécessaire - sinon optionnel.
  • Commandes d'affichage, quelque chose du genre :

    select cb.ID, a.X, a.Y, b.Name 
     from CommandBase cb
       left outer join CommandA a on a.ID = cb.ID
       left outer join CommandB b on b.ID = cb.ID

L'avantage de cette approche est qu'elle reflète la structure de votre classe. Elle est facile à comprendre et à utiliser.

L'inconvénient, c'est que cela devient de plus en plus lourd au fur et à mesure que l'on ajoute de nouvelles classes, qu'il est difficile de modéliser plus d'un niveau de hiérarchie et que la vue peut devenir très longue s'il y a beaucoup de descendants.

Personnellement, j'utiliserais cette approche si je sais que le nombre de sous-classes est relativement faible et relativement fixe, car elle nécessite de créer (et de maintenir) une nouvelle table pour chaque nouveau type. Cependant, le modèle est assez simple, donc il est possible de créer un outil/script qui pourrait faire la création et la maintenance pour vous.

1voto

Scott Johnson Points 1

Nous utilisons de plus en plus le XML en SQL pour nos données informatiques. L'interrogation peut être légèrement pénible (en utilisant XPath), mais elle nous permet de stocker des métadonnées par ligne, validées par rapport à un schéma, etc.

Le XML peut alors être analysé par le code et les paramètres peuvent être mis en correspondance par réflexion avec les paramètres de l'objet dé-sérialisé.

En fait, cela reproduit la fonctionnalité ORM, mais avec une structure de base de données plate, ce qui simplifie les requêtes, et les paramètres peuvent même être interrogés par XPath.

Et rappelez-vous les enfants, les opinions sont mauvaises.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X