views:

943

answers:

1

This is driving me crazy. Hopefully my question makes sense...

I'm using MVC 2 and Entity Framework 1 and am trying to insert a new record with two navigation properties.

I have a SQL table, Categories, that has a lookup table CategoryTypes and another self-referencing lookup CategoryParent. EF makes two nav properties on my Category model, one called Parent and another called CategoryType, both instances of their respective models.

On my view that creates the new Category, I have two dropdowns, one for the CategoryType and another for the ParentCategory. When I try and insert the new Category WITHOUT the ParentCategory, which allows nulls, everything is fine. As soon as I add the ParentCategory, the insert fails, and oddly (or so I think) complains about the CategoryType in the form of this error:

0 related 'CategoryTypes' were found. 1 'CategoryTypes' is expected.

When I step through, I can verifiy that both ID properties coming in on the action method parameter are correct. I can also verify that when I go to the db to get the CategoryType and ParentCategory with the ID's, the records are being pulled fine. Yet it fails on SaveChanges().

All that I can see is that my CategoryParent dropdownlistfor in my view, is somehow causing the insert to bomb.

Please see my comments in my httpPost Create action method.

My view model looks like this:

public class EditModel
{
    public Category MainCategory { get; set; }
    public IEnumerable<CategoryType> CategoryTypesList { get; set; }
    public IEnumerable<Category> ParentCategoriesList { get; set; }
}

My Create action methods look like this:

// GET: /Categories/Create
    public ActionResult Create()
    {
        return View(new EditModel()
        {
            CategoryTypesList = _db.CategoryTypeSet.ToList(),
            ParentCategoriesList = _db.CategorySet.ToList()
        });
    }

    // POST: /Categories/Create
    [HttpPost]
    public ActionResult Create(Category mainCategory)
    {
        if (!ModelState.IsValid)
            return View(new EditModel()
            {
                MainCategory = mainCategory,
                CategoryTypesList = _db.CategoryTypeSet.ToList(),
                ParentCategoriesList = _db.CategorySet.ToList()
            });

        mainCategory.CategoryType = _db.CategoryTypeSet.First(ct => ct.Id == mainCategory.CategoryType.Id);

        // This db call DOES get the correct Category, but fails on _db.SaveChanges().
        // Oddly the error is related to CategoryTypes and not Category.
        // Entities in 'DbEntities.CategorySet' participate in the 'FK_Categories_CategoryTypes' relationship.
        // 0 related 'CategoryTypes' were found. 1 'CategoryTypes' is expected.
        //mainCategory.Parent = _db.CategorySet.First(c => c.Id == mainCategory.Parent.Id);

        // If I just use the literal ID of the same Category,
        // AND comment out the CategoryParent dropdownlistfor in the view, all is fine.
        mainCategory.Parent = _db.CategorySet.First(c => c.Id == 2);

        _db.AddToCategorySet(mainCategory);
        _db.SaveChanges();

        return RedirectToAction("Index");
    }

