views:

1272

answers:

4

Hi,

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?

A: 

How does your mapping look like ? How do your classes look like ?

I think you should have a look at component-collection mappings.

<set name="MyCollectionOfStrings" table="..">
   <key column="..." />
   <composite-element class="..">
     <property ... />
   </composite-element>
</set>

But, I still think you should have a class that represents the records from that table ...

Frederik Gheysels
Re. your last comment, I was thinking the same thing but when all that class would contain is one string property, does it really make sense? I guess the question is how would you then map changes to that collection back to the table... think I just answered my own question :o)
roryf
composite-element is for complex types, not strings.
Stefan Steinegger
A: 

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();
    }
Tim Scott
+3  A: 

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>
Frederik Gheysels
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>());
roryf
+1  A: 

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>

Derek Ekins