Data from Collection is missing from database (ASP.NET MVC) - c#

For simplicity and testing, i'm doing this from a new project and then apply it to my actual project once i understand the problem.
I made a model, named Person, which contains a List property, named ServiceNeeded. Users in the front end may encode as much string of services as they wish, so the input field for ServiceNeeded is dynamically created. In the POST method, those string input binds as expected. I save the Person object into the database, and works as expected. When I try to retrieve the people objects from the database, all but ServicesNeeded are present.
Here are my codes
Model (Person.cs):
public class Person
{
public Guid Id { get; set; }
public String Name { get; set; }
public List<String> ServiceNeeded { get; set; }
public Person()
{
this.ServiceNeeded = new List<String>();
}
}
Controller(Index and [POST]Create methods):
public ActionResult Index()
{
var x = db.People.ToList();
return View(db.People.ToList());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,ServiceNeeded")] Person person)
{
if (ModelState.IsValid)
{
person.Id = Guid.NewGuid();
db.People.Add(person);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
As I said the input fields for ServicesNeeded are dynamically create but properly bind to the model.
Here are some screenshots during runtime:
After user do some input:
The I added a variable before Index returns the View to check in runtime:
As seen, the same Person is present and everything but ServicesNeeded. I am fairly new with ASP.NET MVC and web development in general. I do handle other collections in my project, although are collection of objects. Only this particular case I do not understand. Where could my error be?

You need to tell the dbcontext to load the references if lazy loading is not enabled.
First change ServiceNeeded to a virtual property
public virtual List<String> ServiceNeeded { get; set; }
then load the property when you pull down the person by using Include
var x = db.People.Include("ServiceNeeded").ToList();
return View(x);
Article on loading related entities https://msdn.microsoft.com/en-us/data/jj574232.aspx

Look in the database; are the records there? I think the issue is that the ServicesNeeded collection is not persisted to the database; try adding:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,ServiceNeeded")] Person person)
{
if (ModelState.IsValid)
{
person.Id = Guid.NewGuid();
db.People.Add(person);
foreach (var service in person.ServicesNeeded)
db.ServicesNeeded.Add(service);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
I'm don't believe the relationships attached to the Person object auto-persist on SaveChanges(), and are probably not making it to the database.

I think the best idea that suits Entity Frame work is to create another Model for your 'ServiceNeeded', add it to your 'DbContext', save any services in the corresponding DbSet and retrieve them using .Include in LINQ. After this lengthy introduction look at the following codes:
In your Models:
public class MyService{
public Guid Id {get;set;}
public string ServiceName {get;set;}
public Guid PersonId {get;set;}
}
public class Person
{
public Guid Id { get; set; }
public String Name { get; set; }
public virtual IList<MyService> ServiceNeeded { get; set; }
}
In your ApplicationDbContext:
public System.Data.Entity.DbSet<MyService> MyServices { get; set; }
public System.Data.Entity.DbSet<Person> People { get; set; }
In your POST ActionResult:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,ServiceNeeded")] Person person, List<String> ServiceNeeded)
{
if (ModelState.IsValid)
{
db.People.Add(person);
db.SaveChanges();
db.Entry(person).GetDatabaseValues();
foreach (string service in ServiceNeeded){
db.MyServices.Add( new MyService {ServiceName = service,
PersonId = person.Id})
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
Please note that I removed person.Id = Guid.NewGuid(); so that the Id
be generated by Database Identity and then I re-get the person.Id so that I create the MyService record.
and finally as #JamieD77 suggested, in your GET ActionResult:
public ActionResult Index()
{
var x = db.People.Include("ServiceNeeded").ToList();
return View(x);
}

Related

CRUD Edit operation using a ViewModel with unit testing

First of all: I found this post, but I don't fully understand it, so please don't lock it as a duplicate.
I'm trying to do the Edit operation with a ViewModel.
My problem is for some reason adds a new row to the table instead of editing it. It all used to work before I went for the testing.
I believe it's something silly I missed but I have no idea what I'm doing wrong.
I'm using code-first if it makes any difference
My ViewModel:
public class CreateViewModel
{
public string Title { get; set; }
[Display(Name = "Author")]
public int AuthorId { get; set; }
public DateTime? PublicationDate { get; set; }
public float? Edition { get; set; }
public SelectList Authors { get; set; }
}
My Controller functions:
// GET: Books/Edit/5
public ViewResult Edit(int? id)
{
if (id == null)
{
return View("Error");
}
Book book = db.Books.FirstOrDefault(a => a.Id == id);
var vm = new CreateViewModel()
{
AuthorId = book.AuthorId,
Authors = new SelectList(db.Authors, "Id", "Name"),
PublicationDate = book.PublicationDate,
Title = book.Title,
Edition = book.Edition
};
if (book == null)
{
return View("Error");
}
return View(vm);
}
// POST: Books/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(CreateViewModel vm)
{
if (ModelState.IsValid)
{
var repo = new EFLibraryRepository();
repo.Save(new Book(){
AuthorId = vm.AuthorId,
PublicationDate = vm.PublicationDate,
Title = vm.Title,
Edition = vm.Edition
});
return RedirectToAction("Index");
}
return View("Edit", vm);
}
My mock repository:
public class EFLibraryRepository : ILibraryRepository
{
AuthorAndBookDbModel db = new AuthorAndBookDbModel();
public IQueryable<Author> Authors { get { return db.Authors; } }
public IQueryable<Book> Books { get { return db.Books; } }
public void Delete(Book book)
{
db.Books.Remove(book);
db.SaveChanges();
}
public void Delete(Author author)
{
db.Authors.Remove(author);
db.SaveChanges();
}
public Book Save(Book book)
{
if (book.Id == 0)
{
db.Books.Add(book);
}
else
{
db.Entry(book).State = System.Data.Entity.EntityState.Modified;
}
db.SaveChanges();
return book;
}
public Author Save(Author author)
{
if (author.Id == 0)
{
db.Authors.Add(author);
}
else
{
db.Entry(author).State = System.Data.Entity.EntityState.Modified;
}
db.SaveChanges();
return author;
}
}
The ILibraryRepository:
public interface ILibraryRepository
{
IQueryable<Book> Books { get; }
IQueryable<Author> Authors { get; }
Book Save(Book book);
Author Save(Author author);
void Delete(Book book);
void Delete(Author author);
}
In your POST method, you never set the value of the Book's Id property so its always 0 (the default value for int) so in turn, you always execute the code to add a new Book.
You first need to include a property in your view model for the id so that its value will be bound in the POST method.
public class CreateViewModel
{
public int? Id { get; set; } // add this
....
Note that you do not need to include a hidden input for it in the view assuming your using the default routes (its value will be bound from the route value in the forms action attribute).
Then in the POST method, set the Id of the Book based on the view model
repo.Save(new Book() {
Id = vm.Id, // add
AuthorId = vm.AuthorId,
....
However, the correct approach when editing existing records is to get the original data model from the repository based on the Id and update its properties, for example
Book book = db.Books.FirstOrDefault(a => a.Id == vm.Id);
book.AuthorId = vm.AuthorId;
....
repo.Save(book);
rather than creating a new Book instance. Some of the benefits of this approach include
Your data models will often include properties that should not be in
the view (for example, properties to indicate the date a records was
created, and by who). Creating a new instance of the data model and
saving it means those properties would be overwritten and set to
their default values.
You can do concurrency checks, for example you can check the
TIMESTAMP values and if they are different, you know that another
user has modified the record in the meantime (and you might take a
different course of action rather than just overwriting the previous
uses changes)

How to map users identity and Auditable properties in view model

This is my view model.
public class ProductViewModel
{
public Guid Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public bool IsAvailable { get; set; }
}
When form is posted from client the form is submitted to this Controller
public async Task<IHttpActionResult> AddProduct(ProductViewModel productViewModel)
{
await ServiceInstances.PostAsync("product/add", productViewModel);
return Ok();
}
Then this controller submit the form to the API controller
Which is on my separate Project.
[HttpPost]
[Route("add")]
public IHttpActionResult AddProduct(ProductViewModel model)
{
_productService.AddProduct(model.UserServiceDetails());
return Ok();
}
Extension UserServiceDetails Where i get the Login User Info
public static UserServiceDetailModel<T> UserServiceDetails<T>(this T model)
{
var serviceRequestModel = new ServiceRequestModel<T>()
{
Model = model,
LoginInfo = HttpContext.Current.User.Identity.GetUserLoginInfo();
};
}
AddProductService:
public void AddProduct(UserServiceDetailModel<ProductViewModel> serviceRequestModel)
{
var repo = _genericUnitOfWork.GetRepository<Product, Guid>();
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
mapped.Id = Guid.NewGuid();
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
repo.Add(mapped);
_genericUnitOfWork.SaveChanges();
}
Now my question is Is there any way to assign the value to this field CreatedDate and CreatedById before posting it to service?
Reduce these logic to mapper:
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
Or is there any way that those field gets mapped to Product when
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
Sometime i may have the List<T> on view-model and there i have to add this field using the loop.
So this same mapping may get repeated over and over on Add Method Or Update.
In some entity i have to assign the ModifiedDate and ModifiedById also.
My Mapper Configuration:
public class ProductMapper : Profile
{
public ProductMapper()
{
CreateMap<ProductViewModel, Product>();
}
}
I cannot add the Enitity as IAuditableEntity and Overrride in ApplicationDbContext because my DbContext is in separate Project and i donot have access to Identity there.

Include inner item when return JSon

I have an Employee class, taken from a database. In that Employee class, I want to add a Manager object, which is also an Employee, based on the managerCode that is in the Employee database. See below for the class.
The managerCode is not defined as a key, . I don’t need recursion, i.e. I don’t need the Manager’s manager, etc. Just one level, the Employee and his manager.
Using .NET 4.5, c#, OData v4
I am using OData to send back the Employee, but the Manager part isn’t added in the response, even if it’s there in the object I try to send.
What am I missing? Something in the WebApiConfig?
Thanks
Employee class, first 4 fields are directly taken from database.
Class Employee
{
[Key]
public int id { get; set; }
public string employeeCode { get; set; }
public string employeeName { get; set; }
public string managerCode { get; set; }
[NotMapped]
public Employee Manager { get; set; }
}
Controller class. GetEmployeeById(id) will get Employee(s) with their Manager.
[HttpGet]
[EnableQuery]
[ODataRoute("employeeById(id={id})")]
public IHttpActionResult employeeById([FromODataUri]int id)
{
var sets = dbContext.GetEmployeeById(id);
if (!sets.Any())
return NotFound();
return this.CreateOKHttpActionResult(sets);
}
WebApiConfig
public static void Register(HttpConfiguration config)
{
config.MapODataServiceRoute("ODataRoute", "odata",GetEdmModel(),
new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.Namespace = "employee_odata";
builder.ContainerName = "employee_odataContainer";
builder.EntitySet<Employee>("Employee");
builder.EnableLowerCamelCase();
var unboundGetEmployee = builder.Function("employeeById");
unboundGetEmployee.Returns<Employee>();
unboundGetEmployee.Parameter<int>("id");
unboundGetEmployee.Namespace = "employee_odata.Functions";
return builder.GetEdmModel();
}
SOLUTION
Remove unboundGetEmployee from WebApiConfig (not needed).
Make Manager item in Employee class virtual, without the [NotMapped]:
public virtual Manager Manager { get; set; }
Controller:
[EnableQuery]
[ODataRoute("Employee({id})")]
public IHttpActionResult employeeById([FromODataUri]int id)
{
//handle data return normally...
//I have to detect $expand=Manager, to fetch the actual Manager object.
//otherwise it's null (not linked with primary key)
}
With these fee changes, $expand is working well.
You need to add $expand to show navigation property, like
localhost\odata\employee_odata.Functions.employeeById(id=1)?$expand=Manager
And for get employee by Id, I suggest you to use this method in controller:
[EnableQuery]
public IHttpActionResult Get(int id)
{
var sets = dbContext.GetEmployeeById(id);
if (!sets.Any())
return NotFound();
return this.CreateOKHttpActionResult(sets);
}
Then request like localhost\odata\Employee(1) can route to that method.

ASP.NET MVC How to get FilePath pass into PeopleDB?

I'm a newbie to ASP.NET using Entity Framework. I have different models for People, FileType and FilePath. I want to display the image by retrieving the file path from FilPath together with data like name, age, etc. in index view. I made it happen in Detail view, but in index view page I received error as "Value can not be null", which caused by the FilePath in PeopleDB is null.
Below is my code, please help. Thanks.
/Model/PeopleDB.cs
namespace MvcDemo.Models {
public class PeopleDB
{
public PeopleDB()
{
this.FilePaths = new HashSet<FilePath>();
}
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public string Interests { get; set; }
public ICollection<FilePath> FilePaths { get; set; }
}
public class PeopleDBContext : DbContext
{
public DbSet<FilePath> FilePaths { get; set; }
public DbSet<PeopleDB> People { get; set; }
}
}
/Model/FilePath.cs
namespace Assessment_HC.Models
{
public class FilePath
{
public int FilePathId {get;set;}
[StringLength(255)]
public string FileName {get;set;}
public FileType FileType {get;set;}
public int PersonID {get;set;}
public virtual PeopleDB Person {get;set;}
}
}
Moedel/FileType.cs
namespace Assessment_HC.Models
{
public enum FileType
{
Avatar = 1, Photo
}
}
Here is the controller for index view
//Get: /People/Index
public ActionResult Index()
{
return View(db.People.ToList());
}
In db.People.ToList(), People.FilePath view is null.
In the controller, the detail view is like this, from where I can get the image showing on detail page:
// GET: /People/Details
public ActionResult Details(int id = 0)
{
PeopleDB peopledb = db.People.Find(id);
PeopleDB people = db.People.Include(i => i.FilePaths).SingleOrDefault(i => i.ID == id);
if (peopledb == null)
{
return HttpNotFound();
}
return View(peopledb);
}
Thanks for your help. Let me know if you need more code.
Based on comments, It seems the only thing you should do is changing FilePaths property of your PeopleDB to be virtual to work with Lazy Loading (which is enabled by default):
public virtual ICollection<FilePath> FilePaths { get; set; }
Lazy Loading is enabled by default, and as stated in comments you didn't change it and there is nothing about Lazy Loading in your context constructor, So it seems the problem is in your FilePaths navigation property that is not virtual.
For index action:
return View(db.People.ToList());
For details action its better to do like:
var people = db.People.Where(x => x.ID == id).FirstOrDefault();
if (people == null)
{
return HttpNotFound();
}
return View(people );
But any way, If disable lazy Loading, you should use Include to include your navigation property in result. In this situation you can load data in your index action use:
db.People.Include(x => x.FilePaths).ToList()
or
//Remember to add using System.Data.Entity;
db.People.Include("FilePaths").ToList()
And to disable Lazy Loading you can
db.Configuration.LazyLoadingEnabled = true;
Or in the constructor of your context:
this.Configuration.LazyLoadingEnabled = false;
More information:
Loading Related Entities
Lazy loading is the process whereby an entity or collection of
entities is automatically loaded from the database the first time that
a property referring to the entity/entities is accessed. When using
POCO entity types, lazy loading is achieved by creating instances of
derived proxy types and then overriding virtual properties to add the
loading hook.
I've tested the code, the only one thing that you need is enabling Eager loading using Include method:
public ActionResult Index()
{
var _db = new ApplicationDbContext();
var model = _db.People.Include("FilePaths").ToList();
return View(model);
}
In this case all related file paths will be loaded.
You can also make FilePaths as virtual:
public virtual ICollection<FilePath> FilePaths { get; set; }
And change your query this way:
var model = _db.People.ToList();
In both cases, all related file paths will be loaded.

How to update a collection inside an entity within a post action in ASP.NET MVC5?

I have created an ASP.NET MVC5 sample project. I created my entities and from that, scaffolded the controllers for CRUD operations. I can only edit the POD members with the scaffolded code. I want to be able to add/remove related entities.
With my current code, when I click save there is no error but no related entities are modified (POD data is modified though). For example, if I wanted to remove all players from the account, they aren't removed. What am I doing wrong?
How can I remove/add related entities and push those changes to the database?
Here is the form:
Here is the action to update the entity:
public async Task<ActionResult> Edit([Bind(Include = "Account,Account.AccountModelId,Account.Name,Account.CreatedDate,SelectedPlayers")] AccountViewModel_Form vm){
if (ModelState.IsValid){
if (vm.SelectedPlayers != null){
vm.Account.PlayerModels = db.PlayerModels.Where(p => p.AccountModel.AccountModelId == vm.Account.AccountModelId).ToList();
foreach (var player in vm.Account.PlayerModels){
player.AccountModel = null;
db.Entry(player).State = EntityState.Modified;
}
vm.Account.PlayerModels.Clear();
foreach (var player_id in vm.SelectedPlayers){
var player = db.PlayerModels.Where(p => p.PlayerModelId == player_id).First();
vm.Account.PlayerModels.Add(player);
db.Entry(player).State = EntityState.Modified;
}
}
db.Entry(vm.Account).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(vm);
}
Here are the models:
public class AccountViewModel_Form{
public AccountModel Account { get; set; }
public HashSet<Int32> SelectedPlayers { get; set; }
public virtual List<PlayerModel> PlayersList { get; set; }
}
public class AccountModel{
public AccountModel(){
PlayerModels = new HashSet<PlayerModel>();
}
public Int32 AccountModelId { get; set; }
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
public virtual ICollection<PlayerModel> PlayerModels { get; set; }
}
public class PlayerModel{
public Int32 PlayerModelId { get; set; }
public float Gold { get; set; }
public string Name { get; set; }
public virtual AccountModel AccountModel { get; set; }
}
I'm basically lost. I can't find any examples in how to update related data. Could someone point me in the right direction?
I come from Symfony (PHP Framework) background. I thought it would be easier but I have been having problems.
Basically I was missing the Attach function and that I had to force the load on the collection to make it work.
I found how to attach a non-attached entity here: Model binding in the controller when form is posted - navigation properties are not loaded automatically
When you post the data, the entity is not attached to the context, and when you try to save changes to a complex entity, the context makes a mess.
The code is a little different because I was trying to make it work at home. But it is essentially the same models.
public ActionResult Edit(AccountEditViewModel vm)
{
if (ModelState.IsValid)
{
//I was missing these 2 important lines...
db.Accounts.Attach(vm.Account);
db.Entry(vm.Account).Collection(a => a.Players).Load();
if (vm.SelectedPlayers != null)
{
foreach (var player in vm.Account.Players.ToList())
{
if (vm.SelectedPlayers.Contains(player.Id) == false)
{
player.Account = null;
vm.Account.Players.Remove(player);
db.Entry(player).State = EntityState.Modified;
vm.SelectedPlayers.Remove(player.Id);
}
}
foreach (var player_id in vm.SelectedPlayers)
{
var player = db.Players.Where(p => p.Id == player_id).First();
player.Account = vm.Account;
vm.Account.Players.Add(player);
db.Entry(player).State = EntityState.Modified;
}
}else
{
vm.Account.Players.Clear();
}
db.Entry(vm.Account).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vm);
}

Categories