EF Lambda Expression How to Access Data from Multiple Tables - entity-framework

I need some help writing and understanding EF Lambda expressions.
If I received a productID as a parameter (selectedID), how would I go about accessing the customer name, invoiceID, and PriceTotal related to that productID?
This is what I tried for getting the invoiceID:
db.InvoiceLines.Where(z => z.ProductID == selectedID).Select(x => x.InvoiceID).ToList();
But I can't figure out how to access the rest of the data.
Any help and explanations would be greatly appreciated!

So every Customer has zero or more Invoices, every Invoice is the invoice of exactly one Customer, namely the Customer that the foreign key CustomerId refers to: a straightforward one-to-many relation.
Similarly there is a one-to-many relationship between Invoices and InvoiceLines: Every Invoice has zero or more InvoiceLines, every InvoiceLine belongs to exactly one Invoice, using a foreign key.
Products - InvoiceLines: also a one-to-many relation using a foreign key.
If you've followed the entity-framework coding conventions, you will have classes similar to the following:
class Customer
{
public int Id {get; set;}
public string Name {get; set;}
...
// Every Customer has zero or more Invoices (one-to-many)
public virtual ICollection<Invoice> Invoices {get; set;}
}
class Invoice
{
public int Id {get; set;}
...
// Every invoice belongs to exactly one Customer, using foreign key:
public int CustomerId {get; set;}
public virtual Customer Customer {get; set;}
// Every Invoice has zero or more InvoiceLiness (one-to-many)
public virtual ICollection<InvoiceLine> InvoiceLiness {get; set;}
}
In entity framework columns of tables are represented by non-virtual properties. The virtual properties represent the relations between the tables.
The foreign key CustomerId is a real column in the Invoices table. Hence it is non-virtual. That an Invoice has a relation to a Customer can be seen on the virtual property Customer.
In entity framework the type of relation between tables (one-to-one, one-to-many, many-to-many) can be seen by the virtual ICollection<...> or virtual ...
Tables and collections are identified using plural nouns; a single row of the table, or one item in the collection is identified by a singular noun. Later when we discuss lambda, you will see that this makes interpreting the lambda easier.
class InvoiceLIne
{
public int Id {get; set;}
...
// Every InvoiceLine belongs to exactly one Invoice, using foreign key:
public int InvoiceId{get; set;}
public virtual Invoice Invoice {get; set;}
// Every InvoiceLine has exacaly one Product, using foreign key:
public int ProductId {get; set;}
public virtual Product Product {get; set;}
}
class Product
{
public int Id {get; set;}
public string Name {get; set;}
...
// Every Product is use in has zero or more InvoiceLines (one-to-many)
public virtual ICollection<InvoiceLine> InvoiceLInes {get; set;}
}
And finally the DbContext
class OrderingDbContext : DbContext
{
public DbSet<Customer> Customers {get; set;}
public DbSet<Invoice> Invoices {get; set;}
public DbSet<InvoiceLIne> InvoiceLines {get; set;}
public DbSet<Product> Products {get; set;}
}
In fact, this is all that entity framework needs to know to detect the tables, the columns in the tables and the relations between the tables. No need for attributes or fluent api. Only if you want identifiers that deviate from the default identifiers, for table names, or columnames, or if you want to define something special in the relations, you need fluent API.
Back to your question
If I received a productID as a parameter (selectedID), how would I go about accessing the customer name, invoiceID, and PriceTotal related to that productID?
First of all, if you have a productId, there are probably several Customers who bought the product with this productId, and thus several invoices, and several priceTotals. So you can't say THE customer name, THE invoice id.
Requirement
If I have a productId, give me the names of all Customers who bought this Product, together with the InvoiceIds where is stated that they bought this product and the total price of the invoice where they bought this product.
Note that a Customer might have bought the product several times in different orders, so they can have several Invoices.
If you've followed the conventions as I wrote above, the solution is fairly simple:
int productId = ...
var customersWhoBoughThisProduct = dbContext.Customers
// I only want Customers who have at least one Invoice that has at least one
// invoiceLine for this product
.Where(customer => customer.Invoices
.SelectMany(invoice => invoiceLines, invoice => invoice.ProductId)
.Contains(productId))
// from the customers who bough this product, select several properties:
.Select(customer => new
{
// Select only the Customer properties that you plan to use
Id = customer.Id,
Name = customer.Name,
...
// You want only the invoices that have this product as one of the InvoiceLines
Invoices = customer.Invoices
.Where(invoice => invoice.InvoiceLines
.Any(invoiceLine => invoiceLine.ProductId == productId)
.Select(invoice => new
{
// select the invoice properties that you plan to use, for example:
Id = invoice.Id,
OrderDate = invoice.OrderDate,
PriceTotal = invoice.InvoiceLines
.Select(invoiceLine => invoiceLine.Price)
.Sum(),
})
.ToList(),
});
Well you see several Lambda expressions here. Because I'm quite strict in my use of identifiers, they shouldn't be too difficult:
dbContext.Customers.Where(...)
.Select(customer => new
{
// Select only the Customer properties that you plan to use
Id = customer.Id,
Name = customer.Name,
...
After the Where, I've got a sequence of Customers, and from every customer in this sequence, I make exactly one new object, with property Id, that has the value of customerId, and property Name, which has the value of customer.Name.
This customer object, also has a property Invoices:
Invoices = customer.Invoices
.Where(...)
.Select(invoice => new{...}
You don't want all invoices of this customer, you only want the invoice, where at least one invoice line has the foreign key to the product with ProductId:
.Where(invoice => invoice.InvoiceLines.Any(invoiceLine => invoiceLine.ProductId == productId)
This says: keep only those invoices, that have at least one InvoiceLIne where property ProductId equals productId. Which is the kind of invoices we want.
PriceTotal = invoice.InvoiceLines
.Select(invoiceLine => invoiceLine.Price)
.Sum(),
To calculate the PriceTotal of the invoice that had this product, we take the Price of every InvoiceLine in this invoice, and we sum all these prices.
The most difficult one: the Where of the customers:
.Where(customer => customer.Invoices
.SelectMany(invoice => invoiceLines)
.Any(invoiceLine => invoiceLine.ProductId == productId))
So the input was a sequence of Customers. We keep only those Customers, that have at least an invoiceLine in the sequence of Invoices of this customer that with a productId equal to productId.
Whenever you have a sequence where every element in the sequence has a subsequence, and you want to check all these subsequences as one sequence, use SelectMany.
So if you have Schools, and every School has Students, and you want to see all Students of all Schools in one sequence, user: schools.SelectMany(school => school.Students) I did the same with Invoices and InvoiceLines, to get all InvoiceLines of all Invoices of this Customer: invoices.SelectMany(invoice => invoice.InvoiceLines)
By the way, did you see that because I am very strict in singular and plural nouns, that the identifiers in the lambda expressions are quite easy to understand what they represent?
Hope this helped a bit in understanding lambda expressions

Related

Table with with different fields depending on category

I have table that has these columns:
Id
Title
Description
CreateDateTime
CategoryId
Picture
quantity
Price
RentPrice
WantToExchange
NumberOfRoom
DepositPrice
Only the first 6 are required for each row and other column will be null based on entity category.
For example in cat1 only first 6 field and Price filled by user,and in cat2 only first 6 field and RentPrice, DepositPrice are filled, so after a while many column of table rows become a null
I see some solution in NopCommerce source code that used for store various product property in different language - there are entity called LocalizedProperty and storeEntityId, LanguageId, Name of entity, name of field and its value
it use
Expression<Func<T, TPropType>>
and PropertyInfo to get name of field, but I am searching for an easier way.
How can I redesign my entity?
If you'd have designed this object orientedly, you would probably have create a PictureDescription class, and a PicturePrice class, that would have the other properties.
Every PictureDescription would have zero or one PicturePrice object: a straightforward aggregation
If you want zero-or-one in a relational Database, then this is quite often done using two tables, with a foreign key from one table pointing to the other table.
See Entity Framework zero-or-one-to-one
class PictureDescription
{
public int Id {get; set;}
public string Title {get; set;}
...
// every PictureDescription has zero or one PicturePrice
public virtual PicturePrice PicturePrice {get; set;}
}
class PicturePrice
{
public int Id {get; set;}
public decimal Price {get; set;}
...
// every PicturePrice belongs to exactly one Picture, using foreign key
public int PictureDescriptionId {get; set;}
public virtual PictureDescription PictureDescription {get; set;}
}
This will be enough for entity framework to detect your columns and the relations between the tables. If you want to use fluent API, in DbContext.OnModelCreating:
// every PictureDescription has zero or one PicturePrice:
modelBuilder.Entity<PictureDescription>()
.HasOptional(description => description.PicturePrice)
.WithRequired(price => price.PictureDescription)
.HasForeignKey(price => price.PictureDescriptionId);

how to concert SQL query to LINQ with count , group by and isnull clause

I have the following SQL Query which I am trying to translate to LINQ Query
SELECT C.NAME,C.MOBILEPHONE,ISNULL (SUM(P.PAYMENTAMOUNT),0) AS
PAYAMOUNT,BILLAMOUNT ,B.ID,BILLNO , BILLDATE FROM CUSTOMERS C
JOIN BILLS B ON B.CUSTOMERID=C.ID
LEFT JOIN BILLPAYMENTS P ON P.BILLID=B.ID
GROUP BY C.NAME ,B.BILLAMOUNT,B.ID,BILLNO,BILLDATE,C.MOBILEPHONE
HAVING B.BILLAMOUNT> ( ISNULL(SUM(P.PAYMENTAMOUNT),0))
How do you represent this in LINQ ?
I have seen the typical implementation this
var query = from c in db.Customers
join b in db.Bills on c.Id equals b.CustomerId
join p in db.BillPayments on b.Id equals p.BillId into cs
from xx in cs.DefaultIfEmpty()
group xx by new { c.Name, c.MobilePhone, b.BillAmount, b.BillNo, b.Id, b.BillDate } into g
where g.Sum(p => p.PaymentAmount) < g.Key.BillAmount
select new
{
Received = g.Key,
ReceivedTotal = g.Sum(p => p.PaymentAmount)
};
but am unsure how to implement the following :
HAVING B.BILLAMOUNT> ( ISNULL(SUM(P.PAYMENTAMOUNT),0))
So you have a sequence of Customers, where every Customer has zero or more Bills, and every Bill belongs to exactly one Customer: a straightforward one-to-many relation.
Furthermore, every Bill has zero or more BillPayments, where every BillPayment belongs to exactly one Bill, also a one-to-many relation.
Alas you forgot to tell us your classes. If you followed the entity framework code first conventions, you'll have something similar to:
class Customer
{
public int Id {get; set;}
public string Name {get; set;}
...
// every Customer has zero or more Bills (one-to-many)
public virtual ICollection<Bill> Bills {get; set;}
}
class Bill
{
public int Id {get; set;}
public int BillNo {get; set;}
public decimal BillAmount {get; set;}
...
// every Bill belongs to exactly one Customer, using foreign key
public int CustomerId {get; set;}
public virtual Customer Customer {get; set;}
// every Bill has zero or more BillPayments (one-to-many)
public virtual ICollection<BillPayment> BillPayments {get; set;}
}
class BillPayment
{
public int Id {get; set;}
...
// every BillPayment belongs to exactly one Bill, using foreign key
public int BillId {get; set;}
public virtual Bill Bill{get; set;}
// every Bill has zero or more BillPayments (one-to-many)
public virtual ICollection<BillPayment> BillPayments {get; set;}
}
In entity framework, the columns of your table are represented by non-virtual properties, the virtual properties represent the relations between the tables.
You also forgot the requirements of your query. It seems to me, that you want the following:
Give me certain properties of Bills (Id, BillNo, BillDate, BillAmount), with certain properties of the Customer of this Bill (Name and MobilePhone), of all Bills that are not fully paid yet. Or in other words, of all Bills where the sum of all payments is less than the BillAmount.
One of the nice things about entity framework, is that you don't have to do the joins yourself, you can use the virtual properties. Entity Framework knows the relations between the tables and does the proper joins for you.
Just for fun, we'll add the original BillAmount, the AmountPaid, and the RemainingAmount, so you can tell your Customer how much he still has to pay when you phone him on his mobile phone
In the requirement you see the central role of Bills, so let's use that as starting point:
// (1) from all bills, calculate the AmountPaid; remember the original bill data:
var notFullyPaidBills = myDbContext.Bills
.Select(bill => new
{
BillData = bill,
AmountPaid = bill.BillPayments
.Select(billPayment => billPayment.PaymentAmount)
.Sum(),
})
// (2) Keep only those bills that are not fully paid yet
.Where(bill => bill.Bil.BillAmount > bill.AmountPaid)
// (3) from the remaining bills select the required properties:
.Select(bill => new
{
// Customer properties:
CustomerName = bill.BillData.Customer.Name,
MobilePhone = bill.BillData.Customer.MobilePhone,
// bill properties:
BillId = bill.BillData.Id,
BillNo = bill.BillData.BillNo,
BillDate = bill.BillData.Date,
// Amounts:
BillAmount = bill.BillData.BillAmount,
AmountPaid = bill.AmountPaid,
RemainingAmount = bill.BillData.BillAmount - bill.AmountPaid,
});
See? When using the virtual properties of your entity framework classes, the queries will look much simpler and more intuitive than when you are doing the (group)joins yourself.

Entity Framework: add value to existing record, or insert record

A nice puzzle for the Entity-Framework Guru's out here!
I have a collection of customers. Each customer has some expenses. I need to remember the total of the expenses per day.
In entity framework this is fairly straightforward:
public class Customer
{
public int CustomerId {get; set;}
public string Name {get; set;} // and other name/address fields
public ICollection<Expense> ExpenseHistory{get; set;}
}
public class Expense
{
public int ExpenseId {get; set;}
public int CustomerId {get; set;} // foreign key to customer
public Customer {get; set;}
public DateTime Date {get; set;}
public decimal Total {get; set;} // total expenses on date
}
public class ExpenseContext : DbContext
{
public DbSet<Customer> Customers {get; set;}
public DbSet<Expense> Expenses {get; set;}
}
I am certain that the combination (Expense.CustomerId, Expense.Date) is unique: for every date I only have one expense-total per customer. So I could use this combination as a primary key instead of ExpenseId. But that's another discussion.
A process sends me enumerations of messages: "the customer with this customerId just spent an amount of Value on date Date."
My process needs to check if the customer already spent something on Date.
If not: create an Expense Item for the customer with CustomerId, for Date. Assign value to Total.
If the customer already spent something on Date, add Value to the Total
Is it possible to do this in one query or do I have to check existence of each Expense and either add the expense value or create it? Apart from that the latter is slow, there is the possibility of race conditions: while I just found out a record does not exist, someone else might have created it. Or while I am just adding a value to an existing record, someone else might already have increased the value of the existing record.
The last method would be like:
public void ProcessExpenses(IEnumerable<Expense> expenses)
{
using (var dbContext = ...)
{
foreach (var expense in expenses)
{
var existingExpense = dbContext.Expenses.SingleOrDefault(item =>
item.CustomerId == expense.CustomerId &&
item.Date == expense.Date);
if (existingExpense != null)
{ // already something spent on Date. Add the value:
existingExpense.Total += expense.Total;
}
else
{ // nothing spent by customerId on Date yet. Add expense:
dbContext.Add(expense);
}
dbContext.SaveChanges();
}
}
}
The above is a bit simplified, the enumerable is in fact a different class that contains the spent customerId / date / value, but you get the gist.
You cannot get it with EF 6. Assuming that you working with MS SQL, you can use this query to achieve what you want.
var result = ((IObjectContextAdapter)ExpenseContext).ObjectContext.ExecuteStoreQuery<Expense>(
#" MERGE [Expense] AS target
USING (select #CustomerId as CustomerId, #Date as Date, #Total as Total ) AS source
ON (target.CustomerId = source.CustomerId and target.Date = source.Date)
WHEN MATCHED THEN
UPDATE
SET Total = Total+source.Total
WHEN NOT MATCHED THEN
INSERT (CustomerId, Date, Total )
VALUES (source.CustomerId, source.Date, source.Total)
OUTPUT inserted.CustomerId, inserted.Date, inserted.Total;",
new SqlParameter("#CustomerId", SqlDbType.Int) { Value = ..},
new SqlParameter("#Date", SqlDbType.Date) { Value = ..},
new SqlParameter("#Total", SqlDbType.Decimal) { Value = ..}
);
If you don't need the resulting entity, you may replace this call with
ExpenseContext.Database.ExecuteSqlCommand("MERGE ...", new SqlParameter(....)
If you use other SQL dialects, check for the UPSERT command or something similar to MERGE.
Also, be careful about comparing valued of the DATE type when you provide them from .NET code to SQL, make sure they do not have TIME part. In .NET they are represente public DateTime Date {get; set;} as DateTime that has TIME part.

Entity Framework Association with Filter

I have the following model in my model:
Patient
Vendor
Organization
each of these entities needs Addresses.
The Address basically looks like the following
Address
AddressTypeId // with Navigation Property/Association to AddressType
EntityKey // indicates the PK Id of the entity this address is for
AddressType
EntityId // indicates the entity type this address type corresponds to (Patient or Vendor)
// This should be on the AddressType, not the Address, since we need a way of knowing what kind of AddressTypes are available to create for new addresses for Patients, Vendors, and Organizations
//...that is Patients support AddressType X, Vendors support AddressType Y, etc.
I want to create an association for Patient, Vendor, and Organization on the EntityKey property on Address - each with a filter constraint that the Address's AddressType.EntityId is the matching EntityId for that entity (1 for Patient, 2 for Vendor, 3 for Address).
What is the best way of doing this? Most ORM's on the market support this kind of scenario....and it's certainly a very common one.
NOTE: I don't want to create PatientAddress/PatientAddressType, VendorAddress/VendorAddressType, and OrganizationAddress/OrganizationAddress type derived entities. It severely clutters the model and makes it basically incomprehensible.
Right now I'm solving this by doing explicit joins in my LINQ queries:
const int patientTypeEntityId = 1;
var query = from p in repository.Patients
let addresses = repository.Addresses.Where(a =>
a.EntityKey == p.Id & a.AddressType.EntityId == patientTypeEntityId)
select new { Patient = p, Addresses = a }
but I don't want to continue having to do this.
If I understand correctly you want to have an address collection in your Patient, Vendor, etc...
public class Patient
{
public int Id { get; set; }
public ICollection<Address> Addresses { get; set; }
}
public class Vendor
{
public int Id { get; set; }
public ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int Id { get; set; }
//public int EntityKey { get; set; }
public AddressType AddressType { get; set; }
}
... and somehow tell EF that Patient.Addresses only gets populated with addresses of address type "Patient".
I think that is not possible for several reasons:
If you don't expose the foreign key in Address (no EntityKey property there) you have to tell EF the key in the mapping (otherwise it would create/assume two different FK columns):
modelBuilder.Entity<Patient>()
.HasMany(p => p.PVAddresses)
.WithRequired()
.Map(a => a.MapKey("EntityKey"));
modelBuilder.Entity<Vendor>()
.HasMany(p => p.PVAddresses)
.WithRequired()
.Map(a => a.MapKey("EntityKey"));
This throws an exception due to the duplicate "EntityKey" column for two different relationships.
Next thing we could try is to expose the foreign key as property in Address (EntityKey property is there) and then use this mapping:
modelBuilder.Entity<Patient>()
.HasMany(p => p.PVAddresses)
.WithRequired()
.HasForeignKey(a => a.EntityKey);
modelBuilder.Entity<Vendor>()
.HasMany(p => p.PVAddresses)
.WithRequired()
.HasForeignKey(a => a.EntityKey);
This (surprisingly) doesn't throw an exception but creates two FK constraints in the database between Patient-Address and Vendor-Address with the same FK column EntityKey. For your model, I think, this doesn't make sense because it would require that always a Patient and a Vendor with the same PK exists if you have an address with some EntityKey. So, you would have to remove these FK constraints in the DB manually (which feels very hacky to me).
And the last thing is that you cannot specify a filter for lazy and eager loading of navigation properties. The Addresses collection would always get populated with the addresses which have the same EntityKey as the PK of Patient or Vendor respectively. You can apply a filter though with explicite loading:
var patient = context.Patients.Single(p => p.Id == 1);
context.Entry(patient).Collection(p => p.Addresses).Query()
.Where(a => a.Addresstype.EntityId == patientTypeEntityId)
.Load();
But you would have to ensure that you never use lazy or eager loading for the Addresses collection. So, this is not really a solution and we should forget it immediately.
The ugliest point for me is that you cannot have FK constraints on the EntityKey. In other words: The DB allows to have an EntityKey = 1 with no referenced Patient or Vendor with that PK (because somehow the patient 1 and vendor 1 have been deleted, for example).
For this reason alone I would prefer the solution shown by #Akash - aside from the fact that it is probably the only working and clean solution with EF at all.

Entity Framework many to many relationship without primary key

I have a few tables which all refer to each other with many-to-many relationships but not the normal kind.
Usually a many-to-many relationship has a join table which joins the two other tables on their primary keys.
In my case I have a few tables which relate to each other by sharing matching foreign keys.
I have patient records with two tables.
Doctors who are assigned to a patient.
The patients test results.
I am not allowed to store anything about the patient besides their patient id (and I have no reason to) so there is no point in a patient table.
How could I relate the Doctors to the TestResults?
They both have a foreign key to a table that does not exist. i.e. they both have patient record numbers but there is no table of patient record numbers (the record numbers are generated by a system that I do not have access to).
So in fact they are in a many-to-many relationship with each other.
I did think of making a table just to hold the record ids. That table would have one column which is the primary key and nothing else.
That solution would not work for me at all.
My storage agnostic (poco) library which would be managing and analysing these records would have no way to check if a patient was in our system when adding a new test result.
Even if I did pass a Database Context to the managing library. That would mean that the system would have to make a database call every time it wanted to add a test record just to see if the patient had any previous records with us or if this was the first one. All to add a record in a table that had no purpose. During peak processing times this could be thousands of times per minute. Something that would be trivial to do if your just accessing clr objects, but totally overwhelming if you need to make a database call for each one.
Thank you!
As trivial and probably prohibitive as it is, to enforce the relationship you describe at the physical level, there has to be a patient table. Then the relationship is modeled simply as below:
public class Doctor
{
[Key]
public int DoctorId {get; set;}
public virtual ICollection<Patient> Patients {get; set;}
}
public class Patient
{
[Key]
public int PatientId {get; set;}
public virtual ICollection<Doctor> Doctors {get; set;}
public virtual ICollection<TestResult> Results {get; set;}
}
public class PatientMap : EntityTypeConfiguration<Patient>
{
public PatientMap()
{
HasMany(p => p.Doctors)
.WithMany(d => d.Patients)
.Map(x => {
x.ToTable("DoctorPatient");
x.WithLeftKey("PatientId");
x.WithRightKey("DoctorId");
});
}
}
public class TestResult
{
[Key]
public int ResultId {get; set;}
public int PatientId {get; set;}
[ForeignKey("PatientId")]
public virtual Patient Patient {get; set;}
}
And the SQL just for clarity:
create table Doctor(
DoctorId int not null primary key,
Name nvarchar(50) not null
)
create table Patient(
PatientId int not null primary key,
)
create table DoctorPatient(
DoctorId int not null,
PatientId int not null,
primary key (DoctorId, PatientId),
foreign key (DoctorId) references Doctor(DoctorId),
foreign key (PatientId) references Patient(PatientId)
)
create table TestResult(
ResultId int not null primary key,
PatientId int not null,
foreign key (PatientId) references Patient(PatientId)
)