开发者

EF 4 CTP 5 saving a many-to-many navigation property

I'm using EF4 CTP5 and am having trouble saving records back to the database. I have Contact and ContactType entities. As the post title states, I have set up a many-to-many navigation property between the tables.

The problem is with validating the ContactType values. ModelState.IsValid is false because it's unable to convert the values passed back from the form (a string array of ContactType id's into ContactType objects.

POCO's

public partial class Contact
{
    public Contact()
    {            
        this.ContactTypes = new HashSet<ContactType>();
    }

    // Primitive properties
    public int ContactId { get; set; }
    public string ContactName { get; set; }

    // Navigation properties
    public virtual ICollection<ContactType> ContactTypes { get; set; }
}

public partial class ContactType
{
    public ContactType()
    {
        this.Contacts = new HashSet<Contact>();
    }

    // Primitive properties
    public int ContactTypeId { get; set; }
    public string Name { get; set; }

    // Navigation properties
    public virtual ICollection<Contact> Contacts { get; set; }
}

Controller

//
// GET: /Contact/Edit/5
public virtual ActionResult Edit(int id)
{
    Contact contact = context.Contacts.Include(c => c.ContactTypes).Single(x => x.ContactId == id);
    ViewData["ContactTypesAll"] = GetTypesList();
    return View(contact);
}

//
// POST: /Contact/Edit/5
[HttpPost]
public virtual ActionResult Edit(Contact contact)
{
    if (ModelState.IsValid)
    {
        context.Entry(contact).State = EntityState.Modified;
        context.SaveChanges();
        return RedirectToAction("Index");
    }   
    ViewData["ContactTypesAll"] = GetTypesList();
    return View(contact);
}

View

<div class="field-block">
    @Html.LabelFor(model => model.ContactId)
    @Html.EditorFor(model => model.ContactId, new { fieldName = "ContactId" })
    @Html.ValidationMessageFor(model => model.ContactId)
</div>
<div class="field-block">
    @Html.LabelFor(model => model.OrganizationNameInternal)
    @Html.EditorFor(model => mode开发者_运维问答l.OrganizationNameInternal)
    @Html.ValidationMessageFor(model => model.OrganizationNameInternal)
</div>
<div class="field-block">
    @Html.LabelFor(model => model.ContactTypes)
    @Html.ListBoxFor(modelContactType, 
            new MultiSelectList((IEnumerable<TDAMISObjects.ContactType>)ViewData["ContactTypesAll"],
                "ContactTypeId",
                "Name",
                Model.ContactTypes))
    @Html.ValidationMessageFor(model => model.ContactTypes)
</div>

ModelState error

ModelState.Values.ElementAt(2).Value
{System.Web.Mvc.ValueProviderResult}
    AttemptedValue: "5"
    Culture: {en-US}
    RawValue: {string[1]}

ModelState.Values.ElementAt(2).Errors[0]
{System.Web.Mvc.ModelError}
    ErrorMessage: ""
    Exception: {"The parameter conversion from type 'System.String' to type 'ProjectObjects.ContactType' failed because no type converter can convert between these types."}

So it seems pretty clear what the problem is, but I can't seem to find the solution. I have tried to manually convert the ContactType id's into ContactType objects, and adding them to the Contact object passed into the Edit function (called 'contact'):

contact.ContactTypes.Clear();
string[] ids = this.HttpContext.Request.Form["ContactTypes"].Split(',');
for(int i = 0; i< ids.Length; i++)
{
    int x = Convert.ToInt32(ids[i]);
    ContactType selectedType = context.ContactTypes.Single(t => t.ContactTypeId == x);
    contact.ContactTypes.Add(selectedType);
}

but the error persists. I've also tried calling

context.ChangeTracker.DetectChanges();

but that did not do the trick. I also manually set the ValueProviderResult for the value that will not validate, using

ModelState.SetModelValue("ContactTypes", val);

Which also did not work. I feel like I'm missing something basic here. Any ideas?

Thanks, Steve


Well after more work on this, I found a work around. Basically, I had to ignore the validation errors, then manually remove existing ContactTypes, then add in ones selected by the user. I did try to build a custom validator for the Contact.ContactTypes propery, but a ContactType object was always passed in to that method; I never saw the array of strings. Admittedly, that was the first custom validator I have built, so perhaps I was missing something.

Anyway, here's the Edit method I ended up with:

//
// POST: /Contact/Edit/5
[HttpPost]
public virtual ActionResult Edit(Contact contact)
{
    // clear up ModelState.IsValid for contact type
    ModelState.Remove("ContactTypes");

    if(ModelState.IsValid)
    {
        // remove all contact types for contact
        Contact dummy = context.Contacts.Single(c => c.ContactId == contact.ContactId);
        if(dummy.ContactTypes.Count > 0)
        {
            dummy.ContactTypes.Clear();
            context.Entry(dummy).State = EntityState.Modified;
            context.SaveChanges();
        }
        context.Detach(dummy);
        context.Contacts.Attach(contact);

        // add currently selected contact types, then save
        string[] ids = this.HttpContext.Request.Form["ContactTypes"].Split(',');
        for(int i = 0; i< ids.Length; i++)
        {
            int x = Convert.ToInt32(ids[i]);
            ContactType selectedType = context.ContactTypes.Single(t => t.ContactTypeId == x);
            contact.ContactTypes.Add(selectedType);                    
        }
        context.Entry(contact).State = EntityState.Modified;
        context.SaveChanges();

        ViewBag.Message = "Save was successful.";
    }

    ViewData["ContactTypes"] = contact.ContactTypes.Select(t => t.ContactTypeId);
    ViewData["ContactTypesAll"] = GetTypesList();            
    return View(contact);
}

I had to add a Detach method to my DBContext class as well (in CTP 5 this is not exposed):

public void Detach(object entity) 
{
    ((System.Data.Entity.Infrastructure.IObjectContextAdapter)this).ObjectContext.Detach(entity);
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