I'm using entity framework core with ASP.NET Core, code first.
In my app I have invoices, with the typical InvoiceHeader -> InvoiceLine relationship. The InvoiceLine entities have a LineAmount field, which I want to sum and display on the InvoiceHeader when displayed as a list (so I can see the invoice total when viewing the list of invoices).
I'm guessing I'll need to add a TotalAmount property to the InvoiceHeader entity, with the annotation [NotMapped]. But how to most efficiently populate it?
At the moment my InvoiceHeaderController.Index() is:
// GET: InvoiceHeaders
public async Task<IActionResult> Index()
{
ApplicationUser appUser = ConstantData.GetApplicationUser(_context, _userManager.GetUserId(User));
var applicationDbContext = _context.InvoiceHeader.Include(i => i.Customer).Include(i => i.CustomerBranch)
.Where(i => i.CustomerID == appUser.CustomerID);
return View(await applicationDbContext.ToListAsync());
}
Can anyone tell me what the most efficient way is to calculate (sum) this TotalAmount property?
Thanks.
Selecting sum as separate field you need to create new model class as shown below
public class InvoiceHeaderModel{
public InvoiceHeader invoiceHeader{get;set;}
public decimal TotalAmount {get;set;}
}
and make change in action as
// GET: InvoiceHeaders
public async Task<IActionResult> Index()
{
ApplicationUser appUser = ConstantData.GetApplicationUser(_context, _userManager.GetUserId(User));
var applicationDbContext =await _context.InvoiceHeader.Where(i =>i.CustomerID == appUser.CustomerID).ToListAsync();
var data = applicationDbContext.Aggregate( new List<InvoiceHeaderModel>(),(invoiceHeaderModellist, it)=>{ invoiceHeaderModellist.Add(new InvoiceHeaderModel(){ InvoiceHeader =it,TotalAmount = it.InvoiceLine.Sum(t=>t.LineAmount)}); return invoiceHeaderModellist;});
return View(data);
}
In this action i don't think you required to include 'Include(i => i.Customer).Include(i => i.CustomerBranch)' if required you can add before where closure.
I managed to work it out. Saneesh's suggestion was close, but not quite what I wanted.
The code I ended up using is:
// GET: InvoiceHeaders
public async Task<IActionResult> Index()
{
ApplicationUser appUser = ConstantData.GetApplicationUser(_context, _userManager.GetUserId(User));
var applicationDbContext = _context.InvoiceHeader.Include(i => i.Customer).Include(i => i.CustomerBranch)
.Where(i => i.CustomerID == appUser.CustomerID)
.Select(i => new InvoiceListViewModel
{
invoiceHeader = i,
TotalAmount = i.InvoiceLines.Sum(t => t.LineAmount)
});
return View(await applicationDbContext.ToListAsync());
}
Thanks for your help Saneesh.
Related
I have some global information (Company name, street, phone, …) stored in a table "CompInfo".
This table has only one entry and has no relationship to other tables.
Now I need this information in a view to generate, i.e. offers.
I tried to add this data in the controller, but I don't know how I can send this info to my view.
Here is my controller:
public async Task<IActionResult> Offer(int qry_offer)
{
ViewData["Message"] = "Offer";
var x412Context = _context.Offer
.Where(m => m.Id == qry_offer);
x412Context = x412Context
.Include(o => o.Kunde)
.Include(o => o.Mandant)
.Include(m => m.OfferPos)
.ThenInclude(m => m.Ust)
;
return View(await x412Context.ToListAsync());
}
First I add the following code
var Comp_Info = _context.CompInfo
.Where(m => m.Id == 1);
With breakpoints and debugging I see that Comp_Info has the information I require.
But I don´t know how to get this info to my view.
I tried
ViewData["street"] = Comp_Info.street;
But this doesn't work.
Does anyone have an idea how I can transmit the data to the view?
You can return a viewmodel in your Offer method that looks like this :
public class FooVM
{
public List<Offer> Offers {get; set;}
public CompInfo CompInfo{get; set;}
}
and in your controller initilize the FooVm like this:
var vm = new FooVM
{
Offers = await x412Context.ToListAsync(),
CompInfo = Comp_Info
};
and then in your Offer Method you return View(vm);
then your Offer.cshtml will look something like :
#model FooVm;
// some html code
#foreach (var offer in Model.Offers)
{
// Do something
}
<span>#Model.CompInfo.Street</span>
you can implement another class which should have members as the list (of the same type x412Context.ToListAsync()) and another member which will have the same datatype as Comp_Info.street.
then you can return the same class object to the view like return View(newClassObj);, you can access it as we do to access model class members in view.
I started a new MVC app using .NET Core 3. I have three DbContext files that use three different databases: ComplaintDbContext for my main application, IdentityCoreDbContext for Identity users, and EmployeeDbContext for my employee database.
In my app I have a repository class called ComplaintRepository and the constructor looks like this:
public ComplaintRepository(ComplaintDbContext context,
EmployeeDbContext employeeContext)
{
_context = context;
_employeeContext = employeeContext;
}
In my ComplaintController I need to get data from both databases. I can get my data from my Complaint database, but once I call my Action that gets data from my Employee database I get the error:
Cannot use multiple DbContext instances within a single query execution. Ensure the query uses a single context instance.
I tried something like this:
public class FrameworkContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
}
public class ExtendedContext : FrameworkContext
{
public DbSet<Order> Orders { get; set; }
}
I cannot get it working. Any help would be appreciated. Thanks!
Edit:
I started a new repository called EmployeeRepository to separate concerns. Here is my Action that is giving me problems:
public IEnumerable<ApplicationUser> GetWorkerList()
{
var employees = _employeeRepository.GetEmployeesByUnit(22);
//Get ApplicationUsers where user exists in Employees list
IEnumerable<ApplicationUser> userList = _userManager.Users
.Where(emp => employees.Any(e => emp.EmployeeID == e.EmployeeId)).OrderBy(e => e.LastName);
return userList;
}
My Employee database and my Identity database both share a column called EmployeeId.
When I tried changing it to use ToList() I started getting a different error:
InvalidOperationException: The LINQ expression 'Where( source: DbSet, predicate: (a) => Any( source: (Unhandled parameter: __employees_0), predicate: (e) => a.EmployeeID == e.EmployeeId))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
Edit:
I used Tao Zhou's recommendation of using ToList() and I was able to get it working. I replaced IEnumerable in my repository to use this:
public List<TblEmployee> GetEmployeesByUnit(int unitId)
{
var emp = _context.TblEmployee.Where(e => e.UnitId == unitId &&
e.TermDate == null)
.OrderBy(e => e.LastName).ToList();
return emp;
}
In my controller I basically did the same and I now have this:
public List<ApplicationUser> GetWorkerList()
{
var employees = _employeeRepository.GetEmployeesByUnit(22);
List<ApplicationUser> userList = new List<ApplicationUser>();
//Get ApplicationUsers where user exists in Employees list
foreach (TblEmployee emp in employees)
{
ApplicationUser user = _userManager.Users
.Where(e => e.EmployeeID == emp.EmployeeId).FirstOrDefault();
userList.Add(user);
}
return userList;
}
I would like to use LINQ instead of the foreach loop.
I have also faced the same problem, from what I could tell, you cannot use different DbContext for same query. Just make sure you used same DbContext object for same query.
Since you are only using the EmployeeId in the Any method comparison, you could use Contains
var employees = _employeeRepository.GetEmployeesByUnit(22);
var employeeIds = employees.Select(x => x.EmployeeId).ToHashSet();
//Get ApplicationUsers where user exists in Employees list
IEnumerable<ApplicationUser> userList = _userManager.Users
.Where(emp => employeeIds.Contains(emp.EmployeeID)).OrderBy(e => e.LastName);
return userList;
// or return userList.ToList();
I have 2 tables:
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
return userIdentity;
}
[ForeignKey("ApplicationUserId")]
public virtual ICollection<ApplicationUserNotification> ApplicationUserNotifications { get; set; }
}
public class ApplicationUserNotification
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[StringLength(64)]
public string ApplicationUserId { get; set; }
[ForeignKey("ApplicationUserId")]
public virtual ApplicationUser ApplicationUser { get; set; }
}
I want to show the first 10 notifications for the user and use the same view to load the next 10 notifications when the user scrolls down.
public ActionResult UserNotications(int skip = 0, int take = 10)
{
var db = new ApplicationDbContext();
var user = db.Users.FirstOrDefault(a => a.Id == User.Identity.GetUserId());
var list = user.ApplicationUserNotifications.Skip(skip).Take(take).ToList();
return View(list);
}
But is says:
'ICollection' does not contain a definition for 'Skip' and the best extension method overload 'Queryable.Skip(IQueryable, int)' requires a receiver of type 'IQueryable'
What's the best way to solve this without changing the code to directly query from the usernotification table? The actual usage of this code uses the user class, so it would be better if I can access the usernotifications through the user object.
It's weird I would have expected there would be a lot of questions about this, but searchengines seem to skip the word "skip" so I can't find anything about this.
=====================
Update for mick, I use it in a view and I have a static object where I can access the user object called "Current":
#{
Layout = null;
var skip = Convert.ToInt32(Request["Skip"] ?? "0");
var take = Convert.ToInt32(Request["Take"] ?? "10");
}
#foreach (var item in Current.User.UserNotifications.Skip(skip).Take(take))
{
#Html.Partial(Enum.GetName(typeof(Kiteshoot.Enums.NotificationType), item.NotificationType), item)
}
<div class="UserNotificationsEndOfList" data-index="#(skip + take)"></div>
I really dont know why but the error has magicly disapeared, I've been coding too long I think, sorry. But as Stijn called, this won't skip the query, but just the in-memory list, so back to square one.
If you want to use skip and take to load only a portion of the notifications from the database use...
DbSet<ApplicationUserNotification>()
.Where(n => n.ApplicationUserId == User.Identity.GetUserId())
.Skip(() => skip)
.Take(() => take)
.ToList()
If you're wondering why .Skip(() => skip) instead of .Skip(skip) try out both and look at the SQL generated using SQL Profiler or some other query monitoring tool. You'll see .Skip(() => skip) results in a parameterised query, whereas .Skip(skip).Take(take) will bake the values for skip and take into the query text, which will reduce hits on the SQL plan cache, resulting in poorer performance when paging.
Let say that i have a Books table and a Chapters. Those 2 tables are related in one-to-many relationship. And I want to make a website with ASP.NET MVC5 to display the Book Name and its Chapters. So, I can perform atleast 2 query scenarios to display needed data:
1. Query all data in a Controller
BookViewModel.cs
class BookViewModel{
List<Books> books{set;get;}
List<ChapterList> chapterLists{set;get;}
}
ChapterList.cs
class ChapterList{
List<Chapters> chapters{set;get;}
}
Book.cs (the Controller)
public ActionResult Index()
{
List<Books> books = db.Books.ToList()
List<ChapterList> chapterList = new List<ChapterList>();
foreach(Books m in books)
{
chapterLists.Add(db.Chapters.Where(m => m.book_id = m.id).ToList());
}
BookViewModel bvm = new BookViewModel();
bvm.books = books;
bvm.chapterLists = chapterLists
return view();
}
Index.cshtml (the View) --I think I'll skip this code, because I assume you know how to display it
2. Query in Razor
Book.cs (the Controller)
public ActionResult Index()
{
List<Books> books = db.Books.ToList()
return view(books);
}
Index.cshtml (the View)
#foreach(Books book in model)
{
#book.Name
foreach(Chapters chapter in book.Chapters.ToList())
{
#chapter.Name
}
}
The second query scenario is the easiest for me, but I am not sure which one is faster. So, my question is which one is the best performance (or the fastest displaying the view) if there are million Books data and the databases is in a different server? And is there any other way to display the view faster?
You can use Include method on DbSet.
I tried to simpler and faster your query. You can check below.
In controller
public class HomeController : Controller
{
public ActionResult Index()
{
var db = new YourDbContext();
var booksWithChapterList =
db.Books
.Include(s => s.chapterLists) //I add chapterList to query, so I don't fetch another query.
.Select(s => new BookDto// I convert them to smaller data transfer object.
{
Name = s.Name,
Chapters = s.chapterLists.Select(w => new BookChapterDto
{
Name = w.Name
}).ToList()
})
.ToList();
return View(booksWithChapterList);
}
}
You can find used classes here:
public class BookDto
{
public string Name { get; set; }
public IList<BookChapterDto> Chapters { get; set; }
}
public class BookChapterDto
{
public string Name { get; set; }
}
Hope this helps.
Yes, there are other faster way to display data in entity Framework, one of them is by using LINQ Queries.
In Your Controller you could do something like this:
public ActionResult Index()
{
List<Books> books = db.Books.ToList()
List<ChapterList> chapterList = (from a in db.Books
join b in db.Charpters on a.id equals b.BookId
//where a.id==3 you can also query for specific book
select new ChapterList
{
chapter=b.chapter
}).ToList();
BookViewModel bvm = new BookViewModel();
bvm.books = books;
bvm.chapterLists = chapterLists
return view();
}
This will be faster than looping through each item using foreach.
It is always a good practice to keep all your logic in controller level instead of view.
public ActionResult Users()
{
var user = _userRepository.Find(x => x.Id == 1).FirstOrDefault();
return View(user);
}
public ActionResult CashAccountInfo(int id)
{
var result = _cashAccountRepository.Find(x => x.Id == id).FirstOrDefault();
return View(result);
}
When i debug Users() i get
where i have AppUser
but when i debug CashAccountInfo(int id)
i get
without AppUser
Find method from Repository
public virtual IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
{
return DbContext.Set<TEntity>().AsExpandable().Where(predicate).ToList();
}
Please help why in one query get entity but in another cannot get
Assuming you are using entity framework, this is a Lazy Loading issue. Maybe add an Include to your calls when using your context.
For example:
context.Include(x => x.AppUser).FirstOrDefault(x => x.Id == id);
If this isn't the case or you're not using entity framework then you need to show the code for your repository methods.