Here is my Create form on the view :

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

    <fieldset>
        <legend>Fields</legend>

        <div>
            <%= Html.LabelFor(model => model.MainCategory.Parent.Id) %>
            <%= Html.DropDownListFor(model => model.MainCategory.Parent.Id, new SelectList(Model.ParentCategoriesList, "Id", "Name")) %>
            <%= Html.ValidationMessageFor(model => model.MainCategory.Parent.Id) %>
        </div>

        <div>
            <%= Html.LabelFor(model => model.MainCategory.CategoryType.Id) %>
            <%= Html.DropDownListFor(model => model.MainCategory.CategoryType.Id, new SelectList(Model.CategoryTypesList, "Id", "Name"))%>
            <%= Html.ValidationMessageFor(model => model.MainCategory.CategoryType.Id)%>
        </div>

        <div>
            <%= Html.LabelFor(model => model.MainCategory.Name) %>
            <%= Html.TextBoxFor(model => model.MainCategory.Name)%>
            <%= Html.ValidationMessageFor(model => model.MainCategory.Name)%>
        </div>

        <div>
            <%= Html.LabelFor(model => model.MainCategory.Description)%>
            <%= Html.TextAreaFor(model => model.MainCategory.Description)%>
            <%= Html.ValidationMessageFor(model => model.MainCategory.Description)%>
        </div>

        <div>
            <%= Html.LabelFor(model => model.MainCategory.SeoName)%>
            <%= Html.TextBoxFor(model => model.MainCategory.SeoName, new { @class = "large" })%>
            <%= Html.ValidationMessageFor(model => model.MainCategory.SeoName)%>
        </div>

        <div>
            <%= Html.LabelFor(model => model.MainCategory.HasHomepage)%>
            <%= Html.CheckBoxFor(model => model.MainCategory.HasHomepage)%>
            <%= Html.ValidationMessageFor(model => model.MainCategory.HasHomepage)%>
        </div>

        <p><input type="submit" value="Create" /></p>
    </fieldset>

<% } %>

Maybe I've just been staying up too late playing with MVC 2? :) Please let me know if I'm not being clear enough.

Some things I've changed since asking this question. I think I'm close?

My new Create action method:

        private DbEntities _db = new DbEntities();

    // POST: /Categories/Create
    [HttpPost]
    public ActionResult Create(Category mainCategory)
    {
        if (!ModelState.IsValid)
            return View(new EditModel()
            {
                MainCategory = mainCategory,
                CategoryTypesList = _db.CategoryTypeSet.ToList(),
                ParentCategoriesList = _db.CategorySet.ToList()
            });

        int parentId = 2; // Accessories in db.
        short typeId = 1; // Product type in db.
        mainCategory.ParentReference.EntityKey = new EntityKey("DbEntities.CategorySet", "Id", parentId);
        mainCategory.CategoryTypeReference.EntityKey = new EntityKey("DbEntities.CategoryTypeSet", "Id", typeId);

        _db.AddToCategorySet(mainCategory);     
        _db.SaveChanges();

        return RedirectToAction("Index");
    }

I've also commented out my two dropdownlists so those properties on "mainCategory" don't get instantiated.

So, with the 2 nav properties null now, I use two literals for the Id's and it works perfectly. Ultimately, I want to use the Id's of Parent and CategoryType from mainCategory, but this seems to not work. Probably a good reason I'm not aware of?

+1  A: 

Did you try setting the EntityReference like

mainCategory.CategoryTypeReference.EntityKey = new EnityKey("YourEntities", "Id", mainCategory.CategoryType.Id);

that way you wont have to load your CategoryType and it might resolve your problem.

EDIT: The sample i posted is not complete, sorry, here is what you should do

int parentId = mainCategory.Parent.Id; 
short typeId = mainCategory.CategoryType.Id;

mainCategory.Parent = null;
mainCategory.CategoryType = null;
mainCategory.ParentReference.EntityKey = new EntityKey("DbEntities.CategorySet", "Id", parentId); 
mainCategory.CategoryTypeReference.EntityKey = new EntityKey("DbEntities.CategoryTypeSet", "Id", typeId);
moi_meme
I do like this way of adding a foreign key for EF version 1.0, but unfortunately, it behaves the same way on insert, but only when I add a CategoryType along with a parent Category. If I omit the parent Category, both ways work fine. Your way being the most efficient for sure.
CannibalCorpse
See my edit, forgot to tell that you have to set the Parent and CategoryType to null, that way there won't be problem with reference they could have
moi_meme
Welp, that did the trick!!! I basically just did what you said here, thinking that it was a little odd that I have to set those nav properties to null. But it works! In my edit method I don't have to null those properties, so I guess this only goes for inserts. In any matter, THANK YOU moi_meme!!!!!!!!! ...And everyone who took a stab at this!
CannibalCorpse