You could decorate your enum MyEnum
with [Flags]
attribute and set the values of it's items to be a distinct powers of 2. For example, your enum might look like this:
[Flags]
public enum MyEnum
{
Enum1 = 1,
Enum2 = 2,
Enum3 = 4,
Enum4 = 8,
Enum5 = 16
}
Now, your MyEnumCheckBox
property of the PartRecord
class would be of type int
:
public virtual int MyEnumCheckBox { get; set; }
You could create a proxy property inside of the Part
class. For example:
private IList<MyEnum> _myCheckBox;
[MyEnumSelector]
public IList<MyEnum> MyCheckBox
{
get
{
if (_myCheckBox == null)
{
_myCheckBox = new List<MyEnum>();
foreach (MyEnum item in Enum.GetValues(typeof(MyEnum)))
{
if (((MyEnum)Record.MyEnumCheckBox & item) == item)
_myCheckBox.Add(item);
}
}
return _myCheckBox;
}
set
{
_myCheckBox = value;
Record.MyEnumCheckBox = 0;
foreach (var item in value)
{
Record.MyEnumCheckBox |= (int)item;
}
}
}
You can find some more info on Flags attribute here. It basically serves to enable you to use multiple enum values for a single enum field what is exactly what you're looking for.
EDIT:
I've taken my time and built a custom module to demonstrate this technique. I've tested it and it works the way it should. So here's the source:
Migrations.cs
public int Create()
{
SchemaBuilder.CreateTable("MultipleEnumPickerRecord", table => table
.ContentPartRecord()
.Column<int>("SelectedItems"));
ContentDefinitionManager.AlterPartDefinition("MultipleEnumPickerPart", p => p.Attachable());
return 1;
}
Models:
[Flags]
public enum MyEnum
{
Enum1 = 1, // bit-wise 00001 or 2^0
Enum2 = 2, // bit-wise 00010 or 2^1
Enum3 = 4, // bit-wise 00100 or 2^2
Enum4 = 8, // bit-wise 01000 or 2^3
Enum5 = 16 // bit-wise 10000 or 2^4
}
public class MultipleEnumPickerRecord : ContentPartRecord
{
public virtual int SelectedItems { get; set; }
}
public class MultipleEnumPickerPart : ContentPart<MultipleEnumPickerRecord>
{
private IList<MyEnum> _selectedItems;
public IList<MyEnum> SelectedItems
{
get
{
if (_selectedItems == null)
{
_selectedItems = new List<MyEnum>();
foreach (MyEnum item in Enum.GetValues(typeof(MyEnum)))
{
if (((MyEnum)Record.SelectedItems & item) == item)
_selectedItems.Add(item);
}
}
return _selectedItems;
}
set
{
_selectedItems = value;
Record.SelectedItems = 0;
foreach (var item in value)
{
Record.SelectedItems |= (int)item;
}
}
}
}
Handler:
public class MultipleEnumPickerHandler : ContentHandler
{
public MultipleEnumPickerHandler(IRepository<MultipleEnumPickerRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
}
Driver:
public class MultipleEnumPickerDriver : ContentPartDriver<MultipleEnumPickerPart>
{
protected override string Prefix { get { return "MultipleEnumPicker"; } }
protected override DriverResult Editor(MultipleEnumPickerPart part, dynamic shapeHelper)
{
return ContentShape("Parts_MultipleEnumPicker_Edit", () => shapeHelper.EditorTemplate(
TemplateName: "Parts/MultipleEnumPicker", Model: part, Prefix: Prefix));
}
protected override DriverResult Editor(MultipleEnumPickerPart part, IUpdateModel updater,
dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
Placement:
<Placement>
<Place Parts_MultipleEnumPicker_Edit="Content:5"/>
</Placement>
And finally, the view:
@using ModuleNamespace.Models
@model MultipleEnumPickerPart
<fieldset>
<div class="editor-label">@Html.LabelFor(x => x.SelectedItems)</div>
<div class="editor-field">
<select multiple="multiple" id="@Html.FieldIdFor(x => x.SelectedItems)" name="@Html.FieldNameFor(x => x.SelectedItems)">
@foreach (MyEnum item in Enum.GetValues(typeof(MyEnum))) {
var selected = Model.SelectedItems.Contains(item);
<option value="@((int)item)" @if(selected) {<text>selected="selected"</text>}>
@T(item.ToString())
</option>
}
</select>
</div>
</fieldset>
Hovewer, there are 2 things you have to keep in mind when you're implementing this techinque:
- Enums are treated internally as integers which means their values take up 32 bits. This in turn means that
MyEnum
can't have more than 32 enumeration items defined for this to work
- As Bertrand pointed out, database search for items will be harder (although, not impossible since major databases allow you to use bit-wise operators)
These both constraint could be bypassed by using different mapping function between your database and the model.
What does that mean?
In the example I've shown you, the database value (and the MultipleEnumPickerRecord
value) is of type int
while in the MultipleEnumPickerPart
I've "mapped" that integer to a List<MyEnum>
. This uses less space in the database and is faster than using some other mapping functions.
For example, you might use the string
type for your database and MultipleEnumPickerRecord
and then make some kind of a mapping to List<MyEnum>
inside of MultipleEnumPickerPart
. Most popular string mapping functions are
- comma-delimited mapping - for example, if someone selected
Enum1
and Enum4
, you could map it to a string "Enum1,Enum4"
- semicolon-delimited mapping - you'd map previous example to
"Enum1;Enum4"
The type of delimiter to choose should be based on the character you know your string won't use. To deconstruct the list from a string in the database, you could use a simple value.Split(',').ToList();
(if you're using ',' as a delimiter).
This way, you're not bounded by only 32 enumeration items, and, since the value is saved as string, it's pretty straightforward to search in the database for some value. The downsides are that string will take a lot more space in the database (int will take space of one character from a string), and the string manipulation functions are somewhat slower than bit-wise function demonstrated in the sample above.