C#, ASP.NET MVC, Entity Framework : user.cart items are null - c#

I am using ASP.NET MVC with Entity Framework. I have a class that handles SQL called DataLayer.
There is a User object in the DataLayer.
I built a function that includes all the cart's items and returns the user by id, then makes the user in the datalayer as the found user (so it could log in).
The function code is:
public User userWithInclude(int? userId)
{
List<User> users = Users.Include(x => x.Cart)
.Include(x => x.Cart.shakes)
.Include(x => x.Cart.equipmentList)
.Include(x => x.orders)
.Include(m => m.menus).ToList();
User user = users.ToList().Find(x => x.ID == userId);
return user;
}
Here's a snippet of the function the logs in :
public IActionResult LogInUser(User vm)
{
User user = DataLayer.Data.Users.ToList()
.Find(x => x.userName == vm.userName && x.password == vm.password);
if (user != null)
{
User user2 = DataLayer.Data.userWithInclude(user.ID);
DataLayer.User = user2;
if (user2.Cart == null)
{
user2.Cart = new Cart();
}
DataLayer.Data.SaveChanges();
return RedirectToAction("Index");
}
}
When I debug before the last line of the log in when I hover over the user object the cart's items all the vars exist but the object inside is null.
In the SQL Server database, the data exists with all the details.
How could it be?

You can rewrite the get user class like this.
public User GetUserWithInclude(int? userId)
{
return DataLayer.Data.Users.Include(x => x.Cart)
.Include(x => x.Cart.shakes)
.Include(x => x.Cart.equipmentList)
.Include(x => x.orders)
.Include(m => m.menus)
.FirstOrDefault(x => x.ID == userId);
}
Then your login actionresult can look like this
public IActionResult LogInUser(User vm)
{
if(Login(vm.userName, vm.password)){
return RedirectToAction("Index");
}
// handle login failure
}
Make a seperate loginMethod
public bool Login(string userName, string password)
{
User user = DataLayer.Data.Users.FirstOrDefault(x => x.userName == userName && x.password == password);
if (user != null)
{
User user2 = DataLayer.Data.GetUserWithInclude(user.ID);
DataLayer.User = user2;
if (user2.Cart == null)
{
user2.Cart = new Cart();
}
DataLayer.Data.SaveChanges();
return true;
}
return false;
}
If this doesn't work you can try and get the cart after the user and debug if that's null, but I don't know enough about your foreign keys and relations etc to help you with that

I think you must use ThenInclude() method. You can get more detailed information from from here
For example:
public User GetUserWithInclude(int? userId)
{
return DataLayer.Data.Users.Include(x => x.Cart)
.ThenInclude(m=>m.shakes)
.Include(x=>x.Cart)
.ThenInclude(m => m.equipmentlist)
.Include(m=>m.Orders)
.Include(m => m.menus)
.FirstOrDefault(x => x.ID == userId);
}

Related

The name 'Ok' does not exist in the current context, Razor Code behind

In my code behind I have the following and I'm getting the error 'Ok' does not exist in current context. I'm trying to populate a form with textboxes.
public async Task<IActionResult> OnGetDetails(string custName)
{
var x = _context.Customer
.Join(_context.Sales,
x => x.CustId,
c => c.CustId,
(x,c) => new
{
customerName = x.Name,
address = x.Address,
sale = x.SaleDate
}).ToListArraySync();
return Ok(await q.ToListArraySync()); //causes error 'Ok' doesn't exist in current context
}
I need to prefill the form on the page out with the data. I'm able to get the data on the default OnGet(), however, I need to join two tables in this handler
You can "manually" return OkObjectResult or JsonResult. ControllerBase.Ok is just a convenience method that actually returns an OkObjectResult
public async Task<IActionResult> OnGetDetails(string custName)
{
// ...
return new OkObjectResult(await q.ToListArraySync());
// or
return new JsonResult(await q.ToListArraySync());
}
I got it working.
I added a FK to the table and rebuilt the models and then this:
public async Task<IActionResult> OnGetDetails(string custName)
{
var x = _context.Customer
.Join(_context.Sales,
x => x.CustId,
c => c.CustId,
(x,c) => new
{
customerName = x.Name,
address = x.Address,
sale = x.SaleDate
}).ToListArraySync();
return Ok(await q.ToListArraySync());
}
became this:
public async Task<IActionResult> OnGetDetails(string custName)
{
var x = _context.Customer
.Where(c => c.CustomerName == custName)
.Include(x => x.Sales).ToListArraySync()
}
and now I can see the data from both tables on my view

