0

I have a field in my database that stores an integer value and contains the sum of the bitwise values selected in a list. For instance:

VALUE DESCRIPTION
----- -----------
  1    Option 1
  2    Option 2
  4    Option 3
  8    Option 4

Let's say Options 2 & 4 are selected, so the value stored in the field would be 10.

I'm having a hard time figuring out (if it's even possible) how to represent this in the hbm.xml file.

Here is a generic example of what I'm trying to do:

Product Table Columns

Id, int
Name, varchar(25)
Services, int

Service Table Columns

Id, int
Name, varchar(25)

Product.cs

public class Product
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual IList<Service> Services { get; set; }
}

Service.cs

public class Service
{
    public virtual int Id { get; set; } // bit values: 1, 2, 4, 8, ...
    public virtual string Name { get; set; }
}

Product.hbm.xml

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="AppNS" namespace="AppNS">
    <class name="Product" table="Product">
        <id name="Id" column="Id" type="int">
            <generator class="native"/>
        </id>
        <property name="Name" column="Name" type="string"/>
        <????? name="Services" column="Services" type="AppNS.Service"/>
    </class>
</hibernate-mapping>

Service.hbm.xml

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="AppNS" namespace="AppNS">
    <class name="Service" table="Service">
        <id name="Id" column="Id" type="int">
            <generator class="native"/>
        </id>
        <property name="Name" column="Name" type="string"/>
    </class>
</hibernate-mapping>

I need help with the <????> part in the Product.hbm.xml file.

--EDIT--

Ultimately, I want to be able to call the Load() method to get my Product model back.

Configuration cfg = new Configuration();
...
ISessionFactory sf = cfg.BuildSessionFactory();
using (ISession s = sf.OpenSession())
{
    Product product = s.Load<Product>(100);
    foreach(Service service in product.Services)
    {
        Console.WriteLine(service.Name);
    }
}

Output would be:

Option 2
Option 4

2 Answers2

1

Such field should be mapped as a simple property. The entity should type this property as a flag enum. I do not think you can map that directly as a list.

You may instead compute your list from the mapped enum.

[Flags]
public enum EService
{
    Option1 = 1,
    Option2 = 2,
    Option3 = 4,
    Option4 = 8
}

public class Product
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual EService Services { get; set; }

    // Coded here for simplicity, though you'd probably do not want such
    // dependencies here.
    public virtual List<Service> GetServicesEntities(ISession session)
    {
        var services = new List<Service>();
        foreach (EService s in Enum.GetValues(typeof(EService)))
        {
            if ((Services & s) != 0 &&
                // In case you predefine combinations of options in the enum, you need 
                // this to avoid having them in the list too.
                IsPowerOfTwo((int)s))
            {
                services.Add(
                    // Not a n+1 perf trouble if you have lazy loading batching enabled.
                    session.Load<Service>(s));
            }
        }
        return services;
    }

    // Taken from http://stackoverflow.com/a/600306/1178314
    private bool IsPowerOfTwo(int x)
    {
        return (x != 0) && ((x & (x - 1)) == 0);
    }
}
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="AppNS" namespace="AppNS">
    <class name="Product" table="Product">
        <id name="Id" column="Id">
            <generator class="native"/>
        </id>
        <property name="Name" />
        <property name="Services" />
    </class>
</hibernate-mapping>

(Simplified the mapping by the way, NHibernate assumes columns names are same as property names if not specified, and infers property types from the entity.)

Frédéric
  • 9,364
  • 3
  • 62
  • 112
0

Try xml linq

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string header = 
                "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + 
                "<hibernate-mapping xmlns=\"urn:nhibernate-mapping-2.2\" assembly=\"AppNS\" namespace=\"AppNS\">" +
                "</hibernate-mapping>";

            Product.products = new List<Product>() {
                new Product() { Id = 100, Name = "Product", Services = new List<Service>() {
                    new Service() {Id = 1, Name = "Option 1"},
                    new Service() {Id = 2, Name = "Option 2"},
                    new Service() {Id = 4, Name = "Option 3"},
                    new Service() {Id = 8, Name = "Option 4"}
                }}
            };

            XDocument serviceXml = XDocument.Parse(header);

            XElement mapping = (XElement)serviceXml.FirstNode;
            foreach(Product product in Product.products)
            {
                XElement newProduct = new XElement("class");
                mapping.Add(newProduct);

                newProduct.Add(new object[] {
                    new XAttribute("name",product.Name),
                    new XAttribute("table","Product"),
                    new XElement("id", new object[] {
                        new XAttribute("name", product.Id),
                        new XAttribute("column", product.Id),
                        new XAttribute("type","int"),
                        new XElement("generator", new XAttribute("class","native"))
                    }),
                    new XElement("property", new object[] {
                        new XAttribute("name","Name"),
                        new XAttribute("column", "Name"),
                        new XAttribute("type","string")
                    })
                });
                XElement services = new XElement("class", new object[] {
                    new XAttribute("name", "Service"),
                    new XAttribute("table", "Service")
                });
                newProduct.Add(services);

                foreach (Service service in product.Services)
                {
                    services.Add(new XElement("id", new object[] {
                        new XAttribute("name", service.Name),
                        new XAttribute("column", service.Id),
                        new XAttribute("type", "int"),
                        new XElement("generator", new XAttribute("class","native"))
                    }));
                }
            }
        }
    }
    public class Product
    {
        public static List<Product> products = new List<Product>();
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        public virtual IList<Service> Services { get; set; }
    }
    public class Service
    {
        public virtual int Id { get; set; } // bit values: 1, 2, 4, 8, ...
        public virtual string Name { get; set; }
    }
}
jdweng
  • 33,250
  • 2
  • 15
  • 20
  • My goal is to be able to call the ISession.Load() method to return my Product object from the database. This Product would only have the Services in the list that were selected based on the integer value in the database field. I'm not seeing how your answer can get me there. – DanHarrigan Feb 09 '17 at 15:05