I am using enum‘s and a custom Selector class to help choose between radiobuttons, dropdowns, checkboxes, etc. I am using NHibernate. With a single selection (radiobuttons, dropdowns), the value from attribute [Display(Name = "[Some Text]")] will be populated in the database table (NOTE: I am using an extension to use Display(Name)). However, with multiple selections (checkboxes, multilist), I cannot figure out how to get the values of the enum selections into the database.
Here are parts of my model (each in separate files) (EDIT: I gave them generic names so as not to further confuse the issue):
public enum MyEnum
{
[Display(Name = "Text for enum1")]
enum1,
//Left out 2 - 10 for brevity
[Display(Name = "Text for enum10")]
enum10
}
...
public class MyEnumSelectorAttribute : SelectorAttribute
{
public override IEnumerable<SelectListItem> GetItems()
{
return Selector.GetItemsFromEnum<MyEnum>();
}
}
...
[Display(Name = "This is a checkboxlist (select one or more check boxes)?")]
[MyEnumSelector(BulkSelectionThreshold = 10)]
public virtual List<string> MyEnumCheckBox { get; set; }
...
public List<string> MyEnumCheckBox
{
get { return Record.MyEnumCheckBox; }
set { Record.MyEnumCheckBox = value; }
}
And here is the Selector.cs class (in case it’s relevant to the problem) that help’s to choose radiobuttons, checkboxes, dropdowns, etc.:
public class Selector
{
public IEnumerable<SelectListItem> Items { get; set; }
public string OptionLabel { get; set; }
public bool AllowMultipleSelection { get; set; }
public int BulkSelectionThreshold { get; set; }
public static string GetEnumDescription(string value, Type enumType)
{
var fi = enumType.GetField(value.ToString());
var display = fi
.GetCustomAttributes(typeof(DisplayAttribute), false)
.OfType<DisplayAttribute>()
.FirstOrDefault();
if (display != null)
{
return display.Name;
}
return value;
}
public static IEnumerable<SelectListItem> GetItemsFromEnum<T>
(T selectedValue = default(T)) where T : struct
{
return from name in Enum.GetNames(typeof(T))
let enumValue = Convert.ToString((T)Enum.Parse
(typeof(T), name, true))
select new SelectListItem
{
Text = GetEnumDescription(name, typeof(T)),
Value = enumValue,
Selected = enumValue.Equals(selectedValue)
};
}
}
public static class SelectorHelper
{
public static IEnumerable<SelectListItem> ToSelectList
(this IEnumerable data)
{
return new SelectList(data);
}
public static IEnumerable<SelectListItem> ToSelectList
(this IEnumerable data, string dataValueField,
string dataTextField)
{
return new SelectList(data, dataValueField, dataTextField);
}
public static IEnumerable<SelectListItem> ToSelectList<T>
(this IEnumerable<T> data, Expression<Func<T, object>>
dataValueFieldSelector, Expression<Func<T, string>>
dataTextFieldSelector)
{
var dataValueField = dataValueFieldSelector.ToPropertyInfo().Name;
var dataTextField = dataTextFieldSelector.ToPropertyInfo().Name;
return ToSelectList(data, dataValueField, dataTextField);
}
}
The Selector class is paired with a template Selector.cshtml that has some logic to figure out which to pick (radiobutton, checkboxes, etc.).
I am getting various errors trying either List<string>, List<MyEnum>, IList<string>, IList<MyEnum>, IEnumerable<MyEnum> and IEnumerable<MyEnum>. This error only comes with checkboxes or multilists since they use List<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 one option")]
[Display(Name = "This is a dropdown list (select one option)?")]
[MyEnumSelector(BulkSelectionThreshold = 0)] //0 selects dropdown
public virtual MyEnum? MyEnumDropDown { get; set; }
public MyEnum? MyEnumDropDown
{
get { return Record.MyEnumDropDown; }
set { Record.MyEnumDropDown = value; }
}
Here are some of the errors I am getting based on what I’ve tried:
List<string> error:
NHibernate.Transaction.ITransactionFactory – DTC transaction prepre phase failed
NHibernate.PropertyAccessException: Invalid Cast (check your mapping for property type mismatches); setter of MyNameSpace.Models.MyRecord —> System.InvalidCastException: Unable to cast object of type ‘NHibernate.Collection.Generic.PersistentGenericBag1[System.String]' to type 'System.Collections.Generic.List1[System.String]’.
List<MyEnum> error:
NHibernate.Transaction.ITransactionFactory – DTC transaction prepre phase failed
System.InvalidCastException: Unable to cast object of type ‘System.Collections.Generic.List1[MyNameSpace.Models.MyEnum]' to type 'System.Collections.Generic.ICollection1[System.String]’.
IList<string> error:
NHibernate.AdoNet.AbstractBatcher – Could not execute command: INSERT INTO MyEnumCheckBox (MyRecord_id, Value) VALUES (@p0, @p1)
System.Data.SqlServerCe.SqlCeException (0x80004005): The specified table does not exist. [ MyEnumCheckBox ]
The other variations I tried were similar errors, except that if I used <MyEnum> it would show an error like this:
System.Collections.Generic.List
1[MyNameSpace.Models.MyEnum]' to type 'System.Collections.Generic.ICollection1[System.String]’.
Any thoughts on how to use enum‘s in this scenario when trying to insert multiple selected enums using NHibernate?
Since using lists is used for separate tables by default in Nhibernate, you’ll have to use a mapping functions to map a single value from the database to a list of values in your model.
I’ve demonstrated how to do it here with a working sample, but I’ll sum it up once more on your question.
There are 2 major mapping function types based on your problem. They both include that you:
MyEnumSelectorattribute from theRecordclass to your model class,Record‘sMyEnumCheckBoxproperty andMyEnumCheckBoxproperty so that it maps to and from yourRecord‘sMyEnumCheckBoxpropertySo those mapping functions are:
inttoList<MyEnumSelector>– this is the same technique I’ve shown in the linking post. It includes marking yourMyEnumSelectorenum with the[Flags]attribute and setting it’s items to be of values of subsequent powers of 2. YourRecord‘sMyEnumCheckBoxproperty should be of typeint. The mapping part then involves mapping the bits of theRecord‘sMyEnumCheckBoxproperty to and from the list of correspondingMyEnumSelectorvaluesstringtoList<MyEnumSelector>– this involves setting yourRecord‘sMyEnumCheckBoxproperty to be of typestring. The mapping part then involves mapping the delimited string of theRecord‘sMyEnumCheckBoxproperty to and from the list of correspondingMyEnumSelectorvalues. Delimiter could be comma or semicolon or some other character that you won’t use as a name of yourMyEnumSelectoritem values.Major differences of these 2 approaches are:
intas your database type your’re limited to 32 bits of data. This means that you can’t have more than 32 items in your enum.Strings don’t have this kind of limitationint, you’ll have to deal with bit-wise database operators which are quite messy and not that easy to work with, while you could use simpleLIKEoperator when usingstringmappingintuses always 4 bytes (32-bits) whilestringmapping uses that amount (32-bits) for only one character inside the string (if it’s of typechar,varcharortext, and double the size – 64-bits when usingnchar,nvarcharorntext) so it will use much more space for each row in the databaseintmapping is quite fast whilestringmapping uses string manipulation functions which are much slower. This shouldn’t be the problem though if you’re dealing with a small amount of data. But if you’re going to deal with a massive amount of data this could be a huge issue.