2

我一辈子都无法让它与我现有的代码一起使用,但我正在尝试将我的enum选择保存为 NHibernate 中的字符串。基本上,我有一个 UI 复选框,如果用户选择多个复选框,我想存储这些选择。现在,我可以让 NHibernate 存储一个选择(例如,从下拉列表或单选按钮列表中,用户仅限于一个选择)。

这是我对枚举的主要内容:

public enum IncomeType
{
    [Display(Name = "Full-Time Employment")]
    FullTime,
    [Display(Name = "Part-Time Employment")]
    PartTime,
    [Display(Name = "Self-Employment")]
    SelfEmployed,
    [Display(Name = "Rental")]
    Rental,
    [Display(Name = "Social Security Payments")]
    SocialSecurity,
    [Display(Name = "Retirement / Pension Payments")]
    Retirement,
    [Display(Name = "Child Support Payments")]
    ChildSupport,
    [Display(Name = "Spousal Maintenance")]
    Maintenance,
    [Display(Name = "Other")]
    Other
}

我使用一种方法来“选择”是否显示复选框列表(如果 myBulkItemThreshold等于选项数,则显示复选框列表)。这是那个方法:

public static IEnumerable<SelectListItem> GetItemsFromEnumString<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)
    };
}

(注意:其中的一些项目是助手,但我认为它们不相关;此外,所选输入使用模板.cshtml文件显示 - 再次,不确定这是否相关)

现在,我这样称呼它:

public class IncomeTypeSelectorAttribute : SelectorAttribute
{
    public override IEnumerable<SelectListItem> GetItems()
    {
        return Selector.GetItemsFromEnumString<IncomeType>();
    }
}

最后,我们到达了virtual属性(使用代理),但这是 NHibernate 抛出扳手的地方(注意:在 NHibernate 之前这对我来说工作得很好,现在我试图让许多代码行使用它而不必重新做所有事情;如果我重新做所有事情,我可能会将我已经必须让它工作的代码增加三倍):

财产(记录):

[IncomeTypeSelector(BulkSelectionThreshold = 9)]
public virtual List<string> IndividualIncomeTypeCheckBox { get; set; }

代理(部分):

public List<string> IndividualIncomeTypeCheckBox
{
    get { return Record.IndividualIncomeTypeCheckBox; }
    set { Record.IndividualIncomeTypeCheckBox = value; }
}

同样,这就是我做事的方式,并且在 NHibernate 之前它工作得很好。但现在我必须使用 NHibernate。没有绕过它。

我正在使用一个服务类,它将两者结合在一个 Create 方法中,以使用 NHibernate 保存在数据库中,对于上面的内容,它通常看起来像这样:

 part.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;

如果它只是一个选择,这将起作用。

好吧,我花了整整两 (2) 个月的时间试图让它发挥作用。这很困难,因为我有很多代码,用户只能做出一个选择(例如使用单选按钮列表),而且效果很好——即使使用 NHibernate。让我给你举个例子:

public virtual IncomeType? IndividualIncomeTypeCheckBox { get; set; }

如果我执行上述操作,它将显示一个下拉列表,NHibernate 会将用户选择的 ONE 允许选项存储在数据库中,这没有问题。但是一个以上的选项List<string>不起作用。

现在,我已经尝试了在这里或其他地方能找到的一切,但没有任何效果。是的,我知道它应该是IList<IncomeType>或其他一些变体。但是如果我使用它,那么 NHibernate 需要它IncomeType是数据库中的另一个表。对于我相信的这么简单的事情,编写的代码太多了。我们不是在谈论多对多关系,因为这不是具有多个地址的用户(其中​​地址将具有街道、城市、州、邮政编码等)。

