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.
Related
Kindly tell me where I am doing a mistake I have seen StackOverflow same mistake but I am not finding my error after searchIng a lot.
This line Creates Error After Debugging
#Model.Product.category.Name
This is my front end
<div class="product-categories">
<span>Categories: </span>#Model.Product.category.Name
</div>
my View model looks like
public class ProductViewModel
{
public Product Product { get; set; }
}
This is my Entities
public class Product:BaseEntity
{
public decimal price { get; set; }
//public int CategoryID { get; set; }
public string ImageURL { get; set; }
public virtual Category category { get; set; }
}
```[Ent][3]
[Front End where an error has been raised][1]
[My Entities image for your better understanding][2]
[1]: https://i.stack.imgur.com/sHRtx.png
[2]: https://i.stack.imgur.com/CVsQQ.png
[3]: https://i.stack.imgur.com/WXOa0.png
This is my Product Service where I get Products and these lines of code are showing an error
Before Code
public Product GetProduct(int ID)
{
using (var context = new TablesContext())
{
return context.Products.Find(ID);
}
}
After Same code updated
public Product GetProduct(int ID)
{
using (var context = new TablesContext())
{
return context.Products.Where(x => x.id == ID).Include(x => x.category).FirstOrDefault();
}
}
this line of code updated from this return context.Products.Find(ID);
to this
return context.Products.Where(x => x.id == ID).Include(x => x.category).FirstOrDefault();
I'm really new to ASP.NET Core so I apologize if this is a silly question, but I've spent so many hours trying to figure this out.
I have 2 models, Teacher and Student. The relationship is one teacher to many students. This is a backend API project and Im working on a PUT method which can update fields in both models simultaneously in one request.
Here's my Teacher class:
public partial class Teacher
{
public Teacher()
{
Students = new HashSet<Student>();
}
public int TeacherId { get; set; }
public string Name { get; set; }
... tons of other properties ...
}
Here's my Student class:
public partial class Student
{
public int Id { get; set; }
public int TeacherId { get; set; }
public string Name { get; set; }
public virtual Teacher Teacher { get; set; }
}
Here's the controller:
[HttpPut("{id}")]
public async Task<IActionResult> PutTeachers(int id, TeacherViewModel model)
{
var result = await _service.UpdateAsync(model);
return Ok(result.Message);
}
(The code above is simplified)
- It takes in a TeacherViewModel which restricts the number of fields to be returned
- I used another class as a service to do the update
Here's the service class:
public class TeacherService
{
private readonly Repository<Teacher> _repository;
public TeacherService(DatabaseContextWrapper context)
{
_repository = new Repository<Teacher>(context);
}
public async Task<ITransactionResult> UpdateAsync(TeacherViewModel model)
{
var teacher = _repository.FindAsync(model.TeacherId).Result;
teacher.TeacherId = model.TeacherId;
teacher.Name = model.Name;
teacher.Students.Clear();
foreach(var student in model.Students)
{
teacher.Students
.Add(new Student
{
Id = Student.Id,
TeacherId = Student.TeacherId
Name = Student.Name
});```
}
}
}
My reasoning is to add the Student model to the to students under the Teacher model but it doesn't iterate through. If I comment out the clear code, the update will work but it won't cross update. It just simply wont iterate through. I guess I'm pretty lost at this point. Any help would be appreciated!
Edit 1 (Entity relationship configuration)
modelBuilder.Entity<Student>(entity => {
entity.HasOne(d => d.Teacher)
.WithMany(p => p.Students)
.HasForeignKey(d => d.TeacherId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Students_Teachers");
});
This is my configuration
Try adding the reverse config to your parents entity:
modelBuilder.Entity<Teacher>(entity =>
{
entity.HasMany(d => d.Students)
.WithOne(p => p.Teacher)
.HasForeignKey(d => d.TeacherId);
});
I am trying to display a checklist that gets data from MySQL Database and displays it in a view and updates the value of the variable (IsChecked) of each element in the table by whether we have checked the amenity or not (i am displaying some amenities). The model of the view is Hotel_5.ViewModel.BookingRoom, where BookingRoom is a custom model i created where i use multiple models. I get the exception at Model.AmenitiesList.Count(). The model is null.
This is my view
<div class="form-group">
#for (var i = 0; i < Model.AmenitiesList.Count(); i++)
{
#Html.CheckBoxFor(m => m.AmenitiesList[i].IsChecked, new { #class = "form-control" });
<label>#Model.AmenitiesList[i].amenityType</label>
//If you need to hide any values and get them in your post
#Html.HiddenFor(m => m.AmenitiesList[i].AmenityId)
#Html.HiddenFor(m => m.AmenitiesList[i].AmenityPrice)
}
</div>
This is my ViewModel
public class BookingRoom
{
public Bookings bookings { get; set; }
public Rooms rooms { get; set; }
public List<Amenities> AmenitiesList { get; set; } = new List<Amenities>();
}
This is my Amenities Model
public class Amenities
{
[Key]
public int AmenityId { get; set; }
public double AmenityPrice { get; set; }
public AmenityType amenityType { get; set; }
public bool IsChecked { get; set; }
}
public enum AmenityType
{
tv,
wi_fi,
hair_dryer,
help
}
When Querying you should Include its AmenitiesList too, otherwise it will be null:
In Controller:
var bookingRoom = context.BookingRooms.Include(b => b.AmenitiesList).FirstOrDefault(b => b.Id == someId);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
please note that what I queried might not be what you want, it is just to demonstrate how to use Include() and also you should add using Microsoft.EntityFrameworkCore.
I need to have only one view and :
a) create new Customer and Address or
b) For existing Customer add new Address
But I dont known what is wrong with my Save action.
How to set Customer's AddressId to new Address Id ( jus created) ?
I use:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Address> Addresses { get; set; }
}
I have two models:
public class Customer
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Name { get; set; }
public Address Address { get; set; }
public int? AddressId { get; set; }
}
public class Address
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Town { get; set; }
}
And one viewModel
public class CustomerAddressViewModels
{
public Customer Customer { get; set; }
public Address Addresses { get; set; }
}
Then I create a controller with good working Details action
public class CustomerDetailsController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: CustomerDetails/Details/5
public async Task<ActionResult> Details(int id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Customer customer = await db.Customers
.Include(c => c.Address)
.SingleOrDefaultAsync(c => c.Id == id);
return View("CustomerAddressView");
}
}
I wrote Save action for Create and Update cases:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Save(Customer customer, Address address)
{
if (!ModelState.IsValid)
{
var vieModel = new CustomerAddressViewModels();
return View("CustomerAddressView", vieModel);
}
if (customer.AddressId == 0)
{
address.StreetName = customer.Address.StreetName;
db.Addresses.Add(address);
}
else
{
var addressInDb = db.Addresses
.Single(a => a.Id == customer.AddressId);
addressInDb.StreetName = customer.Address.StreetName;
}
if (customer.Id == 0)
{
db.Customers.Add(customer);
}
else
{
var customerInDb = db.Customers
.Single(c => c.Id == customer.Id);
customerInDb.Name = customer.Name;
customerInDb.AddressId = customer.AddressId;
}
await db.SaveChangesAsync();
return RedirectToAction("Index","Customers");
}
I need to have only one view and :
a) create new Customer and Address or
b) For existing Customer add new Address
But I dont known what is wrong with my Save action.
How to set Customer's AddressId to new Address Id ( jus created) ?
When inserting an address you should add the address to the customer.Addresses collection rather than to db.Address directly, then EF should handle populating the keys for you.
There are some other things worth mentioning here I think.
You should use a viewmodel class that represents the objects being passed to and from your views instead of using your entities directly. So I'd recommend a class like this:
public class CustomerViewModel
{
public int CustomerOd { get; set; }
//<... other properties for customer>
public AddressViewModel Address { get; set; }
}
public class AddressViewModel
{
//Address propties
}
This allows you to have view specific properties on your object that can help with various things (like whether to hide or show a section for example) based on a value that isn't inside your Customers entity.
Then your controller Action save looks like this:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Save(CustomerViewModel model)
{
//preceeding code
var customer = db.Customers.SingleOrDefault(x => x.CustomerId == model.CustomerId)
if (customer.Address == null)
{
var address = new Address()
{
StreetName = model.Address.StreetName
};
Customer.Addresses.Add(address);
}
}
You then map or populate the data to your Entity from the ViewModel object. It involves a little extra work but allows for more flexibility.
What's happening in your Details action? You get customers but then don't pass it to your view?
Customer customer = await db.Customers
.Include(c => c.Address)
.SingleOrDefaultAsync(c => c.Id == id);
return View("CustomerAddressView");
In order for the framework to correctly bind your object from the form to your view for the address object, you should describe your html objects in the same structure of your object. So you could have an input like this
<input value="#Model.Address.StreetName" name="Address.StreetName"/>
and it should bind that value to the address object when you post back.
Hope this helps.
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.