开发者

Mapping a List<string> to a delimited string with Fluent NHibernate

开发者 https://www.devze.com 2023-01-25 20:10 出处:网络
My model looks something like this: p开发者_开发知识库ublic class Product { public string Name {get; set;}

My model looks something like this:

p开发者_开发知识库ublic class Product
{
    public string Name {get; set;}
    public string Description {get; set;}
    public double Price {get; set;}
    public List<string> Features {get; set;}
}

I want my database table to be flat - the List should be stored as a delimited string: Feature one|Feature two|Feature three for example.

When retrieved from the db, it should place each of those items back into a List

Is this possible?


I'm doing the very same in my current project, only I'm persisting a collection of enums as pipe-delimited numbers. It works the same way.

public class Product
{
    protected string _features; //this is where we'll store the pipe-delimited string
    public List<string> Features {
        get
        {
            if(string.IsNullOrEmpty(_features)
                return new List<String>();
            return _features.Split(new[]{"|"}, StringSplitOptions.None).ToList();
        }
        set
        {
            _features = string.Join("|",value);
        }
    }
}

public class ProductMapping : ClassMap<Product>
{
    protected ProductMapping()
    {
        Map(x => x.Features).CustomType(typeof(string)).Access.CamelCaseField(Prefix.Underscore);
    }
}


I implemented something similar for the MySql set data type, which is a comma separated list in the db but a list of strings in the entity model. It involved using a custom data type in NHibernate, based on the PrimitiveType class. You wire this in using the mappings and the .CustomType< CustomType >( ) method on a map.

If you want I can send you a code snipet for the custom class.


I also implemented something similar for a Point3D struct. As cdmdotnet said you basically want to implement and IUserType that will pack/unpack Features into a single string via the NullSafeSet/NullSafeGet methods.

You may also need to implement the Equals() method, which is a little subtle. The reason why is best illustrated by an example:

    Product p = session.Load(...);
p.Features.Add("extra feature");
session.Save(p);

The thing is, NHibernate upon hydration stores a reference to p.Features, and compares it to the value of p.Features upon a save request. For immutable property types this is fine, but in the above example, these references are identical, so the effective comparison is

var x = p.Features;
var changed = Equals(x, x);

Obviously a standard implementation of this will always return false.

How should one deal with this? I have no idea what the best practice is, but solutions are:

  • Make IUserType.Equals(object x, object y) always return false. This will force the packed string to be rebuilt and a database call to be made every single time the Product is saved, irregardless of whether Product has been semantically changed or not. Whether or not this is an issue depends on any number of factors (size/count of Feature objects, whether Product objects are saved when not changed, how many Product objects you have etc).

  • Make Features an IList and implement a ChangeAwareList<T> : IList<T> which is able to track changes (or keep a copy of its original) aware. Implement IUserType.Equals(object x, object y) to check if x/y are ChangeAwareList and implement the necessary logic to see if the list really has changed. This is the solution I went with in the end.

  • Maybe you could reuse code from the NHibernate GenericListType type. At the time I implemented the previous solution I didn't have enough experience to have a go at this.

If you have some prior experience with NHibernate I hope this should help get you started. If not let me know and I will try and put together a more verbose solution.

0

精彩评论

暂无评论...
验证码 换一张
取 消