oData query in c# - c#

I am new to both odata and c# and I cannot figure out how to translate URI query like the following:
http://services.odata.org/Northwind/Northwind.svc/Customers(10)/Orders?$expand=Order_Details
in c#, using the linq method syntax.
I tried this way:
var customer = context.Customers.Where( x => x.Id == 10 ).First();
foreach(var order in customer.Orders.Expand("Order_Details")){
//stuff
}
But customer.Orders does not have the "Expand" method.
How do I deal with these queries where I have to expand a navigation property connected to a specific entity?

First of all your code cannot compile. x => c.Id == 10 is wrong; also there is a closing paranthesis on your foreach missing.
Second, you need to include Orders in your customer variable to do this.
I am using this Northwind v3 service to demonstrate this (http://services.odata.org/V3/Northwind/Northwind.svc/) as well as LinqPad (www.linqpad.net)
var customer = Customers.Expand("Orders/Order_Details")
.Where(cus => cus.CustomerID == "ALFKI");
foreach (var element in customer)
{
element.Orders.Dump();
}
This results in an URL call like this one:
http://services.odata.org/V3/Northwind/Northwind.svc/Customers('ALFKI')?$expand=Orders/Order_Details
Output:
Larger Image
//EDIT
Based on the comments below. If you do not want to go over the customer table and expand orders AND order_details, you can do it like this as well:
var orders = Orders.Expand("Order_Details")
.Where(o => o.CustomerID == "ALFKI").Dump();
OR
var orders = Orders.Expand(ex => ex.Order_Details)
.Where(o => o.CustomerID == "ALFKI").Dump();

You will have to request for all the child properties you need as part of the OData query itself.
try following
context.Customers.Expand("Orders/Order_Details").Where(c => x => c.Id == 10 ).First();
foreach(var order in customer.Orders){
//stuff
}

Related

How to write linq query for this sql statement

How would you write a linq query with the following SQL statement. I've tried several methods referenced on stackoverflow but they either don't work with the EF version I'm using (EF core 3.5.1) or the DBMS (SQL Server).
select a.ProductID, a.DateTimeStamp, a.LastPrice
from Products a
where a.DateTimeStamp = (select max(DateTimeStamp) from Products where a.ProductID = ProductID)
For reference, a couple that I've tried (both get run-time errors).
var results = _context.Products
.GroupBy(s => s.ProductID)
.Select(s => s.OrderByDescending(x => x.DateTimeStamp).FirstOrDefault());
var results = _context.Products
.GroupBy(x => new { x.ProductID, x.DateTimeStamp })
.SelectMany(y => y.OrderByDescending(z => z.DateTimeStamp).Take(1))
Thanks!
I understand you would like to have a list of the latest prices of each products?
First of all I prefer to use group by option even over 1st query
select a.ProductID, a.DateTimeStamp, a.LastPrice
from Products a
where a.DateTimeStamp IN (select max(DateTimeStamp) from Products group by ProductID)
Later Linq:
var maxDateTimeStamps = _context.Products
.GroupBy(s => s.ProductID)
.Select(s => s.Max(x => x.DateTimeStamp)).ToArray();
var results = _context.Products.Where(s=>maxDateTimeStamps.Contains(s.DateTimeStamp));
-- all assuming that max datetime stamps are unique
I've managed to do it with the following which replicates the correlated sub query in the original post (other than using TOP and order by instead of the Max aggregate), though I feel like there must be a more elegant way to do this.
var results = from x
in _context.Products
where x.DateTimeStamp == (from y
in _context.Products
where y.ProductID == x.ProductID
orderby y.DateTimeStamp descending
select y.DateTimeStamp
).FirstOrDefault()
select x;
I prefer to break up these queries into IQueryable parts, do you can debug each "step".
Something like this:
IQueryable<ProductOrmEntity> pocoPerParentMaxUpdateDates =
entityDbContext.Products
//.Where(itm => itm.x == 1)/*if you need where */
.GroupBy(i => i.ProductID)
.Select(g => new ProductOrmEntity
{
ProductID = g.Key,
DateTimeStamp = g.Max(row => row.DateTimeStamp)
});
//// next line for debugging..do not leave in for production code
var temppocoPerParentMaxUpdateDates = pocoPerParentMaxUpdateDates.ToListAsync(CancellationToken.None);
IQueryable<ProductOrmEntity> filteredChildren =
from itm
in entityDbContext.Products
join pocoMaxUpdateDatePerParent in pocoPerParentMaxUpdateDates
on new { a = itm.DateTimeStamp, b = itm.ProductID }
equals
new { a = pocoMaxUpdateDatePerParent.DateTimeStamp, b = pocoMaxUpdateDatePerParent.ProductID }
// where
;
IEnumerable<ProductOrmEntity> hereIsWhatIWantItems = filteredChildren.ToListAsync(CancellationToken.None);
That last step, I am putting in an anonymous object. You can put the data in a "new ProductOrmEntity() { ProductID = pocoMaxUpdateDatePerParent.ProductID }...or you can get the FULL ProductOrmEntity object. Your original code, I don't know if getting all columns of the Product object is what you want, or only some of the columns of the object.

Using .Where() clause inside an .Include() on a linq query with multiples includes

I would like to get a collection of Customers including several properties among which is the address but only when it has not been deleted yet (SuppressionDate == null)
IQueryable<Customer> customers =
context.Persons.OfType<Customer>()
.Include(customer => customer.Addresses)
.Include(customer => customer.Bills)
.Include(customer => customer.Code)
.Include(customer => customer.Tutors);
I have tried several ways to use the where clause in order to filter address:
...
.Include(customer => customer.Addresses.Where(a => a.SuppressionDate == null))
.Include(customer => customer.Bills)
...
That was my first try but it raises the following exception:
System.ArgumentException : 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
I've also tried with the same where clause at the end of the Include() and at the end of the query but neither seems to work.
I'm currently using a workaround which is iterate through the collection of customer and remove the addresses that are deleted as such:
foreach(Customer c in customers){
customer.Addresses = customer.Addresses.Where(a => a.SuppressionDate == null).ToList();
}
Being fairly new to linq to object/entities, I was wondering if there was a built-in way to achieve this.
If you were getting a single customer you could use explicit loading like this:
var customer = context.Persons
.OfType<Customer>()
.Include(customer => customer.Bills)
.Include(customer => customer.Code)
.Include(customer => customer.Tutors)
.FirstOrDefault(); //or whatever
context.Entry(customer).Collections(x => x.Addresses).Query().Where(x => x.SuppressionDate == null).Load();
That makes a nice query and two simple calls to the database. But in this case you are getting a list (or collection or whatever) of customer and there's no shortcuts. Your "workaround" may cause a lot of chatter with the database.
So you probably just have to take it one step at time:
//1. query db to get customers
var customers = context.Persons
.OfType<Customer>()
.Include(customer => customer.Bills)
.Include(customer => customer.Code)
.Include(customer => customer.Tutors)
.ToList();
//2. make an array of all customerIds (no db interation here)
var customerIds = customers.Select(x => x.CustomerId).ToArray();
//3. query db to get addresses for customers above
var addresses = context.Addresses.Where(x => customerIds.Contains(x.CustomerId).ToList();
//4. assign addresses to customers (again, no db chatter)
foreach (var customer in customers)
{
customer.Addresses = addresses
.Where(x => x.CustomerId == customer.CustomerId && x.SuppressionDate == null)
.ToList();
}
Not too bad- still just two queries to database.

Convert this LINQ to Lambda Expression?

I have a LINQ, it works fine. My question is: how to convert it to Lambda Expression?
var searchResults = from study in dataContext.Studies
join location in dataContext.Locations
on study.LocationID equals location.LocationID
join doctorLocation in dataContext.DoctorLocations
on location.LocationID equals doctorLocation.LocationID
join doctor in dataContext.Doctors
on doctorLocation.DoctorID equals doctor.DoctorID
where doctor.DoctorID == doctorId
select study;
I think LINQ is more natural to me (similar to SQL script). However, in this case, I just want to convert it to Lambda Expression, but I could not make it work.
I got stuck at:
var searchResults = dataContext.Studies.Where(x =>
x.Location.DoctorLocations.FirstOrDefault() != null &&
x.Location.DoctorLocations.FirstOrDefault().DoctorID == doctorId);
This only works for FirstOrDefault. Since there are multiple DoctorLocations, I do not how to write this one.
Try this:
var searchResults = dataContext.Studies.Where(x =>
x.Location != null
&& x.Location.DoctorLocations.Any(dl => dl.DoctorID == doctorId));
you will get all Studies related to at least one DoctorLocation with DoctorID equals doctorId
var searchResults = dataContext.Studies
.Include(x => x.Locations)
.Include(x => x.DoctorLocations)
.Include(x => x.Doctors)
.Where(x => x.[InheritedPathToDoctor].DoctorId == id)
.Select(x => x.[InheritedPathToStudy].Study)
.FirstOrDefault() OR .ToList()
I have made a lot of assumptions here as to how you have set up your context. I've assumed it's a relational database and therefore the includes simply means it returns all data. I haven't tested it though so there are probably a few errors.
You require an include for every class and the where is pretty self explanatory.

LINQ Include Extension - Drill Down

I am able to use the strongly typed LINQ Extension : .Include
result = (from A in context.Transactions.Include(_ => _.TransactionDetails)
where A.TransactionId == transactionId
select A).SingleOrDefault();
However I cannot go further within TransactionDetails. My TransactionDetails also have a navigation named User however I don't know how to put it. The available options I have inside TransactiomDetails are the regular extensions for Collections (e.g First ; FirstOrDefault,etc).
I was able to do it using the regular string method (That I want to avoid) :
result = (from A in context.Transactions.Include(_ => _.TransactionDetails)
.Include("TransactionDetails.User")
where A.TransactionId == transactionId
select A).SingleOrDefault();
Thanks
Use this:
result = context.Transactions
.Where(t => t.TransactionId == transactionId)
.Include(t => t.TransactionDetails.Select(u => u.User))
.FirstOrDefault();

"OR" on-demand with Linq to Entities, how can i do it?

I have the following query that performs a "AND" on-demand:
var products = // Selecting a list of products in anywhere based in a filter...
foreach (var product in products)
{
query = query.Where(p => p.Code == product.Code); // with this way, this query make no sense because this happens in any scenario, never a code can be more than one code.
}
So, how can i do the same query but performing a "OR" on-demand (so that the query makes sense)?
You can use the facsimile of an IN for LINQ:
var productCodeList = products.Select(p => p.Code).ToList();
query = query.Where(p => productCodeList.Contains(p.Code));
It's basically saying:
SELECT *
FROM products
WHERE code IN (<list_of_codes>)
Using Contains:
var codes = products.Select(x => x.Code).ToArray();
query.Where(p => codes.Contains(p.Code));
Either use the Contains method ad Brad and Joe wrote, or (when that's not possible) use the PredicateBuilder:
var predicate = PredicateBuilder.False<Product>();
foreach (var product in products)
{
var code = product.Code;
predicate = predicate.Or(p => p.Code == code);
}
var products dataContext.Products.Where(predicate);

Categories