2

私の人生では、これを既存のコードで動作させることはできませんがenum、NHibernate で選択内容を文字列として保存しようとしています。基本的に、UI チェック ボックスがあり、ユーザーが複数のチェック ボックスを選択した場合は、それらの選択を保存します。これで、NHibernate に 1 つの選択 (たとえば、ユーザーが 1 つの選択肢のみに制限されているドロップダウン リストまたはラジオ ボタン リストから) を保存することができます。

これは私が列挙型のために持っているものの要点です:

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 の前に私にとってはうまく機能していましたが、現在、多くのコード行を取得する必要なしにそれを操作しようとしています。すべてをやり直します; すべてをやり直すと、すでに動作させる必要があるコードがおそらく 3 倍になります):

プロパティ (レコード):

[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 メソッドで 2 つを結び付けて NHibernate を使用して DB に保存するサービス クラスを使用しています。上記の場合、通常は次のようになります。

 part.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;

これは、選択が 1 つだけの場合に機能します。

さて、私はこれを機能させるために 2 か月を費やしました。ユーザーが 1 つしか選択できないコード (ラジオボタン リストなど) がたくさんあるので、大変です。例を挙げましょう:

public virtual IncomeType? IndividualIncomeTypeCheckBox { get; set; }

上記を実行すると、ドロップダウン リストが表示され、NHibernate はユーザーが選択した 1 つの許容可能なオプションを問題なく DB に保存します。ただし、複数のオプションはList<string>機能しません。

今、私はここまたは他の場所で見つけることができるすべてを試しましたが、何も機能しません. IList<IncomeType>はい、私はそれがまたは他の変種であるべきであることを知っています。しかし、これを使用する場合、NHibernate はそれIncomeTypeが DB 内の別のテーブルである必要があります。これは、私が信じているような単純なことのために書くには多すぎるコードです。これは、複数の住所を持つユーザーではないという意味で、多対多の関係について話しているわけではありません (住所には、番地、都市、州、郵便番号などがあります)。

さまざまな種類のプロキシgetsetコードを試しましたが、何も機能しません。私は試してみましたが[Flags]、他のことstringだけで動作しましたが、役に立ちませんでした。これらの最後のソリューションは「機能」しますが、複数の中から選択された最初の項目のみを保存します (つまり、私のシナリオでは、ユーザーが収入の種類として「フルタイム」と「レンタル」を選択した場合、「フルタイム」( string) のみが保存されます)または "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 画面に移動して 1 つ以上のオプションを選択すると、検証エラー (値が無効) が発生します。たとえば、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;
    ...
}

そして、これはコントローラーで呼び出される方法です(これはDBに永続化されます):(コントローラーコードの更新)

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" に見られるように) をセットアップするため、シリアライゼーションを使用します。 ~/Views フォルダーの直下にある .cshtml ファイルのみ (私が使用しているような構造化されたフォルダー リストではありません)) ドライバーなし = 更新ビュー モデル タイプ コードなし = DB にデータを「作成」するメカニズムなし。 「作成」タイプのメソッドは使用しません。フォームは送信されますが、すべてのデータは「NULL」になります。

データを自動的に永続化する必要があるとのことですが、申し訳ありませんが、方法がわかりません。私が読んだものやレビューしたコードには、フォームに入力されたものでDBを更新するいくつかの方法があります。何か不足している場合は、お詫び申し上げます。

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

私が得ているエラーは

タイプ 'string' を 'System.Collections.Generic.IEnumerable' に暗黙的に変換することはできません"

サービスクラスの「Create」メソッドで次のことを行うと:

simple.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;

追加の考え: nn Relation サンプルを使用して、このシナリオを処理しようとしました。単純明快にすべきだと思っていたコードが余分にたくさんあることは別として、シリアライゼーションの使用方法が原因で、多くのオブジェクト参照エラーが発生し、それを処理するためにコントローラーを適切にコーディングする方法を理解できませんでした。

4

3 に答える 3

5

ここにはたくさんの情報がありますので、ポイントを逃していないことを願っています。目標は次のとおりです。

  • ビジネス クラスにはIList<IncomeType>、追加のテーブルを必要としないコレクション プロパティがあります。
  • そのコレクションの値は、列挙名の区切り文字列として永続化する必要があります

最善の方法は、カスタム ユーザー タイプ ( の実装NHibernate.UserTypes.IUserType) を使用してプロパティをマップすることです。以下は、プロパティIUserTypeの列挙型をデータベース内のカンマ区切りの文字列にマップし、再び戻すジェネリックです。T を列挙型に制限する簡単な方法はありませんが、コードは列挙型でのみ機能します。TIList<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

問題は、中間の関連付けテーブルとの完全な関係を構築しないと List をマップできないことです。値をコンマ区切りの文字列としてレコードに格納する方が簡単で (したがって、レコード プロパティは文字列のリストではなく文字列です)、パーツは文字列とリストの間を行き来できます。

ここで非常に近いものの例を見つけることができます:

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 に答える