Mapping an immutable structure as a component in NHibernate
Asked Answered
A

3

9

I'm testing out NHibernate to be the solution to my company's ORM needs. To do this, I've produced a small test model based on a school, providing some useful edge-cases for NHibernate to handle.

I'm having problems finding out how to map a custom structure as a component of an entity without using theIUserType interface. I should stress that it is an important requirement that the domain classes are in a separate assembly from our NHibernate code, and that the domain assembly does not have a reference to NHibernate.

The custom structure is Time, used to represent a time of day in hours and minutes. It's a very simple immutable structure and only provided to illustrate the problem of a custom structure. The constructor takes a single argument which is hours and minutes, as an integer in the form hhmm.

public struct Time
{
    public Time(int hoursAndMinutes)
    {
        // Initialize Structure //
    }

    public int Hours { get; private set; }

    public int Minutes { get; private set; }

    public int HoursAndMinutes { get; private set; }
}

This structure is used as a component of the Lesson class to store the time of day the lesson starts:

public class Lesson
{
    public int ID { get; private set; }
    public Teacher Teacher { get; internal set; }
    public DayOfWeek Day { get; set; }
    public Time StartTime { get; set; } // <-- Custom Type
    public int Periods { get; set; }
}

This class maps directly to this table:

CREATE TABLE Lessons
(
    ID INT,
    Subject NVARCHAR(128)
    TeacherID INT,
    Day VARCHAR(9),
    StartTime INT, // <-- Maps to custom type.
    Periods INT
)

I am looking for a way to map this structure as a component of the Lesson class, so that NHibernate will read a property value on the structure (like any other component) to get a value for the column, but will initialize a new instance of the structure by passing the column value to the constructor when reading the value from the column into the entity.

If you have any suggestions, that'd be super. If you want to tell me this can't be accomplished without using IUserType, that's a fine answer too.

Azeria answered 31/12, 2009 at 11:13 Comment(0)
W
16

To my knowledge, there are three plans of attack.

  • You can map the component directly into properties of the custom type. In this example, have NHibernate set the HoursAndMinutes property, change the code in that property's setter to update the Hours and Minutes properties appropriately, and have the constructor just call this.HoursAndMinutes = hoursAndMinutes; so the same code to update the Hours and Minutes properties gets executed regardless of whether the constructor is used or the setter on the HoursAndMinutes property is used. Would you write it that way if you weren't using an ORM and knew it was going to diddle with that property? Probably not. But it's not the end of the world and a comment would explain everything.

  • You write an IUserType or ICompositeUserType implementation. Really, they exist precisely for this scenario and give you the flexibility to instantiate the structure however you please in the NullSafeGet() implementation and extract the data however you please in the NullSafeSet() implementation. Put it in another assembly, say MyModel.NHibernateCrap.dll, if you like. There is no need for your model/domain to be aware that the IUserType implementation or NHibernate exists--that is all for the mapping file to specify.

  • You use the code-based workaround described by Miki Watts in his answer. That is, your component in the NHibernate mapping maps to fields or private properties in your model type that do some magic hand-waving to convert them into public properties that the application uses, and vice versa. (It's similar to the first option I provide; the only difference is that in his scenario the field is a workaround that lets the legacy database leak into the class implementation, but not to other parts of the application or model. For small, isolated cases, or if the legacy database is just a Fact of Life, then I think this is perfectly reasonable.)

To directly and realistically answer your question, NHibernate is not going to call a constructor that has parameters, period--that is simply how it works, it news up an object and then starts setting and reflecting on it--unless you start doing strange things with proxies or tell it to use your IUserType implementation. There is no mechanism for saying something like <constructor><arg>HoursAndMinutes</arg></constructor> in a mapping file or anything like that. Stop worrying and love the bomb.

Since IUserType is the mechanism provided by NHibernate for doing such things, I don't really understand why one wouldn't want to use it.

Good luck!

Wigging answered 4/1, 2010 at 3:19 Comment(2)
Good information; +1 for you. My requirement to not use IUserType is simply to see what alternatives there are available, which you have graciously provided.Azeria
Nicholas - if we have structures which contain Entity types (as opposed to value types), can we map these with IUserType? If so, how; if not, what is the alternative?Gereron
D
2

I'm working with a legacy database that is based on Priority ERP. In the database, time is represented as an integer number of minutes, starting from an epoch. So for example, the number 0 represents 01/01/1988 00:00, and the number 1440 represents 02/01/1988 00:00 and so on.

The solution that I was found for this looks like this:

    [Field("CURDATE")]
    private int transactionDate = DateTimeHelper.ConvertToInternalValue(DateTime.Today);

    public DateTime TransactionDate
    {
        get { return DateTimeHelper.ConvertToDateValue(transactionDate); }
        set { transactionDate = DateTimeHelper.ConvertToInternalValue(value); }
    }

where the functions on DateTimeHelper do the actual converting from the integer number of minutes to an actual DateTime structure.

Diskin answered 3/1, 2010 at 16:14 Comment(1)
Thanks for the answer, but this is a purely theoretical piece of work to explore the limits of ORM tools. Time in a non-standard format was chosen to illustrate mapping immutable structures. Workarounds, though fun to read, aren't directly answering the question.Azeria
E
0

Doing protected sets and a protected default constructor on it seemed to work for me

like this:

public struct Time
{
    protected Time(){}
    public Time(int hoursAndMinutes)
    {
        // Initialize Structure //
    }

    public int Hours { get; protected set; }

    public int Minutes { get; protected set; }

    public int HoursAndMinutes { get; protected set; }
}
Eventual answered 31/7, 2012 at 21:16 Comment(1)
How does this work? When I create a empty constructor for a struct i get the compilation error "struct cannot contain a parameterless constructor".Cirilla

© 2022 - 2024 — McMap. All rights reserved.