I'm loading an IQueryable of books from the Books table and there's another table named BookLoans. I have a DateTime? property in my ViewModel named Availability. I'm trying to figure out the best way to basically go out during my select statement and see if the current book's BookID shows up in a record within the BookLoans table and if a DateTime? variable named ReturnedWhen is also NULL in that record. I'm basically trying to mark the book as available or checked outed when I populate the the IQueryable.
It's been suggested that I try to do this all as one LINQ statement but I'm trying to figure out if it would be easier to just populate books like the following and then run it through a foreach loop? It's also been suggested that a Join would work but really I'm just trying to figure out the best method and then how to go about actually doing it.
var books =
_context.Books
.Select(r => new BookIndexViewModel
{
BookID = r.BookID,
Title = r.Title,
Author = r.Author,
ISBN = r.ISBN,
GenreName = r.Genre.Name
}).ToList();
Book Entity:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Open_School_Library.Data.Entities
{
public class Book
{
public int BookID { get; set; }
public string Title { get; set; }
public string SubTitle { get; set; }
public string Author { get; set; }
public int GenreID { get; set; }
public int DeweyID { get; set; }
public int ISBN { get; set; }
public Genre Genre { get; set; }
public Dewey Dewey { get; set; }
public virtual BookLoan BookLoan { get; set; }
}
}
BookLoan Entity:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Open_School_Library.Data.Entities
{
public class BookLoan
{
public int BookLoanID { get; set; }
public int BookID { get; set; }
public int StudentID { get; set; }
public DateTime CheckedOutWhen { get; set; }
public DateTime DueWhen { get; set; }
public DateTime? ReturnedWhen { get; set; }
public Book Book { get; set; }
public Student Student { get; set; }
}
}
BookIndexViewModel:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Open_School_Library.Models.BookViewModels
{
public class BookIndexViewModel
{
public int BookID { get; set; }
public string Title { get; set; }
[Display(Name = "Author")]
public string Author { get; set; }
[Display(Name = "Genre")]
public string GenreName { get; set; }
public int ISBN { get; set; }
public string BookLoan { get; set; }
public DateTime? Availability { get; set; }
}
}
Here are some simplified versions of your classes
public class Book {
public int BookId { get; set; }
public string Title { get; set; }
}
public class BookLoan {
public int BookId { get; set; }
public DateTime CheckedOutOn { get; set; }
public DateTime? ReturnedOn { get; set; }
public DateTime DueOn { get; set; }
}
public class BookViewModel {
public int BookId { get; set; }
public string Title { get; set; }
public bool IsAvailable { get; set; }
public DateTime? AvailableOn { get; set; }
}
Then we can do two things to get the view model list you're looking for:
Look at only book loans where ReturnedOn is null
Use DefaultIfEmpty to do a left outer join, see https://msdn.microsoft.com/en-us/library/bb397895.aspx
So you end up with one item per book and only a loanWithDefault if the book is currently checked out.
var viewModel = from book in books
join loan in loans.Where(x => !x.ReturnedOn.HasValue) on book.BookId equals loan.BookId into result
from loanWithDefault in result.DefaultIfEmpty()
select new BookViewModel {
BookId = book.BookId,
Title = book.Title,
IsAvailable = loanWithDefault == null,
AvailableOn = loanWithDefault == null ? (DateTime?) null : loanWithDefault.DueOn
};
Here is a working example: https://dotnetfiddle.net/NZc2Xd
Here is a breakdown of the LINQ query:
from book in books is the first table you are selecting from
loans.Where(x => !x.ReturnedOn.HasValue) limits the loans to a maximum of one item per book (and the loans we care about in this case)
join loan in loans on book.BookId equals loan.BookId into result is joining on the loans table so that we get that as part of our result set. The join condition is the book's BookId equals the loan's BookId
from loanWithDefault in result.DefaultIfEmpty() basically makes the join on loans a "left outer join". This means that even if there are no loans that have yet to be returned for this book you will get the book in your result set. Otherwise, you would only get unavailable books. See this Stack Overflow answer for a good explanation of different joins. Also, see https://msdn.microsoft.com/en-us/library/bb397895.aspx for how to do left outer joins with LINQ.
Finally, you select your view model from the available Book and BookLoan object.
Here is what a similar raw SQL query would look like
select
Book.BookId,
Book.BookTitle,
BookLoan.DueOn
from
Book
left outer join BookLoan on
Book.BookId = BookLoan.BookId
and BookLoan.ReturnedOn is null
Related
I'm badly stuck on this one, I've a one to many relationship between two models (POS_cities,POS_company)
POS_cities.cs
public class POS_cities
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public int CountryID { get; set; }
public virtual POS_country country { get; set; }
public virtual ICollection<POS_company> company { get; set; }
}
POS_company.cs
public class POS_company
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public string BusinessName { get; set; }
public int CityID { get; set; }
public virtual POS_cities cities { get; set; }
}
So, I scaffold the above using Entity framework, but it did not generate the code as expected, so, I had to modify the code according to my need, such as the below Index action :
public ActionResult Index()
{
var pOS_company = db.POS_company.Include(p => p.cities);
return View(pOS_company.ToList());
}
In the above code EF did not generate the Include(p => p.cities) function, so, I had to add that explicitly. Now, when I execute the above Index() action, the navigation property cities returns null even if the data is present in database :
Now, let's make sure that data is actually present in database :
Data in POS_cities
Data in POS_company
So, what could possibly be the thing I'm doing wrong? Why the navigation property cities is returning null even if the data is present in database? Thanks in Advance :)
Update
"SELECT [Extent1].[ID] AS [ID], [Extent1].[Name] AS [Name], [Extent1].[BusinessName] AS [BusinessName], [Extent1].[CityID] AS [CityID], [Extent2].[ID] AS [ID1], [Extent2].[Name] AS [Name1], [Extent2].[CountryID] AS [CountryID] FROM [dbo].[POS_company] AS [Extent1] LEFT OUTER JOIN [dbo].[POS_cities] AS [Extent2] ON [Extent1].[CityID1] = [Extent2].[ID]"
Try this:
public class POS_company
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public string BusinessName { get; set; }
[ForeignKey("CityID")]
public virtual POS_cities cities { get; set; }
public int CityID { get; set; }
}
I'm working on a cinema application which allows users to surf through movies, cinema places and allows them to buy or reserve tickets. If a user reserved a ticket online, then the ticket must be activated in 12 hours by sellerperson who also uses the same program. I need to show the ticket informations on grid and need to make editable. Here's my database classes that must be included in query and have relationship with Sale class. (I want to select objects from Sale class which includes ti's related classes: Ticket, customer, movie, status and saloon infos.
Sale Class:
public class Sale
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[ForeignKey("CustomerId")]
public virtual Customer Customer { get; set; }
public int CustomerId { get; set; }
[ForeignKey("StatusId")]
public virtual Status Status { get; set; }
public int StatusId { get; set; }
public virtual Seller Seller { get; set; }
public DateTime SellDate { get; set; }
public double Price { get; set; }
[ForeignKey("TicketID")]
public virtual Ticket Ticket { get; set; }
public int TicketID { get; set; }
}
Ticket Class:
public class Ticket
{
public Ticket()
{
Seats = new List<Seat>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[ForeignKey("MovieId")]
public virtual Movie Movie { get; set; }
public int MovieId { get; set; }
public virtual List<Seat> Seats { get; set; }
public virtual TimeSpan SeanceTime { get; set; }
public bool IsActive { get; set; }
public DateTime BuyDate { get; set; }
[ForeignKey("SaloonId")]
public virtual Saloon Saloon { get; set; }
public int? SaloonId { get; set; }
public string TicketNumber { get; set; }
}
Customer Class:
public class Customer
{
public Customer()
{
Sales = new List<Sale>();
CreditCards = new List<CreditCard>();
}
[Key]
public int UserID { get; set; }
public virtual List<Sale> Sales { get; set; }
public virtual User User { get; set; }
[DataType(DataType.CreditCard)]
public virtual List<CreditCard> CreditCards { get; set; }
}
User Class:
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
Status Class(Holds info of tickets. Bought or reserved.)
public class Status
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public bool IsRez { get; set; }
public bool IsBuy { get; set; }
public bool IsCancel { get; set; }
public bool IsPaid { get; set; }
}
Saloon Class:
public class Saloon
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Name { get; set; }
public double salePrices { get; set; }
}
Movie Class:
public class Movie
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Name { get; set; }
}
I can't edit because in my select query I'm using anonymous type for selection. My query code:
var Source = entities.Sales.Where(w => w.Ticket.Saloon.CinemaPlace.ID == seller.CinemaPlace.ID).Select(s => new
{
CustomerName = s.Customer.User.Name,
CustomerSurname = s.Customer.User.Surname,
SalePrice = s.Price,
s.Status.IsBuy,
s.Status.IsCancel,
s.Status.IsPaid,
s.Status.IsRez,
MovieName = s.Ticket.BuyDate,
s.Ticket.Movie.Name,
SaloonName = s.Ticket.Saloon.Name,
s.Ticket.SeanceTime,
s.Ticket.TicketNumber
}).ToList();
RezervationsGrid.DataSource = Source3;
But in the grid, the datas couldn't be edited. Then I tried to join every single table using Linq to Entities queries but it didn't help either. Is there a way make a datasource from my related objects that allows edit option in grid? Thanks.
Anonymous types (those that you can declare via the new operator in the Select method) cannot have writable properties in .NET. That's why the grid is not editable. To take advantage of in-place editing, you need to instantiate objects of a real CLR type.
For this, you can declare a special ViewModel class with public properties that you should populate with values in the Select method using object initializer.
.Select(s => new SaleViewModel() {
CustomerName = s.Customer.User.Name,
SalePrice = Price
})
Note that you should not move the property initialisation logic to the ViewModel constructor to use it this way:
.Select(s => new SaleViewModel(s))
The object initialiser is the expression tree, which Entity Framework can translate into an SQL query. The constructor is just a method reference, so Entity Framework will reject such an expression. If you would like to use this approach, you will need to call the ToList method before the Select.
SaleViewModel can have the method accepting the DbContext class to save changes.
You also can select the Sale instances and use complex property paths in columns' field names (such as "Customer.User.Name"). This can probably help you to simplify the saving logic, as you will not need to find a model specific to a certain view model and copy modified property values.
I am working on Web-API project and using Entity Framework with Generic Repository and Unit Of work. Basically i follow a tutorial for this.
Here is my table architecture.
Entity
public class ProductEntity
{
public int ProductId { get; set; }
public string ProductCode { get; set; }
public string ProductName { get; set; }
public string ProductDescription { get; set; }
public string ProductImgName { get; set; }
public bool IsActive { get; set; }
public int PrimaryCatId { get; set; }
public int SecondaryCatId { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
public System.DateTime CreateDate { get; set; }
public List<PrimaryProductEntity> objPrimaryProduct { get; set; }
public List<SecondaryProductEntity> objSecondaryProduct { get; set; }
}
public class PrimaryProductEntity
{
public int PrimaryCatId { get; set; }
public string PrimaryCatName { get; set; }
}
public class SecondaryProductEntity
{
public int SecondaryCatId { get; set; }
public string SecondaryCatName { get; set; }
public int PrimaryCatId { get; set; }
}
Services Code
public IEnumerable<BusinessEntities.ProductEntity> GetAllProducts()
{
var products = _unitOfWork.ProductRepository.GetAll().ToList();
var primaryProducts = _unitOfWork.PrimaryProductRepository.GetAll().ToList();
var secondaryProducts = _unitOfWork.SecondaryProductRepository.GetAll().ToList();
if (products.Any())
{
Mapper.CreateMap<tblProduct, ProductEntity>();
var proInfo = from P in products
join PP in primaryProducts on P.PrimaryCatId equals PP.PrimaryCatId
join SP in primaryProducts on P.SecondaryCatId equals SP.SecondaryCatId
select P;
var productsModel = Mapper.Map<List<tblProduct>, List<ProductEntity>>(proInfo);//getting error
return productsModel;
}
return null;
}
i know my implementation is wrong, i don't know what to write in code for fetch data from multiple tables. Please help me.
Required Data
ProductID,ProductName, PrimaryCatName, SecondaryCatName,Price, Quantity
Your Product Entity class Doesn't require a List<PrimaryProductEntity> and List<SecondaryProductEntity>. I suppose according to your class diagram Each Product is associated with one PrimaryProductEntity and one SecondaryProductEntity.
Once your model class is corrected, you would be able to access the properties of the navigation. I am not so good with writing a Query the way you want. But i hope you could get an idea of what you should be doing
I have the following relationship between the entities.
Company 1 ---* Appointments *---1 Employee
I have the .net asp membership in a separate database. Whenever a user is created it can be assigned to companies, employees, or administrators roles.
in the Index action of my Company Controller, I check the logged in user's role. Based on the role, I make different linq query. For example, administrators can get list of all companies, companies can get list of company which has a username property (string) same as the User.Identity.Name. For both of administrators and companies role, it is working fine.
For the employees role, I want to load all the companies that are related to the current employee. I am having hard time to compose a linq query that does this job.
i tried
var companies = db.Companies.Include(c => c.Appointments.Select(a=>a.Employee).Where(e=>e.Username.ToLower() == this.User.Identity.Name.ToLower())).ToList();
to which i get this error
"The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parameter name: path"
Here are the source code,
CompanyController
[Authorize]
public class CompanyController : Controller
{
private MyDBContext db = new MyDBContext();
//
// GET: /Company/
public ViewResult Index()
{
var viewModel = new CompanyIndexViewModel();
if (Roles.IsUserInRole("administrators")) {
viewModel = new CompanyIndexViewModel { Companies = db.Companies.ToList() };
}
else if (Roles.IsUserInRole("companies")) {
viewModel = new CompanyIndexViewModel { Companies = db.Companies.Where(c => c.Username.ToLower().Equals(this.User.Identity.Name.ToLower())).ToList() };
}
else if (Roles.IsUserInRole("employees")) {
var companies = db.Companies.Include(c => c.Appointments.Select(a=>a.Employee).Where(e=>e.Username.ToLower() == this.User.Identity.Name.ToLower())).ToList();
viewModel = new CompanyIndexViewModel { Companies = companies.ToList() };
}
return View(viewModel);
}
...
Models
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace TorontoWorkforce.Models
{
public class Company
{
public int CompanyId { get; set; }
[Required]
public string Username { get; set; }
[Display(Name="Company Name")]
[Required]
public string Name { get; set; }
[UIHint("PhoneNumber")]
public string Phone { get; set; }
[DataType(DataType.Url)]
public string Website { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
public AddressInfo AddressInfo { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
public virtual ICollection<Appointment> Appointments { get; set; }
public Company(){
this.AddressInfo = new AddressInfo();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace TorontoWorkforce.Models
{
public class Appointment
{
public int AppointmentId { get; set; }
[Required]
[UIHint("DateTime")]
[Display(Name="Appointment Date")]
public DateTime? DateOfAppointment { get; set; }
[Required]
public int CompanyId { get; set; }
[Required]
public int EmployeeId { get; set; }
[Required]
[UIHint("MultilineText")]
[Display(Name = "Appointment Summary")]
public string Description { get; set; }
[Display(Name="Allocated No of Hours")]
public decimal NoOfHoursWorked { get; set; }
public virtual Company Company { get; set; }
public virtual Employee Employee { get; set; }
public virtual ICollection<AppointmentLine> AppointmentLines { get; set; }
public Appointment() {
//this.AppointmentLines = new List<AppointmentLine>();
this.DateOfAppointment = DateTime.Now;
}
[NotMapped]
[Display(Name="Actual No of Hours")]
public decimal ActualHoursWorked {
get
{
decimal total = 0;
foreach (var jobline in this.AppointmentLines)
{
total = total + jobline.TimeSpent;
}
return total;
}
}
}
public class AppointmentLine
{
public int AppointmentLineId { get; set; }
[UIHint("MultilineText")]
[Required]
public string Description { get; set; }
[Display(Name="Time Spent")]
[DataType(DataType.Duration)]
public decimal TimeSpent { get; set; }
public int AppointmentId { get; set; }
public virtual Appointment Appointment { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace TorontoWorkforce.Models
{
public class Employee: TorontoWorkforce.Models.Person
{
public int EmployeeId { get; set; }
[Required]
public string Username { get; set; }
[Display(Name="Date Hired")]
public DateTime? DateHired { get; set; }
[Required]
public string Position { get; set; }
public virtual ICollection<Appointment> Appointments { get; set; }
public Employee() {
this.DateHired = DateTime.Now;
}
}
}
If you want to get companies which have appointment with selected employee you don't need to use Include. Include is for instructing EF to load all appointments related to the company (and it doesn't support filtering). Try this:
string userName = this.User.Identity.Name.ToLower();
var companies = db.Companies.Where(c => c.Appointments.Any(a =>
a.Employee.Username.ToLower() == userName)).ToList();
I think you just have an end parentheses in the wrong place. You need one more after "a => a.Employee" and one less after "this.User.Identity.Name.ToLower()));"
Try this code:
var companies = db.Companies.Include(c => c.Appointments.Select(a=>a.Employee)).Where(e=>e.Username.ToLower() == this.User.Identity.Name.ToLower()).ToList();
Edit:
You should also be able to use the standard string include method:
var companies = db.Companies.Include("Appointments.Employee").Where(e=>e.Username.ToLower() == this.User.Identity.Name.ToLower()).ToList();
I have a model like the following:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public DateTime DateTime { get; set; }
public Customer Customer { get; set; }
public ICollection<OrderLine> OrderLines { get; set; }
}
public class OrderLine
{
public int Id { get; set; }
public Product Product { get; set; }
public int Price { get; set; }
public int Quantity { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public Category Category { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
}
I am using this infrastructure.
My aggregate roots are Customer, Order, Product. I did not include the mappings here as they are straight forward.
var customers = unitOfWork.Customers.FindAll();
var orders = unitOfWork.Orders.FindAll();
var products = unitOfWork.Products.FindAll();
var query = ......
Using LINQ, how would you select all customers that have orders for products in the "Beverages" category?
All samples I have seen on the web are very basic queries nothing advanced.
i found http://msdn.microsoft.com/en-us/vbasic/bb737909
May be your query should look like:
from c in unitOfWork.Customers
join o in unitOfWork.Orders on o.Customer = c
join ol in unitOfWork.OrderLines on ol.Order = o
where ol.Product.Category.Name == "Beverages"
select c
And it is necessary to add all parent-object-properties
This might work or not:
from customer in customers
where customer.Orders.Any(
o => o.OrderLines.Any(l => l.Product.Category.Name == "Beverages")
select customer
(I'm assuming you forgot the relationship between Product and Category)