ViewDataDictionary Model Type Exception

I am running an App with two main tables - TJob & TJobHistory. The TJobHistory table serves as an audit table to any changes made in the TJob table. I am trying to change my 'details' view so it will display the TJobHistory of the specific TJob entry that the user clicks on in the homepage. I am running into the following exception when I try to load the details page for any TJobs entry:
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'Planner_App.Models.TJob', but this ViewDataDictionary instance requires a model item of type 'Planner_App.Models.TJobHistory'.
Controller Code
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
//Foreign key pulls//
var TJob = await _context.TJob
.Include(t => t.intCustomer)
.Include(t => t.intDeveloper)
.Include(t => t.intJobStatus)
.Include(t => t.intJobType)
.FirstOrDefaultAsync(m => m.intJobId == id);
if (TJob == null)
{
return NotFound();
}
return View(TJob);
}
View Code
#model Planner_App.Models.TJobHistory
#{
ViewData["Title"] = "Details";
}
I am still very new to MVC so I apologize if this isn't helpful - but I can't seem to figure out what I'm doing wrong here. If anyone could point me in the right direction it would be much appreciated!
you have to fix you view model
#model Planner_App.Models.TJob
or if you really need to use TJobHistory and it is possible to convert TJob to TJobHistory you can try something like this
var model = await _context.TJob
.Include(t => t.intCustomer)
.Include(t => t.intDeveloper)
.Include(t => t.intJobStatus)
.Include(t => t.intJobType)
.Where(m => m.intJobId == id)
Select(i=> new TJobHistory
{
Id=i.Id,
... TJobHistory properties
}).FirstOrDefaultAsync();
if model == null)
{
return NotFound();
}
return View(model);
or if TJobHistory is a copy of TJob maybe if would be enough just replace _context.TJob with _context.TJobHistory
var tJobHistory = await _context.TJobHistory
....

How to redirect from one Razor Page to another

I have an Page called Page1.
Inside of OnGet(string parameter1) I check a parameter, and in some case want to route the user to another Page.
The Page is located here:
Pages/App/App.cshtml
I have tried this:
this.RedirectToPage("/App/App");//
But the user does not get redirected. It just shows the same page as expected if the redirect was not there. I want them to see the App page.
So how do I redirect to the App page?
This is what worked:
public async Task<IActionResult> OnGet(string web_registration_key)
{
//Check parameter here
if(doRedirect)
{
return RedirectToPage("/App/App")
}
}
return null;
}
The return null seems odd, but not sure what else to return.
I used the return Page();
Here is an example from on of my projects:
public IActionResult OnGet(int id)
{
var MenuItemFromDb = _db.MenuItem.Include(m => m.CategoryType).Include(m => m.FoodType)
.Where(x => x.Id == id).FirstOrDefault();
if (MenuItemFromDb == null)
{
return RedirectToPage("./Index");
}
else
{
ShowCart(id);
return Page();
}
}
private void ShowCart(int id)
{
var MenuItemFromDb = _db.MenuItem.Include(m => m.CategoryType).Include(m => m.FoodType)
.Where(x => x.Id == id).FirstOrDefault();
CartObj = new ShoppingCart()
{
MenuItemId = MenuItemFromDb.Id,
MenuItem = MenuItemFromDb
};
}

Is conditional includes depending on the current page a good practise?