我尝试了不同类型的代理getset代码,但没有任何效果。我已经尝试过[Flags]和其他东西一起工作string,但无济于事。那些最后的解决方案将“工作”,但只能保存从多个中选择的第一个项目(即,在我的场景中,如果用户选择“FullTime”和“Rental”作为收入类型,那么只会string保存“FullTime”()或“1”([Flags]/ int),而不是两个项目都被选中。

ReadOnly我有一种情况,我使用这样的属性重新显示选择:

[IncomeTypeSelector]
[ReadOnly(true)]
public List<string> IndividualIncomeTypeCheckBoxPost
{
    get { return IndividualIncomeTypeCheckBox; }
}

这将显示在 UI 上,但我尝试用 NHibernate 做这样的事情,但它不起作用。

enum谁能告诉我,使用上面的内容,我怎样才能让 NHibernate在这个复选框列表场景中存储多个?

更新: 更多在这里和网络上戳,我想出了以下(仍然不起作用)。

财产(记录):

[IncomeTypeSelector(BulkSelectionThreshold = 9)]
public virtual IList<IncomeTypeRecord> IndividualIncomeTypeCheckBox
{ 
    get { return incomeType; } 
    set { incomeType= value; } 
}
private IList<IncomeTypeRecord> incomeType = 
    new List<IncomeTypeRecord>();

代理(部分):

public IList<IncomeTypeRecord> IndividualIncomeTypeCheckBox
{
    get { return Record.IndividualIncomeTypeCheckBox; }
    set { Record.IndividualIncomeTypeCheckBox= value; }
}

以及对枚举的更改:

public enum IncomeType : int // removing int & value still gives validate error
{
[Display(Name = "Full-Time Employment")]
FullTime = 1,
[Display(Name = "Part-Time Employment")]
PartTime,
....
}

我添加了这个类来支持IncomeTypeRecord

public class IncomeTypeRecord
{
    public virtual int Id { get; set; }
    public virtual IncomeType Value { get; set; }
}

但是,当我进入 UI 屏幕并选择一个或多个选项时,我收到验证错误(值无效)。例如,假设我单独选择 FullTime,或者选择 FullTime 和 Retirement,那么 UI 将显示以下错误:

'FullTime' 值无效。

'FullTime,Retirement' 值无效。

(分别)

即使我删除了int声明enum并摆脱了以“1”开头的值,我仍然会收到此验证错误。我尝试弄乱并添加不同的模型活页夹(现在让我很难过我原来的问题是否仍然存在,现在我有一个不同的问题 - 但你仍然可以获得赏金:))。

把我的头发拉出来。如果我能提供更多的赏金,我会的。我需要一个明确的解决方案。我很感激任何帮助。

更新 这是我到目前为止所拥有的:

记录:

public virtual string IndividualIncomeTypeCheckBox{ get; set; }

部分:

//If I do IEnumberable<string> my .Select throws a cast error
public IEnumerable<IncomeType> IndividualIncomeTypeCheckBox
    {
        get
        {
            return Record
                .IndividualIncomeTypeCheckBox
                .Split(',')
                .Select(r => (IncomeType)Enum.Parse(typeof(IncomeType), r));
        }
        set { Record.IndividualIncomeTypeCheckBox= value 
            == null ? null : String.Join(",", value); }
    }

服务等级:

public SimplePart CreateSimple(SimplePartRecord record)
{
    SimplePart simple = Services.ContentManager.Create<SimplePart>("Simple");
    ...
    //How I would save a FirstName property (example Part / PartRecord below)
    //public virtual string FirstName { get; set; } - PartRecord
    //public string FirstName - Part
    //{
    //    get { return Record.FirstName ; }
    //    set { Record.FirstName= value; }
    //}
    simple.FirstName = record.FristName;
    ...
    //I obviously cannot do the following with the above IncomeType
    //Getting cannot convert string to IEnumerable error
    //How would I write this:
    simple.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;
    ...
}

这就是它在控制器中的调用方式(这持续到数据库):(更新控制器代码)

public ActionResult Confirm(string backButton, string nextButton)
{
    if (backButton != null)
        return RedirectToAction("WrapUp");
    else if ((nextButton != null) && ModelState.IsValid)
    {
        _myService.CreateSimple(myData.SimplePartRecord);
        return RedirectToAction("Submitted");
    }
    else
        return View(myData);
}

更新附加代码(序列化和视图模型):

“myData”在控制器(使用序列化)中定义为:

private MyViewModel myData;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
    var serialized = Request.Form["myData"];
    if (serialized != null)
    {
        myData = (MyViewModel)new MvcSerializer().Deserialize
            (serialized, SerializationMode.Signed);
        TryUpdateModel(myData);
    }
    else
        myData = (MyViewModel)TempData["myData"] ?? new MyViewModel();
    TempData.Keep();
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
   if (filterContext.Result is RedirectToRouteResult)
        TempData["myData"] = myData;
}

