10

複数のモデルが送信されているフォームのバインドに問題があります。苦情情報と1対多の苦情を含む苦情フォームがあります。フォームを送信しようとしていますが、バインドでエラーが発生します。ModelState.IsValidは常にfalseを返します。

ModelStateエラーをデバッグして表示すると、「EntityCollectionは既に初期化されています。InitializeRelatedCollectionメソッドは、オブジェクトグラフの逆シリアル化中に新しいEntityCollectionを初期化するためにのみ呼び出す必要があります」というメッセージが表示されます。

また、デバッグ時に、フォーム送信から苦情モデルに苦情が入力されていることがわかります。そのため、一部は機能しているようです。

デフォルトのModelBinderで自分が行っていることが不可能なのか、それとも単に正しい方法で行っていないのかはわかりません。これに関する具体的な例やドキュメントが見つからないようです。ここのstackoverflowでも非常によく似た問題が見つかりますが、強く型付けされたビューを処理していないようです。

コントローラーコード:

    public ActionResult Edit(int id)
    {
        var complaint = (from c in _entities.ComplaintSet.Include("Complainants")
                     where c.Id == id
                     select c).FirstOrDefault();

        return View(complaint);
    }

    //
    // POST: /Home/Edit/5
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(Complaint complaint)
    {
        if (!ModelState.IsValid)
        {
            return View();
        }
        try
        {
            var originalComplaint = (from c in _entities.ComplaintSet.Include("Complainants")
                                     where c.Id == complaint.Id
                                     select c).FirstOrDefault();
            _entities.ApplyPropertyChanges(originalComplaint.EntityKey.EntitySetName, complaint);
            _entities.SaveChanges();
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }

ビューコード(これは、ビューの作成/編集によって呼び出される部分的なビューであり、苦情で強く入力されます):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ProStand.Models.Complaint>" %>

<%= Html.ValidationSummary() %>
<% using (Html.BeginForm()) {%>

<table cellpadding="0" cellspacing="0" class="table">
    <tr>
        <td>
        <label for="DateReceived">Date Received:</label>
            <%= Html.TextBox("DateReceived") %>
            <%= Html.ValidationMessage("DateReceived", "*") %>    
        </td>
        <td>
            <label for="DateEntered">Date Entered:</label>
            <%= Html.TextBox("DateEntered")%>
            <%= Html.ValidationMessage("DateEntered", "*") %>
        </td>
    </tr>
    <tr>
        <td>
            <label for="Concluded">Concluded:</label>
            <%= Html.CheckBox("Concluded")%>
            <%= Html.ValidationMessage("Concluded", "*") %>
            </td>
        <td>
            <label for="IncidentDate">Incident Date:</label>
            <%= Html.TextBox("IncidentDate")%>
            <%= Html.ValidationMessage("IncidentDate", "*") %></td>
    </tr>
</table>
    <hr />
    <table>
    <% if (Model != null) {
           int i = 0;
       foreach (var complainant in Model.Complainants){ %>
       <%= Html.Hidden("Complainants[" + i + "].Id", complainant.Id)%>
    <tr>
        <td>
            <label for="Surname">Surname:</label>

            <%= Html.TextBox("Complainants[" + i + "].Surname", complainant.Surname)%>
            <%= Html.ValidationMessage("Surname", "*")%>
        </td>
        <td>
            <label for="GivenName1">GivenName1:</label>
            <%= Html.TextBox("Complainants[" + i + "].GivenName1", complainant.GivenName1)%>
            <%= Html.ValidationMessage("GivenName1", "*")%>
        </td>
    </tr>
    <% i++; %>
    <% }} %>
    <tr>
        <td colspan=2>
            <input type="submit" value="Submit" />
        </td>
    </tr>
</table>
<% } %>
<div>
    <%=Html.ActionLink("Back to List", "Index") %>
</div>
4

5 に答える 5

3

盲目的な推測:

変化する:

<%= Html.TextBox("Complainants[" + i + "].Surname", complainant.Surname)%>

と:

<%= Html.TextBox("Complaint.Complainants[" + i + "].Surname",  
complainant.Surname)%>

それぞれ-「苦情」を追加します。「申立人[...」の前

編集

これは正しい答えではありません。適切な答えが表示されるまで、それが何らかの価値を追加する可能性があるという理由だけで、削除せずに残しました。

EDIT2:

私は間違っているかもしれませんが、私にとっては、エンティティフレームワーク(または-使用方法)に問題があるようです。つまり、asp.net mvcは要求から値を読み取ることができますが、不満のコレクションを初期化することはできません。

ここに書かれています:

InitializeRelatedCollection(TTargetEntity)メソッドは、デフォルトのコンストラクターを使用して作成された既存のEntityCollection(TEntity)を初期化します。EntityCollection(TEntity)は、提供された関係とターゲットの役割名を使用して初期化されます。

InitializeRelatedCollection(TTargetEntity)メソッドは、逆シリアル化中にのみ使用されます。

いくつかの詳細:

例外:

  • InvalidOperationException

条件:

  • 提供されたEntityCollection(TEntity)がすでに初期化されている場合。
  • リレーションシップマネージャがすでにObjectContextに接続されている場合。
  • リレーションシップマネージャーに、この名前とターゲットロールのリレーションシップが既に含まれている場合。

InitializeRelatedCollectionが2回起動されるのはなぜですか。不幸なことに、私はなぜ正確に明るい考えを持っていませんでした。たぶん、この小さな調査は他の誰かのために役立つでしょう-EFの経験が豊富です。:)

EDIT3:
これは、この特定の問題の解決策ではなく、回避策のようなものであり、MVCのモデル部分を処理する適切な方法です。

プレゼンテーション専用のビューモデルを作成します。純粋なPOCOからも新しいドメインモデルを作成します(EFは次のバージョンでのみそれらをサポートするため)。AutoMapperを使用して、EFDataContext <=> Model<=>ViewModelをマップします。

それには多少の努力が必要ですが、それを処理する必要があります。このアプローチは、モデルからプレゼンテーションの責任を取り除き、ドメインモデルをクリーンアップし(モデルからEFのものを削除し)、バインディングの問題を解決します。

于 2009-07-21T11:48:05.297 に答える
3
public ActionResult Edit([Bind(Exclude = "Complainants")]Complaint model)
{
  TryUpdateModel(model.Complainants, "Complainants");
  if (!ModelState.IsValid)
  {
      // return the pre populated model
      return View(model);
  }

}

これは私のために働きます!

Complaintオブジェクトが作成されると、その'Complainants'コレクションが初期化され(エンティティフレームワークの自動ロジックのため)、モデルバインダーもコレクション自体を作成しようとするため、エラーが発生すると思います。ただし、モデルを手動で更新しようとすると、コレクションは既に初期化されていますが、モデルバインダーはモデルを再度初期化するように求められません。

于 2010-02-01T11:10:48.980 に答える
2

ケースバイケースの回避策なしでこれを機能させるには、独自のモデルバインダーを作成し、メソッドSetPropertyをオーバーライドする必要があります。

public class MyDefaultModelBinder : DefaultModelBinder
{
    protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
    { 
        ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name]; 
        propertyMetadata.Model = value;
        string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyMetadata.PropertyName);

        // Try to set a value into the property unless we know it will fail (read-only 
        // properties and null values with non-nullable types)
        if (!propertyDescriptor.IsReadOnly) { 
        try {
            if (value == null)
            {
            propertyDescriptor.SetValue(bindingContext.Model, value);
            }
            else
            {
            Type valueType = value.GetType();

            if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(EntityCollection<>))
            {
                IListSource ls = (IListSource)propertyDescriptor.GetValue(bindingContext.Model);
                IList list = ls.GetList();

                foreach (var item in (IEnumerable)value)
                {
                list.Add(item);
                }
            }
            else
            {
                propertyDescriptor.SetValue(bindingContext.Model, value);
            }
            }

        }
        catch (Exception ex) {
            // Only add if we're not already invalid
            if (bindingContext.ModelState.IsValidField(modelStateKey)) { 
            bindingContext.ModelState.AddModelError(modelStateKey, ex); 
            }
        } 
        }
    }
}

