Mapping collection of strings with NHibernate
Asked Answered
C

3

14

I have a domain class with a property IList<string> that I want to map to a table with a single data value (i.e. it has an ID, a foreign key ID to the domain entity table, and a varchar data column).

I keep getting the error:

Association references unmapped class: System.String

How can I map a table to a collection of strings?

Confirmed answered 3/3, 2009 at 14:35 Comment(1)
There is nothing wrong with this. You just mapped it with one-to-many, which only works for lists of entities. Take a look at derek's and Frederiks anserwers.Biserrate
P
22

I just ran into a similar situation; and I found that it is indeed possible to map a collection of strings. Note that you'll have to map those strings as value objects.

This is what I have:

public class Chapter
{
    private ISet<string> _synonyms = new HashedSet<string>();

    public ReadOnlyCollection<string> Synonyms
    {
       get { return new List<string>(_synonyms).AsReadOnly(); }
    }
}

Mapping:

<class name="Chapter" table="Chapter">
   <set name="Synonyms" table="ChapterSynonyms">
       <key column="ChapterId" />
       <element column="ChapterCode" type="string" />
   </set>
</class>
Pindling answered 4/3, 2009 at 14:18 Comment(4)
Came across this again recently, here is the FluentNHibernate mapping I used based on your XML mapping: mapping.HasMany(x => x.Synonyms).AsBag().Element("ChapterCode", m => m.Type<string>());Confirmed
Shouldn't HashedSet be HashShet?Rhyne
@Confirmed you are using a Bag, where it should be a Set. The correct code should be: HasMany(x => x.Synonyms).Table("Synonyms").AsSet().Element("ChapterCode", m => m.Type<string>().Not.Nullable());Rhyne
No HashedSet should not be HashSet. HashedSet is an Iesi.Collections type, and this was used when using NHibernate before MS created the HashSet type. Although the sample code will work with an MS HashSet<T> as well, using HashedSet is perfectly valid.Pindling
L
8

Unless I am mistaken you can do this:

<bag name="Identities" access="property">
  <key column="accountId"/>
  <element column="identity" type="string"/>
</bag>

Identities being an IList<string>

Loki answered 11/5, 2009 at 15:20 Comment(0)
P
1

You can do this with IUserType like so:

public class DelimitedList : IUserType
{
    private const string delimiter = "|";

    public new bool Equals(object x, object y)
    {
        return object.Equals(x, y);
    }

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

    public object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        var r = rs[names[0]];
        return r == DBNull.Value 
            ? new List<string>()
            : ((string)r).SplitAndTrim(new [] { delimiter });
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        object paramVal = DBNull.Value;
        if (value != null)
        {
            paramVal = ((IEnumerable<string>)value).Join(delimiter);
        }
        var parameter = (IDataParameter)cmd.Parameters[index];
        parameter.Value = paramVal;
    }

    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 StringSqlType() }; }
    }

    public Type ReturnedType
    {
        get { return typeof(IList<string>); }
    }

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

Then define the IList<string> property as type="MyApp.DelimitedList, MyApp".

NOTE: SplitAndTrim is a string extension with various overrides that I created. Here is the core method:

public static IList<string> SplitAndTrim(this string s, StringSplitOptions options, params string[] delimiters)
    {
        if (s == null)
        {
            return null;
        }
        var query = s.Split(delimiters, StringSplitOptions.None).Select(x => x.Trim());
        if (options == StringSplitOptions.RemoveEmptyEntries)
        {
            query = query.Where(x => x.Trim() != string.Empty);
        }
        return query.ToList();
    }
Peterec answered 3/3, 2009 at 19:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.