我使用序列化是因为我在前端设置了一个多步骤向导(如控制器操作“backButton”“nextButton”中所示)。我没有使用驱动程序(只能显示 Admin 或在前端但然后仅在 ~/Views 文件夹下的 .cshtml 文件上(而不是像我正在使用的结构化文件夹列表中)。没有驱动程序 = 没有更新视图模型类型代码 = 没有在数据库中“创建”数据的机制。如果我不使用一些“创建”类型的方法,表单会提交,但所有数据都是“NULL”。

当您说数据应该自动持久化时,我很抱歉,但我不知道如何。我阅读的所有内容或我查看的代码都有一些方法可以使用表单中输入的任何内容来更新数据库。如果我遗漏了什么,我很抱歉。

“MyViewModel”非常简单:

[Serializabel]
public class MyViewModel
{
    public SimplePartRecord SimplePartRecord { get; set; }
}

而且,以防万一,这里是迁移的相关部分(返回 1 是一个完全独立且不相关的表):

public int UpdateFrom1()
{
SchemaBuilder.CreateTable("SimplePartRecord",
    table => table
    .ContentPartRecord()
        ...
        .Column("IndividualIncomeTypeCheckBox", DbType.String)
        ...
    );
ContentDefinitionManager.AlterPartDefinition("SimplePart",
    part => part
    .Attachable(false));
return 2;
}

我得到的错误是

无法将类型“字符串”隐式转换为“System.Collections.Generic.IEnumerable””

当我在我的服务类的“创建”方法中执行以下操作时:

simple.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;

另一个想法:我尝试使用 nn Relation 示例来处理这种情况。除了我认为应该简单明了的大量额外代码之外,由于我使用序列化的方式,我有很多对象引用错误,并且无法弄清楚如何正确编码我的控制器来处理它。

4

3 回答 3

5

这里有很多信息需要浏览,所以希望我没有错过重点。在我看来,目标是:

  • 业务类有一个集合属性,IList<IncomeType>不需要额外的表
  • 该集合中的值应保留为枚举名称的分隔字符串

最好的方法是使用自定义用户类型(的实现NHibernate.UserTypes.IUserType)来映射属性。下面是一个泛型IUserType,它将类型的枚举TIList<T>属性映射到数据库中的逗号分隔字符串并再次返回。没有简单的方法将 T 限制为枚举,但代码仅适用于枚举。

使用 Fluent NHibernate 使用自定义类型映射属性很简单:

public class Person
{
    public Person()
    {
        IncomeTypes = new List<IncomeType>();
    }

    public virtual int PersonId { get; protected set; }
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }

    public virtual IList<IncomeType> IncomeTypes { get; protected set; }
}

public class PersonMap : ClassMap<Person>
{
    public PersonMap()
    {
        Table("Person");
        Id(x => x.PersonId).GeneratedBy.Identity();
        Map(x => x.FirstName);
        Map(x => x.LastName);
        Map(x => x.IncomeTypes).CustomType<EnumAsDelimitedStringType<IncomeType>>();
    }
}

这是用户类型的代码:

public class EnumAsDelimitedStringType<T> : IUserType
{
    public new bool Equals(object x, object y)
    {
        if (ReferenceEquals(x, y))
        {
            return true;
        }
        var xList = x as IList<T>;
        var yList = y as IList<T>;
        if (xList == null || yList == null)
        {
            return false;
        }
        // compare set contents
        return xList.OrderBy(xValue => xValue).SequenceEqual(yList.OrderBy(yValue => yValue));
    }

    public int GetHashCode(object x)
    {
        return x.GetHashCode();
    }

