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.
Related
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
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);
}
I have asp net Web Api. In get all method I use EF Core IQueryable to retrieve data from DB. Then use project to for map Domain class to response class. Then send this queryable in LoadAsync method from DevExtreme library to search/filter/pagination.
public override async Task HandleAsync(DataSourceLoadOptions req, CancellationToken ct)
{
var feature = this.CreateFeature(_mapper, () => new GetReservationsQueryFeature());
Result<IQueryable<Domain.Reservation>> result = await _mediator.SendRequestAsync(feature, ct);
if (result.IsSuccess)
{
var cultureInfo = new CultureInfo(feature.Language);
var queryResult = await DataSourceLoader.LoadAsync(result.Payload.ProjectTo<GetReservationResponse>(_mapper.ConfigurationProvider), req, ct);
//do some localization
await SendOkAsync(queryResult, cancellation: ct);
}
else
{
await this.SendIssueAsync(result.Issue, ct);
}
}
This is my mapping Profile.
public class GetReservationMappingProfile : Profile
{
public GetReservationMappingProfile()
{
CreateMap<Core.ReservationCore.Domain.Reservation, GetReservationResponse>()
.ForMember(dest => dest.Uid, opt => opt.MapFrom(src => src.Uid))
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
//...
}
}
Now I need localize Status property based on cultureInfo. Status is enum property and I have .resx to localize this enum. How can I do it? Without any extra for/foreach loop on queryResult?
Thanks.
Im looking for a method to project included objects in EntityFramework Core in a way we can project objects (Select function):
[HttpGet]
public async Task<IActionResult> GetBooks()
{
return Json(new { data = await _db.Books.Select(b => _mapper.Map<BookDto>(b)).ToListAsync() });
}
Here is my code where I tried to project included objects but its not valid:
[HttpGet]
public async Task<JsonResult> GetMessagesAsync()
{
var msgs = await _db.Messages.OrderBy(m => m.Sent).Include(m => _mapper.Map<AppUserDto>(m.AppUser)).ToListAsync();
return Json(new { data = msgs });
}
EDIT1:
I want to project included AppUser objects into AppUserDto.
EDIT2:
public MappingProfile()
{
CreateMap<Book, BookDto>();
CreateMap<BookDto, Book>();
CreateMap<Client, ClientDto>();
CreateMap<ClientDto, Client>();
CreateMap<Reservation, ReservationDto>();
CreateMap<ReservationDto, Reservation>();
CreateMap<AppUser, AppUserDto>();
CreateMap<AppUserDto, AppUser>();
}
If you have your mapper set up correctly you should be able to just do the following:
var msgs = await _db.Messages
.OrderBy(m => m.Sent)
.Include(m => m.AppUser)
.Select(m => _mapper.Map<MessageDto>(m))
.ToListAsync();
So your MessageDto will need to have AppUserDto AppUser property among others.
Is it possible to unit test this?
public class MyRepository<T> where T : IdentityUser, new()
{
public async Task UpdateAsync(T user)
{
_context.Entry(user).State = EntityState.Modified;
_context.Entry(user).Property("UserName").IsModified = false;
await _context.SaveChangesAsync();
}
}
The [TestInitialize] adds 1 user to the repository
_user = new IdentityUser { Id = "70a038cdde40" };
IDbSet<IdentityUser> users = new FakeDbSet<IdentityUser> { _user };
var dbContext = new Mock<MyDbContext<IdentityUser>>();
dbContext.Setup(x => x.Users).Returns(() => users);
_repository = new MyRepository<IdentityUser>(dbContext.Object);
and I'm trying to test with this
private MyRepository<IdentityUser> _repository;
[TestMethod]
public async Task UpdateUser_Success2()
{
var user = await _repository.FindByIdAsync("70a038cdde40");
Assert.IsFalse(user.EmailConfirmed, "User.EmailConfirmed is True");
user.EmailConfirmed = true;
await _repository.UpdateAsync(user);
(...)
}
But it dies on 1st line of UpdateAsync. Is the test that is wrong or the UpdateAsync implementation? Is there any way I can test it?
Edit
I added as suggested by Belogix
dbContext.Setup(x => x.Entry(It.IsAny<IdentityUser>()))
.Returns(() => dbContext.Object.Entry(_user));
That gets me closer, I think, but still have the non-virtual error: Invalid setup on a non-virtual member: x => x.Entry(It.IsAny())
Best quote ever: "All problems in computer science can be solved by another level of indirection" - Butler Lampson.
It looks like this can't be tested directly without adding some additional abstraction. I had to refactor my UpdateAsync method this way
public async Task UpdateAsync(T user)
{
SetEntityStateModified(user);
SetPropertyIsModified(user);
await _context.SaveChangesAsync();
}
public virtual void SetPropertyIsModified(T user)
{
_context.Entry(user).Property("UserName").IsModified = false;
}
public virtual void SetEntityStateModified(T user)
{
_context.Entry(user).State = EntityState.Modified;
}
And then update my test code in the Initialize
_repository = new Mock<MyRepository<IdentityUser>>(dbContext.Object);
_repository.Setup(x => x.SetEntityStateModified(It.IsAny<IdentityUser>()));
_repository.Setup(x => x.SetPropertyIsModified(It.IsAny<IdentityUser>()));
My test then finally passes
[TestMethod]
public async Task can_update_user_details()
{
//Arrange
var user = await _repository.Object.FindByIdAsync("70a038cdde40");
Assert.IsFalse(user.EmailConfirmed, "User.EmailConfirmed is True");
//Act
user.EmailConfirmed = true;
await _repository.Object.UpdateAsync(user);
var newUser = await _repository.Object.FindByIdAsync("70a038cdde40");
//Assert
Assert.IsTrue(newUser.EmailConfirmed, "User.EmailConfirmed is False");
}
The ChangeTracker in dbContext tracks changes and hold the entities that are changed. So you can assert the changed entity is among them.
Assert.IsTrue(dbContext.Object.ChangeTracker.Entries().Any(entry =>
entry.State == EntityState.Modified &&
entry.Entity is IdentityUser &&
(entry.Entity as IdentityUser).Id == users[0].Id // Here you can check if it is actually the same user
));
For the property it would be something like this:
Assert.IsTrue(_context.Object.ChangeTracker.Entries().Any(entry =>
entry.Property("UserName").IsModified == false &&
entry.Entity is IdentityUser &&
(entry.Entity as IdentityUser).Id == users[0].Id // Here you can check if it is actually the same user
));
It looks like you have not stubbed your context correctly... I am not at a computer with Visual Studio so here is some pseudo code that should demonstrate what I mean. Replace the IsAnything with either your mocking frameworks way of ignoring argument or actually the user if you want to handle different responses.
// Existing context initialisation...
var dbContext = new Mock<MyDbContext<IdentityUser>>();
dbContext.Setup(x => x.Users).Returns(() => users);
// NEW: Mock what / how Entry is going to return when called (i.e. return a user)
dbContext.Setup(x => x.Entry(IsAnything)).Returns(() => users[0]);