尝试根据Orchard(NHibernate)中的枚举保存复选框选择

时间:2012-12-13 23:35:34

标签: asp.net-mvc-3 nhibernate orchardcms

我花了五(5)天的大部分时间试图弄清楚如何使这项工作:

我使用enum来存储用户输入的“选项”,通过单选按钮列表,下拉列表(这些是多个选项中的一(1)个选项),以及复选框列表和多选列表(这些是多个选项中的一个(1))。

现在,通过单选按钮列表和下拉列表,我可以在Orchard数据库中保存。用户选择一个选项,该选项将保存为选择到数据库中的特定enum

但是,对于复选框列表或多个选择列表,我无法让Orchard / NHibernate保存多个选定的enum

我尝试了一切我可以在SO或Google搜索中找到。对于这种情况来说,唯一“可行”的解决方案是创建一个新表(通过迁移)/部件/部件记录组合,以便在某些情况下存储7-8个选项。然后,当然,我可以做public virtual IList<NewContentPartRecord> { get; set; }

之类的事情

是的,我查看了Orchard文档中的Creating 1-N and N-N Relations。有人认为LazyField<T>可能是一个解决方案。但它出现(至少在我发现的信息或代码示例中我已经看过),LazyField<T>处理单独的表格场景。

请告诉我,我不需要单独的表格来完成我想要完成的任务。再次,这似乎有点矫枉过正。

以下是我想要做的一个简单示例:

public enum MyEnum
{
    Enum1,
    Enum2,
    Enum3,
    Enum4,
    Enum5
}

Selector.cs有助于自动选择单选按钮,下拉列表,复选框或多选列表:

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

PartRecord.cs

[MyEnumSelector]
public virtual IList<string> MyEnumCheckBox { get; set; }

Part.cs

public IList<string> MyEnumCheckBox
{
    get { return Record.MyEnumCheckBox; }
    set { Record.MyEnumCheckBox = value; }
}

注意:当我使用<string>时,我得到一个“表不存在”错误。如果我使用<MyEnum>代替,我会收到强制转换错误(generic.list v.abongper.icollection或某些变体)。

我已尝试使用不同的错误消息IEnumerableICollection

我必须想象Orchard / NHibernate会允许这种类型的行为,而不需要我创建一个新的表来引用(在这种情况下,这似乎有些过分)。

如果有人可以提供帮助,我会非常感激。这个问题让我斗智斗勇。赏金?现金?你说它的名字。是的,我很绝望。 :)

2 个答案:

答案 0 :(得分:4)

您可以使用enum MyEnum属性修饰[Flags],并将其项目的值设置为2的不同幂。例如,您的枚举可能如下所示:

[Flags]
public enum MyEnum
{
    Enum1 = 1,
    Enum2 = 2,
    Enum3 = 4,
    Enum4 = 8,
    Enum5 = 16
}

现在,MyEnumCheckBox类的PartRecord属性类型为int

public virtual int MyEnumCheckBox { get; set; }

您可以在Part类中创建代理属性。例如:

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;
        }
    }
}

您可以在Flags属性here上找到更多信息。它主要用于使您能够为单个枚举字段使用多个枚举值,这正是您正在寻找的内容。


编辑:

我花了很多时间并构建了一个自定义模块来演示这种技术。我已经测试了它,它的工作方式应该如此。所以这是来源:

Migrations.cs

public int Create() 
{
  SchemaBuilder.CreateTable("MultipleEnumPickerRecord", table => table
    .ContentPartRecord()
    .Column<int>("SelectedItems"));

  ContentDefinitionManager.AlterPartDefinition("MultipleEnumPickerPart", p => p.Attachable());

  return 1;
}

型号:

[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;
            }
        }
    }
}

处理程序:

public class MultipleEnumPickerHandler : ContentHandler
{
    public MultipleEnumPickerHandler(IRepository<MultipleEnumPickerRecord> repository)
    {
        Filters.Add(StorageFilter.For(repository));
    }
}

驱动:

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>
    <Place Parts_MultipleEnumPicker_Edit="Content:5"/>
</Placement>

最后,观点:

@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,当你实施这项技术时,你必须记住两件事:

  1. 枚举在内部被视为整数,这意味着它们的值占用32位。这反过来意味着MyEnum无法为此工作定义超过32个枚举项
  2. 正如Bertrand所指出的,数据库搜索项目会更难(尽管主要数据库允许您使用逐位运算符,但这并非不可能)。
  3. 可以通过在数据库和模型之间使用不同的映射函数来绕过这两个约束。

    这是什么意思?

    在我向您展示的示例中,数据库值(和MultipleEnumPickerRecord值)的类型为int,而在MultipleEnumPickerPart我已“映射”该整数为一个List<MyEnum>。这在数据库中占用的空间更少,并且比使用其他一些映射函数更快。

    例如,您可以对数据库string使用MultipleEnumPickerRecord类型,然后在List<MyEnum>内对MultipleEnumPickerPart进行某种映射。最流行的字符串映射函数是

    • 以逗号分隔的映射 - 例如,如果某人选择了Enum1Enum4,您可以将其映射到字符串"Enum1,Enum4"
    • 以分号分隔的映射 - 您可以将上一个示例映射到"Enum1;Enum4"

    要选择的分隔符类型应基于您知道字符串不会使用的字符。要从数据库中的字符串解构列表,您可以使用简单的value.Split(',').ToList();(如果您使用','作为分隔符)。

    这样,您不仅仅受到32个枚举项的限制,并且由于该值保存为字符串,因此在数据库中搜索某些值非常简单。缺点是字符串将在数据库中占用更多空间(int将占用字符串中一个字符的空间),字符串操作函数比上面示例中演示的逐位函数稍慢。

答案 1 :(得分:2)

您根本无法映射列表&lt; T&gt;像那样。您需要与另一个记录/表创建正确的关系,或者您可以使用例如以逗号分隔的值列表来管理存储。第一种情况当然有点干净,但第二种情况更容易,坦率地说,如果你不希望给定记录有太多的价值,那就很好了。

您可能还想考虑枚举字段。