I'm use bundle of: ASP.NET MVC3, SQL Server 2012, EF5. When I try to save values into my database, I can save only Book's property IsSelected in Books table in my database, StudentName cant saved, and cant saved datas into my StudentBooks table in database, whach contains StudentId and BookId, keys for many-to-many relationships, any ideas?
public class CheckboxController : Controller
{
EFDbContext context = new EFDbContext(ConfigurationManager.ConnectionStrings[1].ConnectionString);
//
//GET: /Checkbox/
public ActionResult Index()
{
ViewModelData vmd = new ViewModelData();
List<Book> bookList = new List<Book>();
using (EFDbContext te = new EFDbContext(ConfigurationManager.ConnectionStrings[1].ConnectionString))
{
var student = te.Students.First();
vmd.StudentName = student.StudentName;
var data = te.Books.ToList();
foreach (var d in data) {
Book book = new Book();
book.BookName = d.BookName;
book.IsSelected = false;
book.BookId = d.BookId;
bookList.Add(book);
}
}
vmd.Books = bookList;
return View(vmd);
}
//
//GET: /Checkbox/
[HttpPost]
public ActionResult Index(ViewModelData vm)
{
foreach (var book in vm.Books) {
context.Books.First(x => x.BookId == book.BookId).IsSelected = book.IsSelected;
}
context.SaveChanges();
return View(vm);
}
}
public class ViewModelData
{
public string StudentName { get; set; }
public List<Book> Books { get; set; }
}
View
#model UsingEFNew.Controllers.ViewModelData
#{
ViewBag.Title = "Example";
}
#using (Html.BeginForm("Index", "Checkbox", null, FormMethod.Post))
{
<div>Your Name:</div>
<div>
#Html.TextBoxFor(model => model.StudentName)
</div>
for (int i = 0; i < Model.Books.Count; i++) {
#Html.CheckBoxFor(m => Model.Books[i].IsSelected)
#Html.DisplayFor(m => Model.Books[i].BookName)
#Html.HiddenFor(m => Model.Books[i].BookId)
#Html.HiddenFor(m => Model.Books[i].BookName)
#:</br>
}
}
My Models
public class Book {
public int BookId { get; set; }
public string BookName { get; set; }
public bool IsSelected { get; set; }
public ICollection<Student> Students { get; set; }
}
public class Student {
public int StudentId { get; set; }
public string StudentName { get; set; }
public ICollection<Book> Books { get; set; }
public ICollection<City> Cities { get; set; }
}
In your [HttpPost]Index you're not take the student Name from you ViewDataModel and pushing it into any Student entity.
[HttpPost]
public ActionResult Index(ViewModelData vm)
{
foreach (var book in vm.Books) {
context.Books.First(x => x.BookId == book.BookId).IsSelected = book.IsSelected;
}
var student = context.Students.First();
student.Name = vm.StudentName;//now I've actually changed the student entity and changes will be pushed to database on Save()
context.SaveChanges();
return View(vm);
}
It doesn't look like you're really leveraging the relationship between them however. You just grab the first student rather than using the relationship from book to student. I have done the same here just to demonstrate mapping the value from your view model into your entity.
Related
I'm trying to build a form for information about upcoming exams. My plan is to make a dropdown list which shows a List of teachers to chose from and who will be responsible for the exam.
Here's a simplified version of my Models:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
}
public class Teacher
{
public int Id { get; set; }
public int PersonId { get; set; }
public Person Person { get; set; }
}
public class Exam
{
public int Id { get; set; }
public string Subject { get; set; }
public DateTime ExamDate { get; set; }
public int TeacherId { get; set; }
public Teacher Teacher { get; set; }
}
My ViewModel:
public class ExamViewModel
{
public Exam Exam { get; set; }
public IEnumerable<Person> People { get; set; }
public IEnumerable<Teacher> Teachers { get; set; }
}
And my Create action from ExamController:
public ActionResult Create()
{
var people = _context.People.ToList();
var teachers = _context.Teachers.ToList();
var viewModel = new ExamViewModel
{
People = people,
Teachers = teachers,
};
return View(viewModel);
}
I'd like to display People.Firstname + " " + People.Surname of all teachers on the dropdown mentioned above, but instead of submitting People.Id I'd like to submit Teachers.Id to Exams.TeacherId
I first tried to displaying a list of the all FirstName before trying displaying FirstName and Surname with the following razor html helper in my Create view but I already failed there as I was only able to access the properties from one single class (Teachers) to use is as dataValueField and dataTextField for new SelectList():
<h2>Plan exam</h2>
#using (Html.BeginForm("Create", "ExamController"))
{
<div class="form-floating mb-3">
#Html.DropDownListFor(m => m.Exam.TeacherId, new SelectList(Model.Teachers, "Id", "FirstName"), "-- Please select --", new { #class = "form-control", #placeholder = "Teacher" })
#Html.LabelFor(m => m.Exam.TeacherId)
</div>
}
I'm quite new to programming so I'd be very very grateful for any kind of help.
in your viewModel you can only send Exams , but in the query to fetch exams you must make a join to fetch for every exam the teacherID , teacherName , teacherSubname,
to Learn about that check this :
here
or this :
here
another thing , in your model ( classes ) definition , i think you have to add a relationship between Exam and Teacher , and between Teacher an People , if you do so it will be easier to fetch only the necessary data , and you will use only one property ( exams ) in your viewModel to send data to the view, to do so check this here
I found a solution:
First I created a second ViewModel with a property Id for Teacher.Id and FullName for Person.FirstName + " " + Person.Surname:
public class TeacherViewModel
{
public int Id { get; set; }
public string FullName { get ; set; }
}
Then I removed the unnecessary IEnumerable<Person> People from my ExamViewModel and added an IEnumerable<> for Teachers of Type TeacherViewModel:
public class ExamViewModel
{
public Exam Exam { get; set; }
public IEnumerable<TeacherViewModel> Teachers { get; set; }
}
I created a join as mentionen at step 1, converted it to a list in the new TeacherViewModel:
public ActionResult Create()
{
var teachers = (from t in _context.Teachers
join p in _context.People
on t.PersonId equals p.Id
select new TeacherViewModel
{
Id = t.Id,
FullName = p.FirstName + " " + p.Surname,
}).ToList();
var viewModel = new ExamViewModel
{
Teachers = teachers,
};
return View(viewModel);
}
I added a Save() method to the controller:
public ActionResult Save(Exams exam)
{
_context.Exams.Add(exam);
_context.SaveChanges();
return RedirectToAction("Create", "Exams");
}
Added the submit button to the View and changed the action name beside Html.BeginForm( to Save as it will handle saving the variables from the form.
#using (Html.BeginForm("Save", "Exams"))
{
<div class="form-floating mb-3">
#Html.DropDownListFor(m => m.Exam.TeacherId, new SelectList(Model.Teachers, "Id", "FullName"), "-- please select --", new { #class = "form-control", #placeholder = "Teacher" })
#Html.LabelFor(m => m.Exam.TeacherId)
</div>
<button type="submit" class="btn btn-primary">Submit</button>
}
I have a form which has a place where a user can insert multiple tags separated by a comma into the database. I got it to insert, but I'm having trouble retrieving it to show on my edit form.
This is my Edit Action:
public IActionResult Edit(int id)
{
var gallery = _ctx.GalleryImages.SingleOrDefault(m => m.Id == id);
if (gallery == null)
return NotFound();
var categories = _ctx.Categories.ToList();
var model = new GalleryFormViewModel(gallery)
{
Tags = gallery.Tags,
Category = categories,
};
return View("Views/Image/UploadForm.cshtml", model);
}
Here is my ViewModel:
public class GalleryFormViewModel
{
public int? Id { get; set; }
public string Title { get; set; }
public IEnumerable<ImageTag> Tags { get; set; }
public IEnumerable<Category> Category { get; set; }
[Required]
public int CategoryId { get; set; }
public IFormFile ImageUplaod { get; set; }
public GalleryFormViewModel()
{
Id = 0;
}
public GalleryFormViewModel(GalleryImage galleryImage)
{
Id = galleryImage.Id;
Title = galleryImage.Title;
Tags = galleryImage.Tags;
CategoryId = galleryImage.CategoryId;
}
}
And here is the Form input: (I'm using this form for creating and editing the gallery)
<div class="form-group">
#Html.LabelFor(m => m.Tags)
#Html.TextBoxFor(m => m.Tags, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Tags)
</div>
Here is the Tag Model:
namespace SimpleImageGallery.Data.Models
{
public class ImageTag
{
public int Id { get; set; }
public string Description { get; set; }
}
}
Here is the Gallery Model:
public class GalleryImage
{
public virtual IEnumerable<ImageTag> Tags { get; set; }
// ....
}
This is how the tags table looks in the database:
It seems like I'm not getting any errors, maybe something is wrong in the actual input field?
There are some mistakes :
First, you have to Include the Tags to retrieve them from DB (if using Entity Framework):
var gallery = _ctx.GalleryImages.Include(m=>m.Tags).SingleOrDefault(m => m.Id == id);
Secondly, you are doing the same this twice :
var model = new GalleryFormViewModel(gallery)
{
Tags = gallery.Tags,
Category = categories,
};
and
public GalleryFormViewModel(GalleryImage galleryImage)
{
Id = galleryImage.Id;
Title = galleryImage.Title;
Tags = galleryImage.Tags;
CategoryId = galleryImage.CategoryId;
}
Thirdly, you cannot do this : #Html.TextBoxFor(m => m.Tags, new { #class = "form-control" }) for a enumerable, you have to reconstruct the string.
I have these models
public class employee
{
public int Empid {get;set;}
public string name {get;set;}
public string fname{get;set;}
}
public class empLanguage
{
public string language { get; set; }
public string speaking{ get; set; }
}
public class EmpInsertion
{
public string name { get; set; }
public string fname { get; set; }
public List<ViewModels.Employee.empLanguage> EmpLangs { get; set; }
}
and i have this controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Insert(EmpInsertion empins)
{
if (ModelState.IsValid)
{
Employee Emp = new Employee();
Emp.name= empins.name;
Emp.fname= empins.fname;
var MaxID = (from emp in db.Employees select emp.EmployeeID).Max();
EmpLanguage objlng = new EmpLanguage();
objlng.EmployeeID = MaxID;
foreach (var emplang in Emp.EmpLanguages.ToList())
{
empLanguage lng = new empLanguage();
emplang.Language = lng.language;
emplang.Speaking = lng.speaking;
empins.EmpLangs.Add(lng);
}
}
}
I have two tables Employee(id, name, fname) Language(id, language, speaking, empid) it has one to many relationship, each employee can speak multiple language at same time.
I want to add data in both tables from one view, how can I add to one employee multiple language
I think you can do something like this.
public Employee CreatePartnerWithUser(Employee employee, List<Language> language)
{
using (DataContext context = new DataContext())
{
using (var trans = context.Database.BeginTransaction())
{
var insertEmployee = context.Employee.Add(employee);
context.SaveChanges();
var insertLanguage = context.Language.AddRange(language);
context.SaveChanges();
trans.Commit();
}
}
}
If use DbContext and EF change your model like this:
public class employee
{
public int id {get; set;}
public string name {get; set;}
public string fname {get; set;}
public virtual ICollection<language> language { get; set; }
}
public class language
{
public int id {get; set;}
public string languageName {get; set;}
public virtual ICollection<employee> employee { get; set; }
}
then in DbContext class add this modelBuilder to have many to many relations
modelBuilder.Entity<employee>()
.HasMany(e => e.language)
.WithMany(e => e.employee)
.Map(m => m.ToTable("employeeLanguage").MapLeftKey("employeeID").MapRightKey("languageID"));
after that, you can insert employee in a model like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Insert(employee emp, string langs)
{
if (ModelState.IsValid)
{
using(DbContext db = new DbContext())
{
if(langs != null)
{
string[] strLangs = langs.Split('-');
foreach (var l in strLangs)
{
string lan = l.Trim();
while (lan.Contains(" "))
lan.Replace(" ", " ");
emp.language.Add(new language
{
languageName = lan
});
}
}
db.employee.Add(emp);
db.SaveChanges();
return View("YourReturnPage");
}
}
return View(Insert);
}
and in Insert.cshtml file adds this piece of code:
<div class="form-group">
<label for="Title" class="control-label col-md-2">Languages</label>
<div class="col-md-10">
<textarea type="text" name="langs" class="form-control" value="#ViewBag.langs"></textarea>
<p class="help-block text-danger">Separate language keywords with - </p>
</div>
</div>
I'm trying to create a drop down list that will let you specify which company an employee works for. Below is my EmployeeViewModel. CompanyId is a foreign-key constrained database field which links to the Company table.
public class EmployeeViewModel
{
public EmployeeViewModel()
{
}
public EmployeeViewModel(Employee Employee, string CompanyName)
{
Initialize(Employee, CompanyName);
}
public EmployeeViewModel(Employee Employee, CliFFEntities db)
{
Initialize(Employee, db.Companies.Find(Employee.CompanyId).Name);
}
private void Initialize(Employee employee, string CompanyName)
{
this.Id = employee.Id;
this.Name = employee.Name;
this.CompanyId = employee.CompanyId;
this.InternalId = employee.InternalId;
this.CompanyName = CompanyName;
}
public int Id { get; set; }
public string Name { get; set; }
public Nullable<int> InternalId { get; set; }
[Display(Name = "Company")]
public int CompanyId { get; set; }
public String CompanyName { get; set; }
//public List<Company> CompanyList { get; set; }
public IEnumerable<SelectListItem> CompanyList { get; set; } //to be set in controller on an as-needed basis
}
Relevant part of the Employees controller:
// GET: Employees/Create
public ActionResult Create()
{
var evm = new EmployeeViewModel();
evm.CompanyList = new SelectList(db.Companies, "Id", "Name");
return View(evm);
}
Relevant part of my create view:
<div class="form-group">
#Html.LabelFor(m => m.CompanyId)
<div class="col-md-10">
#Html.DropDownListFor(m => m.CompanyId, Model.CompanyList)
#Html.ValidationMessageFor(m => m.CompanyId)
</div>
</div>
So all that seems to work fine. In fact, when I look at the post data it even sets the CompanyId to the correct value, corresponding to a company ID in the database.
But if I enforce the foreign key on the database side, it throws an error because that CompanyId doesn't seem to make it into the Employee record. If I turn off the FK constraint, the CompanyId just shows up as a 0.
What gives?
Welp, looks like I'm so new to MVC 5 that I didn't realize that there's such thing as a post method. Here's what I changed it to to map the EmployeeViewModel back to Employee:
// POST: Employees/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,CompanyId,InternalId")] EmployeeViewModel evm)
{
var employee = new Employee { Name = evm.Name, CompanyId = evm.CompanyId, InternalId = evm.InternalId };
if (ModelState.IsValid)
{
db.Employees.Add(employee);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(employee);
}
My goal here is to use EF7 with MVC6 [BETA2] to list a number of bookshelves and the number of books on each shelf.
The database schema is created correctly with the correct table relationships. I can successfully add shelves and books to the database including the foreign key relationships (see code below).
When I test the index page that should show the book count on each shelf, I receive no book count data and no errors. In the Shelf entity the property Books remains unpopulated with Book entities thus the count is null (see code below).
In EF7 is there somewhere where I need to write code to populate Shelf.Books or should this happen automatically in EF7?
BookShelf.cs
namespace MyApp.Models
{
public class Shelf
{
public int ShelfId { get; set; }
public string Name { get; set; }
public virtual List<Books> Books { get; set; }
}
public class Book
{
public int BookId { get; set; }
public string Name { get; set; }
public int ShelfId { get; set; }
public Shelf Shelf{ get; set; }
}
}
ApplicationDbContext.cs
namespace MyApp
{
public class ApplicationDBContext
{
public DbSet<Shelf> Shelf { get; set; }
public DbSet<Book> Book { get; set; }
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Shelf>().Key(s => s.ShelfId);
builder.Entity<Book>().Key(b => b.BookId);
builder.Entity<Shelf>()
.OneToMany(s => s.Book)
.ForeignKey(k => k.ShelfId);
base.OnModelCreating(builder);
}
}
ShelfController.cs
namespace MyApp
{
private ApplicationDBContext db;
public BuildingsController(ApplicationDBContext context)
{
db = context;
}
// GET: Shelves
public async Task<IActionResult> Index()
{
return View(await db.Shelves.ToListAsync());
}
}
Index.cshtml
...
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Books.Count)
</td>
</tr>
}
....
Take a look at ICollection Vs List in Entity Framework. I have a feeling the minor examples of EF7 using List<> are just incorrect (hard to imagine that with EF7 the best practice is changed from ICollection<> to List<>, it's generally very poor practice to expose a concrete collection type as a property.)
Per your comment I would change:
Create View Models
public class IndexViewModel
{
public IEnumerable<ShelveModel> Shelves { get; set; }
}
public class ShelveModel
{
public string Name { get; set; }
public int BookCount { get ; set; }
}
Update the logic
// GET: Shelves
public async Task<IActionResult> Index()
{
var model = new IndexViewModel();
model.Shelves = db.Shelves
.Select(s => new
{
Name = s.Name,
BookCount = s.Books.Count()
})
.ToListAsync()
.Select(s => new ShelveModel()
{
Name = s.Name,
BookCount = s.Books.Count()
})
.ToList();
return View(model);
}
What I have discovered is that EF does not populate the parent object with related children objects out of the box. Example, myShelf.Books will be empty until populated in the controller action function.