Search code examples
c#nhibernatefluent-nhibernatefluent-nhibernate-mapping

Mapping a custom type property in Fluent NHibernate


I have a database that saves an Id of an item that is stored in another system and deserialized to an object in code. I am trying to use Fluent NHibernate to build a Domain Model entity that is composed of data from the database and the external service. An example will explain this better. In the database I have a table looking like this:

CREATE TABLE entities
(
  id integer NOT NULL,
  custom_thing text NOT NULL,
  CONSTRAINT entities_id_pk PRIMARY KEY (id)
);

It is PostgreSQL but the problem is not database specific. Now in the code I have these classes:

class Entity
{
    public virtual int Id { get; set; }

    public virtual CustomThing CustomThing { get; set; }
}

class CustomThing
{
    public string Id { get; set; }
    public string Name { get; set; }
}

I'm trying to use a CustomMap to define the mapping:

class EntityMap : ClassMap<Entity>
{
    public EntityMap()
    {
        Table("entities");
        Id(e => e.Id, "id").GeneratedBy.Assigned();
        // Map(e => e.CustomThing, "custom_thing");
    }
}

The question is: how can I map the CustomThing? Here's a program trying to map the Entity class (need FluentNHibernate package and NpgSQL if run against PostgreSQL database). For simplicity I just create instances of CustomThing in code:

class Program
{
    // How to use this in mapping?
    static CustomThing[] _customThings =
    {
        new CustomThing {Id = "abc", Name = "ABC"},
        new CustomThing {Id = "def", Name = "DEF"}
    };

    static void Main()
    {
        using (ISessionFactory sessionFactory = Fluently.Configure()
            .Database(PostgreSQLConfiguration.Standard
                .ConnectionString(
                    @"Server=localhost; Database=test_nhibernate; User Id=postgres; Password=little_secret;"))
            .Mappings(m => m.FluentMappings.AddFromAssemblyOf<Entity>())
            .BuildSessionFactory())
        {
            using (ISession session = sessionFactory.OpenSession())
            {
                var entity = session.Get<Entity>(1);

                Console.WriteLine($"{entity?.Id} {entity?.CustomThing?.Name}");
            }
        }
        Console.ReadLine();
    }
}

The output is of course only the Id property value because mapping for CustomThing is not defined. Is it possible to configure the mapping so that I can pass parameters to it and map custom_thing column values to objects in _customThings by Id property somehow?


Solution

  • Yes, you can do it either by save the Id, and get rest of data in your code, or put every thing in your mapper to return an object to you, if you select the second option you can follow this code:

    [Serializable]
    public class CustomThingUserType : IUserType {
        public new bool Equals(object x, object y) {
            if (object.ReferenceEquals(x, y))
                return true;
    
            if (x == null || y == null)
                return false;
    
            return x.Equals(y);
        }
    
        public int GetHashCode(object x) {
            if (x == null)
                return 0;
    
            return x.GetHashCode();
        }
    
        public object NullSafeGet(IDataReader rs, string[] names, object owner) {
            if (names.Length == 0)
                throw new ArgumentException("Expecting at least one column");
    
            int id = (int)NHibernateUtil.Int32.NullSafeGet(rs, names[0]);
    
            var obj = new CustomThing { Id = id };
    
            // here you can grab your data from external service
    
            return obj;
        }
    
        public void NullSafeSet(IDbCommand cmd, object value, int index) {
            var parameter = (DbParameter)cmd.Parameters[index];
    
            if (value == null) {
                parameter.Value = 0;
            }
            else {
                parameter.Value = ((CustomThing)value).Id;
            }
        }
    
        public object DeepCopy(object value) {
            return value;
        }
    
        public object Replace(object original, object target, object owner) {
            return original;
        }
    
        public object Assemble(object cached, object owner) {
            return cached;
        }
    
        public object Disassemble(object value) {
            return value;
        }
    
        public SqlType[] SqlTypes {
            get {
                return new SqlType[] { new SqlType(DbType.Int32) };
            }
        }
    
        public Type ReturnedType {
            get { return typeof(CustomThing); }
        }
    
        public bool IsMutable {
            get { return false; }
        }
    }