I'm quite new to using MVC3 and EF4 and I'm trying to implement the CRUD functions of a parent-child entity set, however I haven't found an example of some specific requirements that I need and therefore would like a bit of help.
The situation I have is that I have a Product
entity that has child Category
entities. I have all of the CRUD functionality working fine for the Product
entity and the detail functionality working with the Category
entity within the Product
details view. However I need to implement the addition and removal of child Categories
from a given Product
.
Normally this wouldn't be much of an issue, but in this case when a user adds a Category
to a Product
I need to only allow the user to be able to select from a list of all available Categories
from the database. The user will also be able to remove any of the existing child Categories
from a Product
.
I expect implementing a DropDownList with all of the unused 'Categories' would work well, however I don't know how to use one to allow the user to add and remove 'Categories' and then save the changes to the database via EF.
Has anyone got any suggestions/examples on how to accomplish this?
If any extra information is required, please ask.
Thanks very much.
I have done similar with Authors and Books with many-to-many relation. The basic idea is to create a JSON object from view which contains all authors inside it and submit to controller. I also used jQuery UI TagIt to allow user to add/remove authors associated with the book. When user clicks on 'Save Book' button, the script builds a JSON object which mimics the Book object.
Below is the code. Please make sure you have added "json2.js" and "tagit.js" in the project before you try this code.
View Models:
public class BookViewModel
{
public string Title { get; set; }
public int BookId { get; set; }
public int IsAvail { get; set; }
public string CallNumber { get; set; }
//Assiged authors
public List<AuthorViewModel> Authors { get; set; }
//available authors
public List<AuthorViewModel> AuthorOptions { get; set; }
}
public class AuthorViewModel
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
}
Code for Book/Edit.chtml:
@using System.Web.Script.Serialization
@model eLibrary.Models.BookViewModel
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@*This is for JSON*@
<script src="../../Scripts/json2.js" type="text/javascript"></script>
<script src="../../Scripts/tagit.js" type="text/javascript"></script>
@*These are for styling Control*@
<link href="../../Content/themes/base/jquery.ui.all.css" rel="stylesheet" type="text/css" />
<script type="text/javascript">
//This function is used for sending data(JSON Data) to BookController
function BookSave() {
// Step 1: Read View Data and Create JSON Object
var author = { "AuthorId": "", "FirstName": "" };
// Creating book Json Object
var book = { "BookId": "", "Title": "", "IsAvail": "", "CallNumber":"", "authors": []};
// Set Boook Value
book.BookId = $("#BookId").val();
book.Title = $("#Title").val();
book.IsAvail = $("#IsAvail").val();
book.CallNumber = $("#CallNumber").val() ;
var tags = $('#authors').tagit('tags');
for (var i in tags) {
author.AuthorId = tags[i].value;
author.FirstName = tags[i].label;
book.authors.push(author );
author = { "AuthorId": "", "FirstName": "" };
}
// Step 1: Ends Here
// Set 2: Ajax Post
// Here i have used ajax post for saving/updating information
$.ajax({
url: '/Book/Edit',
data: JSON.stringify(book),
type: 'POST',
contentType: 'application/json;',
dataType: 'json',
success: function (result) {
if (result.Success == "1") {
window.location.href = "/Book/Edit";
}
else {
alert(result.ex);
}
}
});
}
</script>
@using (Html.BeginForm())
{
@Html.ValidationSummary(true)
<fieldset>
<legend>Book Details</legend>
@Html.HiddenFor(model => model.BookId)
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.IsAvail)
</div>
@Html.EditorFor(model => model.IsAvail)
@Html.ValidationMessageFor(model => model.IsAvail)
@Html.EditorFor(model => model.CallNumber);
</fieldset>
@Html.Partial("AuthorsByBook", Model.Authors, new ViewDataDictionary { { "mode", "EDIT" } })
<input type="button" value="Book Save" onclick="BookSave()" />
}
Code for Book/AuthorsByBook.chtml
@model IEnumerable< eLibrary.Models.AuthorViewModel>
<link href="../../Content/tagit-awesome-blue.css" rel="stylesheet" type="text/css" />
<div class="box">
<ul id="authors" name="authors">
</ul>
</div>
<script type="text/javascript">
//Load authors in the javascript variable
$(function () {
var initialAuthorList=[];
@if(ViewData["mode"]=="EDIT")
{
foreach (var category in Model)
{
<text>
initialAuthorList.push({label: "@category.FirstName", value: @category.AuthorId });
</text>
}
}
$('#authors').tagit({tagSource: function (request, response) {
$.ajax({
url: "/Author/SearchAuthor", type: "POST", dataType: "json",
data: { searchText: request.term, maxResults: 10 },
success: function (data) {
response($.map(data, function (item) {
return { label: item.FirstName, value: item.AuthorId }
}))
}
})
},
initialTags:initialAuthorList,minLength:3,allowNewTags:false,sortable:true,delay:400});
});
</script>
Code for BookController.cs
public ActionResult Edit(int id)
{
//To DO: use repository to fetch data
Book book = db.Books.Single(a => a.BookId == id);
Mapper.CreateMap<Book, BookViewModel>();
Mapper.CreateMap<Author, AuthorViewModel>();
BookViewModel bookVm = Mapper.Map<Book, BookViewModel>(book);
List<AuthorViewModel> Authors = Mapper.Map<List<Author>,List<AuthorViewModel>>( db.Authors.ToList());
bookVm.AuthorOptions = Authors;
return View(bookVm);
}
[HttpPost]
public ActionResult Edit(BookViewModel bookv)
{
//create maps
Mapper.CreateMap<AuthorViewModel, Author>();
Mapper.CreateMap<BookViewModel, Book>();
//convert view objects to model objects
Book book = Mapper.Map<BookViewModel, Book>(bookv);
List<Author> authors = Mapper.Map<List<AuthorViewModel>, List<Author>>(bookv.Authors.ToList());
//this has to be executed before db.Books.Attach
//clear authors
book.Authors.Clear();
db.Books.Attach(book);
//assign authors to book
foreach (Author a in authors) { db.Authors.Attach(a); }
book.Authors.Clear();
foreach (Author a in authors) { book.Authors.Add(a); }
db.ObjectStateManager.ChangeObjectState(book, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
Code for SearchAuthor method in AuthorController.cs
public JsonResult SearchAuthor(string searchText, int? maxResults)
{
IEnumerable<Author> query = db.Authors;
searchText = searchText.ToLower();
query = query.Where(c => c.FirstName.ToLower().Contains(searchText));
if ((maxResults ?? 0) == 0)
{
return Json(query.ToList());
}
else
{
return Json(query.Take((int)maxResults).ToList());
}
}