Global.asaxにバインダーを登録することを忘れないでください:

ModelBinders.Binders.DefaultBinder = new MyDefaultModelBinder();
于 2010-10-08T13:48:48.397 に答える
1

次のようにして、ModelBinding例外を回避しました。

// Remove the error from ModelState which will have the same name as the collection.
ModelState.Remove("Complaints"/*EntityCollection*/); 
if (ModelState.IsValid) // Still catches other errors.
{
    entities.SaveChanges(); // Your ObjectContext
}

主な欠点は、例外がまだスローされ、実行時にコストがかかる可能性があることです。洗練された回避策は、既存のDefaultBinderのラッパーを作成し、それがEntityCollectionを再度インスタンス化しないようにすることです。これはすでにEFによって実行されています。次に、そのコレクションをフォーム値にバインドします(FormCollection)。

複数のコレクションをバインドしている場合は、コレクションごとにエラーを削除する必要があることに注意してください。

私の実験では、コレクションは正常に保存され、コレクションが含まれていたグラフのルートオブジェクトも保存されました。

それが他の誰かを助けることを願っています。

于 2009-10-01T02:42:49.470 に答える
0

私も同じ問題を抱えていました!結局、フレームワークは複雑なモデルを処理できないことがわかります。

投稿の複雑なバインディングを初期化できる小さなバインディングコンポーネントを作成しました。

しかし、基本的にあなたがしなければならないことは、ArnisL。が言っていることです。

于 2009-09-17T04:20:51.943 に答える