In my website, the current user retrieved in each page_load has lots of dependencies (13 in total) :
public User Get(Guid userId)
{
MyEntities entities = _contextManager.Context;
User user = entities.Users
.Include(x => x.Something1)
.Include(x => x.Something2)
.Include(x => x.Something3)
.Include(x => x.Something4)
.Include(x => x.Something5)
.Include(x => x.Something6)
.Include(x => x.Something7)
.Include(x => x.Something8)
.Include(x => x.Something9)
.Include(x => x.Something10)
.Include(x => x.Something11)
.Include(x => x.Something12)
.Include(x => x.Something13.Select(s => s.Something14))
.SingleOrDefault(u => (u.Id == userId));
return user;
}
But it takes sooo long time that it is just no possible to keep it like that.
However, I don't need all those related objects in every single page.
Consequently, I thought I could do something like :
public partial class MyPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
App.GetCurrentUser(this);
}
}
And in GetCurrentUser :
public static void GetCurrentUser(System.Web.UI.Page page)
{
// Here only load data required by the current page
}
Is this a bad practise ?
If it is, is there any proper solution to make a query with lots of includes speeder ?
Thanks a lot !
EDIT
Here is the current App.GetCurrentUser :
public static User GetCurrentUser()
{
using (UnitOfWork uow = new UnitOfWork())
{
if (Membership.GetUser() != null)
{
UserRepo userRepo = new UserRepo();
Guid guid = (Guid)Membership.GetUser().ProviderUserKey;
User user = userRepo.Get(guid); // the function with the 13 includes
return user;
}
}
}
Since you have so little work involved in this query being duplicated between your various pages, there's simply no need to generalize the query such that each page can call this one method. Just have each page perform its own query, performing the includes that it needs.

The ObjectStateManager cannot track multiple objects with the same key

I'm aware many questions like this one have already been asked, but I just can't seem to understannd what is wrong. This is my code:
[HttpGet]
public ViewResult Edit(int id)
{
User user = userRepository.GetAll().FirstOrDefault(x => x.ID == id);
return View("Edit", user);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(User user)
{
if (ModelState.IsValid)
{
user.Password = HashHelper.GetHash(user.Password);
if (user.ID == 0) // new user
{
User testUser = userRepository.GetAll().FirstOrDefault(x => x.Name.Equals(user.Name));
if (testUser == null)
userRepository.AddEntity(user);
else
{
ModelState.AddModelError("", "Deze gebruikersnaam bestaat al");
return View(user);
}
}
else // edit existing user
{
User tempUser = userRepository.GetAll().First(x => x.ID == user.ID);
if (!user.Name.Equals(tempUser.Name))
{
// naam werd aangepast
int count = userRepository.GetAll().Count(x => x.Name.Equals(user.Name));
if (count > 0)
{
ModelState.AddModelError("", "Deze gebruikersnaam bestaat al");
return View(user);
}
}
userRepository.UpdateEntity(user);
}
userRepository.SaveChanges();
return RedirectToAction("Index");
}
else
{
return View(user);
}
}
UpdateEntity:
public void UpdateEntity(T entity)
{
var entry = context.Entry(entity);
if (entry.State == EntityState.Detached)
context.Set<T>().Attach(entity);
context.Entry<T>(entity).State = EntityState.Modified;
}
This results in this error:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
I don't get it. Why doesn't this work and how can I fix it?
there are multiple problems;
Do not call GetAll() if you want to fetch a single entity, what if you have thousands of entities in the database. Just implement a SingleOrDefault in the repository.
Use :
User testUser = userRepository.FirstOrDefault(x => x.Name.Equals(user.Name));
instead of :
User testUser = userRepository.GetAll().FirstOrDefault(x => x.Name.Equals(user.Name));
After fetching an existing user from database, just update this item using new values instead of trying to persist the one returned from page;
use
User tempUser = userRepository.GetAll().First(x => x.ID == user.ID);
tempUser.UserName = user.UserName;
....
SaveChanges();
instead of trying to persist user retrieved from page.
you need to decide the key of your entity; is it name, is it Id or both.

Categories

Resources