NHibernate Error (NHibernate v Generic.List)

Topics: Troubleshooting
Dec 6, 2012 at 4:49 PM
Edited Dec 6, 2012 at 4:52 PM

I am getting one of several errors trying to work through this problem.

I am using `enum` and a custom `Selector` to help choose between radiobuttons, dropdowns, checkboxes, etc. Getting this error when trying to use a checkbox list (NOTE: my `enum` and Part/PartRecord code works fine for dropdown lists and radiobutton lists):

 System.InvalidCastException: Unable to cast object of type 'NHibernate.Collection.Generic.PersistentGenericBag'1[System.String]' to type 'System.Collections.Generic.List'1[System.String]

This error comes on using `IList<string>` (using `List<string>` throws a similar error) on the code I show below. I have tried

IList<string>

I have tried (just to try)

 IEnumerable<string>

and have even tried variations of each of those with the name of the `enum` in the `<>` to no avail.  I understand the error, I just don't know how to code around it.

Here are parts of my model (each in separate files):

public enum ContactClientDay
     {
        [Display(Name = "Monday")]
        Monday,
        .... 
        [Display(Name = "Saturday")]
        Saturday
     }
...
public class ContactClientDaySelectorAttribute : SelectorAttribute 
{
    public override IEnumerable<SelectListItem> GetItems()
    {
        return Selector.GetItemsFromEnum<ContactClientDay>();
    }
}
...
[Display(Name = "What are the best days to contact you (select one or more days)?")]
[ContactClientDaySelector(BulkSelectionThreshold = 10)]
public virtual IList<string> ContactClientDayCheckBox { get; set; }
...
public IList<string> ContactClientDayCheckBox
{
    get { return Record.ContactClientDayCheckBox; }
    set { Record.ContactClientDayCheckBox = value; }
}

Using

List<string>

doesn't work either.

If i take the `enum` and change `<string>` so that it's `<ContactClientDay>` instead, i get the following error:

 

 DTC transaction prepre phase failed NHibernate.PropertyAccessException: Invalid Cast (check your mapping for property type mismatches); setter of MyNameSpace.Models.QuotePartRecord ---> System.InvalidCastException: Unable to cast object of type 'NHibernate.Collection.Generic.PersistentGenericBag`1[System.String]' to type 'System.Collections.Generic.List`1[System.String]'. at (Object , Object[] 

 

As I mentioned, this error only comes with checkboxes since they use `IList<string>`. Dropdowns, for example, work fine with no errors. Here is a sample dropdown model (can reuse `enum` above) that works and will allow mapping to the DB through NHibernate:

 

    [Required(ErrorMessage = "Please select a day")]
    [Display(Name = "What are the best days to contact you (select one day)?")]
    [ContactClientDaySelector(BulkSelectionThreshold = 0)] //0 selects dropdown
    public virtual ContactClientDay? ContactClientDayDropDown { get; set; }

    public ContactClientDay? ContactClientDayDropDown
        {
            get { return Record.ContactClientDayDropDown; }
            set { Record.ContactClientDayDropDown = value; }
        }

 

In both cases (whether for checkbox or radiobutton/dropdown) my migration is set up thusly:

 

.Column("ContactClientDayCheckBox", DbType.String)
//or, as an example only (other dropdowns are done this way)
.Column("ContactClientDayDropDown", DbType.String)

 

Any thoughts?

Developer
Dec 7, 2012 at 10:07 AM

If I understand correctly, you want to use an IList<string> as a property on your entity/record class. I have never tried that before and don't know if NHibernate supports something like that (and if it does, if Orchard is setup to generate a mapping for that).

Looking at the exception, NHibernate tries to perform a cast from a 'PersistentGenericBag`1[System.String] to List`1[System.String]. Unless the former type inherits from List, this obviously won't work. Still it's interesting to see that there seems to be a mapping generated for the property, so what you could try is to change the type of your property from List<string> to PersistentGenericBag<string>. I'm not familiar with the type so I don't know if it's IENumerable.

If NHibernate does support mapping IList<string> properties to a DbType.String column, then I suppose Orchard should be able to handle it as well. In that case I would file a bug in the form of a feature request. When you do, please include a link to NHibernate instructions showing how to setup such a mapping.

I did find this on SO: http://stackoverflow.com/questions/606607/mapping-collection-of-strings-with-nhibernate but it looks like that mapping a collection of strings is usually done to another table.

Unless somebody knows the answer to this, what I would do for now is change the type from IList<string> to string, and store the data as a comma separated list of values. If your entity class is a content part record, then you could go as far as implementing a LazyField<T> on your content part, that would lazily parse the comma separated string and return an IList<string> of values.

Dec 7, 2012 at 9:24 PM

@sfmskywalker, thanks very much for the detailed response. Unfortunately, I couldn't figure out how to code with teh LazyField<> because it appears to be expecting an ID (going off your blog example and the one found on the Orchard Docs regarding 1>1>n).

What I am gleaning from this is the use of IList<> seems to be expecting another PartRecord in terms of the 1>1>n concept, which is not what I'm after. Not sure if its "buggy" in this sense because my code is what appears to be causing the problem.

Anyway, I had to hack/slash/hack my way to a potential solution (which I am sure will <creep> some later time). Essentially, I had to change my SelectorAttribute from this:

 

public class ContactClientDaySelectorAttribute : SelectorAttribute 
{
    public override IEnumerable<SelectListItem> GetItems()
    {
        return Selector.GetItemsFromEnum<ContactClientDay>();
    }
}

 

to this (and elminated the corresponding `enum`):

 

public class ContactClientDaySelectorAttribute : SelectorAttribute
    {
        public override IEnumerable<SelectListItem> GetItems()
        {
            var contactClientDay = new List<ContactClientDay> 
            { 
                new ContactClientDay {Id = 1, Name = "Monday"},
                new ContactClientDay {Id = 2, Name = "Tuesday"},
                new ContactClientDay {Id = 3, Name = "Wednesday"},
                new ContactClientDay {Id = 4, Name = "Thursday"},
                new ContactClientDay {Id = 5, Name = "Friday"},
                new ContactClientDay {Id = 6, Name = "Saturday"},

            };
            return contactClientDay.ToSelectList(m => m.Id, m => m.Name);
        }
    }

 

I then had to change my Part / PartRecord from this:

 

public IList<string> ContactClientDayCheckBox

 

to this:

 

public virtual int? ContactClientDayCheckBox { get; set; }

 

and finally update my Migrations thusly:

SchemaBuilder.AlterTable("MyPartRecord",
                table => table
                .AlterColumn("ContactClientDayCheckBox",
                    column => column.WithType(DbType.Int16)));
Yuck!