2010-11-15 10 views
2

j'ai le scénario suivant:Interrogation années usertype dans NHibernate

Disons que ma table « produit » dans cette base de données existante a une colonne « Catégories » de type chaîne. Cette colonne stocke les ID de catégorie séparés par une sorte de caractère ASCII. Par exemple: "| 1 |" (pour la catégorie 1), "| 1 | 2 | 3 |" Pour les catégories 1, 2 et 3, etc. .

Je crée un type de SelectedCatories qui est tout simplement un IEnumerable, et ma classe de produit ressemble à ceci:

public class Product 
{ 
    public virtual Guid Id { get; set; } 
    public virtual string Name { get; set; } 
    public virtual bool Discontinued { get; set; } 
    public virtual SelectedCategories Categories { get; set; } 
} 

J'ai ensuite créé une classe SelectedCategoriesUserType comme ceci:

public class SeletedCategoriesUserType : IUserType 
{ 
    static readonly SqlType[] _sqlTypes = {NHibernateUtil.String.SqlType}; 

    public bool Equals(object x, object y) 
    { 
     // Fix this to check for Categories... 
     if (ReferenceEquals(x, y)) return true; 
     if (x == null || y == null) return false; 
     return x.Equals(y); 
    } 

    public int GetHashCode(object x) 
    { 
     return x.GetHashCode(); 
    } 

    public object NullSafeGet(IDataReader rs, string[] names, object owner) 
    { 
     object obj = NHibernateUtil.String.NullSafeGet(rs, names[0]); 
     if (obj == null) return null; 

     string[] stringCategories = obj.ToString().Split(new[] {'|'}, StringSplitOptions.RemoveEmptyEntries); 

     var categories = new Categories(); 

     return 
      new SelectedCategories(
       stringCategories.Select(
        stringCategory => categories.Single(cat => cat.Id == int.Parse(stringCategory))) 
        .ToList()); 
    } 

    public void NullSafeSet(IDbCommand cmd, object value, int index) 
    { 
     if (value == null) 
     { 
      ((IDataParameter) cmd.Parameters[index]).Value = DBNull.Value; 
     } 
     else 
     { 
      var theCategories = (SelectedCategories) value; 

      var builder = new StringBuilder(); 
      builder.Append("|"); 
      theCategories.ForEach(i => builder.AppendFormat("{0}|", i.Id.ToString())); 

      ((IDataParameter) cmd.Parameters[index]).Value = builder.ToString(); 
     } 
    } 

    public object DeepCopy(object value) 
    { 
     return value; 
    } 

    public object Replace(object original, object target, object owner) 
    { 
     throw new NotImplementedException(); 
    } 

    public object Assemble(object cached, object owner) 
    { 
     throw new NotImplementedException(); 
    } 

    public object Disassemble(object value) 
    { 
     throw new NotImplementedException(); 
    } 

    public SqlType[] SqlTypes 
    { 
     get { return _sqlTypes; } 
    } 

    public Type ReturnedType 
    { 
     get { return typeof (SelectedCategories); } 
    } 

    public bool IsMutable 
    { 
     get { return false; } 
    } 
} 

Je veux alors pour créer une requête qui me renvoie tout produit appartenant à une catégorie spécifique (disons, catégorie 2), correspondant à la fois à "| 2 |" et "| 1 | 2 | 3 |".

En ce moment, ma mise en œuvre naïve qui fait à peine mon laissez-passer de test ressemble à ceci:

public IEnumerable<Product> GetByCategory(Category category) 
    { 
     using (ISession session = NHibernateHelper.OpenSession()) 
     { 
      return session 
       .CreateSQLQuery("select * from product where categories LIKE :category") 
       .AddEntity(typeof(Product)) 
       .SetString("category", string.Format("%|{0}|%", category.Id)) 
       .List() 
       .Cast<Product>(); 
     } 
    } 

Ma question est: quelle est la bonne façon de droite cette requête?

+0

juste pour clarifier: la requête ne fonctionne pas pour vous ou voulez-vous connaître un moyen "plus propre" d'écrire votre requête? Et est-il obligatoire que vos identifiants de catégorie soient séparés par un | ou pourraient-ils aussi être séparés par une virgule (,)? – Max

Répondre

0

Une autre façon de faire cette requête ICriteria serait ce ...

return Session 
    .CreateCriteria(typeof(Product), "product") 
    .Add(Expression.Sql(
     "{alias}.categories LIKE ?", 
     string.Format("%|{0}|%", category.Id), 
     NHibernateUtil.String)) 
    .List<Product>(); 

Cependant, vous voudrez peut-être penser à mettre en place un grand nombre à plusieurs produits et table entre catégorie et la mise en place d'un collection de catégories dans la classe Product. Vous pouvez toujours conserver votre champ d'identifiants de catégorie concaténés (je suppose que c'est nécessaire pour les besoins hérités), mais reliez-le à la collection avec quelque chose comme ça.

public virtual ISet<Category> Categories { get; private set; } 

public virtual string CategoriesString 
{ 
    get { return string.Join("|", Categories.Select(c => c.Id.ToString()).ToArray()); } 
} 

Faire quelque chose comme cela vous permettra de définir les clés étrangères sur vos tables, et de faire les requêtes un peu plus facile à construire.

return Session 
    .CreateCriteria(typeof(Product), "product") 
    .CreateCriteria("product.Categories", "category") 
    .Add(Restrictions.Eq("category.Id", category.Id)) 
    .List<Product>(); 
+0

Merci, Adam. Cela fonctionne pour ce dont j'ai besoin maintenant. Je suis d'accord qu'il serait préférable d'avoir une table "Catégories", mais dans l'application réelle où j'ai besoin de mettre en œuvre ce type d'utilisateur, les choses sont un peu plus compliquées que le scénario que je décris ici (il y a beaucoup de données et des choses que nous ne pouvons pas changer si facilement ...). Mais je garde votre conseil, car nous pouvons être en mesure de faire ces changements en fonction de la façon dont les choses se passent. –