    public object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        var outValue = NHibernateUtil.AnsiString.NullSafeGet(rs, names[0]) as string;
        if (string.IsNullOrEmpty(outValue))
        {
            return new List<T>();
        }
        var getValueArray = outValue.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
        return Array.ConvertAll(getValueArray, s => (T)Enum.Parse(typeof(T), s)).ToList();
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        var inValue = value as IList<T>;
        // set to string.Empty if you prefer to store that instead of null when the collection is null or empty
        object setValue = null;
        if (inValue != null && inValue.Any())
        {
            var setValueArray = Array.ConvertAll(inValue.ToArray(), v => Enum.GetName(typeof(T), v));
            setValue = string.Join(",", setValueArray);
        }
        NHibernateUtil.AnsiString.NullSafeSet(cmd, setValue, index);
    }

    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[] {new SqlType(DbType.AnsiString)}; }
    }
    public Type ReturnedType
    {
        get { return typeof(IList<T>); }
    }

    public bool IsMutable
    {
        get { return false; }
    }
}
于 2013-01-15T17:46:56.567 回答
2

我认为你在追求[Flags]枚举的正确轨道上。您可能已经这样做了,但以防万一——使枚举标志有价值不仅仅是添加属性。您还必须以二进制友好的方式指定项目的值。我发现最简单的方法如下:

[Flags]
public enum IncomeType : long // you'll need the room with several options
{
    FullTime = 1,
    PartTime = 1 << 1,
    SelfEmployed = 1 << 2
    // And so on
}

如果您不这样做,那么您将获得连续的整数值,这会破坏允许您在单个整数中执行多个值的按位比较。

您创建SelectList外观的代码很好。您的选项应该构造以相同名称回发的表单值。如果你想使用默认的模型绑定器,这意味着你的视图模型上的关联属性需要是List<int>. 如果您不使用视图模型(您可能应该),您可以将其从表单集合中拉出。

一旦你完成了这个设置,那么从你的视图模型转换到你的 NHibernate 实体是很简单的,如果有点烦人的话。您基本上必须循环遍历列表中的值并将|=它们放到您的 NHibernate 实体的单个枚举属性中。

所以让我们假设你有一个这样的视图模型:

public class MyEditViewModel
{
    public string Name { get; set; }
    public List<int> IncomeSelections { get; set; }

    // You'll probably have this to populate the initial view rendering
    public SelectList AllIncomeOptions { get; set; }
}

您将使用您的助手和所有这些构建您的视图,然后使用构建复选框,SelectList 但确保输入名称为 IncomeSelections,然后当它被回发时,您会将视图模型数据推送到您的 NHibernate 实体中,如下所示:

var myNHEntity = new NHEntity(); 
// If you're editing an existing entity, then be sure to reset the enum
// value to 0 before going into the following foreach loop...

foreach (var incomeSelection in viewModel.IncomeSelections)
{
    myNHEntity.IncomeSelection |= incomeSelection;
}

可能有一种更聪明的方法可以做到这一点,您可能必须将其int转换为您的枚举类型,但您会弄清楚的(我会为您做的,但现在是星期五,我已经打开了啤酒) .

NHibernate 应该坚持它,而您不必在 NH 方面做任何时髦的事情。

总之...

与 NHibernate 方面相比,这似乎更像是您如何处理已发布数据的问题。如果你实现了这样的东西,那么一定要使用 Fiddler 或 FireBug 来检查发布的值,以确保 1)它们是整数和 2)名称相同,因此它们将被添加到列表中。

祝你好运!

于 2013-01-11T23:42:32.690 回答
0

问题很简单,如果不与中间关联表建立完整关系,它将无法映射列表。让记录将值存储为逗号分隔的字符串更简单(因此您的记录属性是字符串,而不是字符串列表),并且您的部分可以在字符串和列表之间来回映射。

你可以在这里找到一个非常接近的例子:

https://bitbucket.org/bleroy/nwazet.commerce/src/d722cbebea525203b22c445905c9f28d2af7db46/Models/ProductAttributesPartRecord.cs?at=default

https://bitbucket.org/bleroy/nwazet.commerce/src/d722cbebea525203b22c445905c9f28d2af7db46/Models/ProductAttributesPart.cs?at=default

它没有使用枚举值,而是一个 id 列表,但这应该让您对如何相当简单地进行这项工作有一个很好的了解:解析您已经知道该怎么做的枚举。

如果您需要更多详细信息,请告诉我,但我认为这就是您解除封锁所需要的。

于 2013-01-15T00:00:43.320 回答