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);
Related
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.
I have a method with the following signatures that return a C# expression
Expression<Func<T, bool>> GetExpression<T>(IList<Filter> filters)
Then the following code that uses Dynamic LINQ
using (TestContext tEntities = new TestContext())
{
var filterExp = Exp.ExpressionBuilder.GetExpression<Client>(filters);
var filteredCollection = tEntities.Client.Where(filterExp);
IQueryable<Client> queryResult;
if (filterExp == null)
queryResult = tEntities.Client;
else
queryResult = tEntities.Client.Where(filterExp);
}
This is a simple scenario.
I have queries that are 50 lines long sometimes more. I want to avoid having the same code twice with only difference using the where clause.
Does anyone knows if I achieve something different?
from product in context.Product.Where(deleg)
.Include(x => x.Type)
.Include(x => x.Category)
.Include(x => x.WareHouse)
.Include(x => x.Photos)
join f in context.Favorite on product.Id equals f.ProductFid into fg
from fgi in fg.Where(f => f.UserFid == userId).DefaultIfEmpty()
orderby product.Id descending
select new ProductngDto()
{
ProductItem = product,
FavoriteId = fgi != null ? fgi.Id : (long?)null
}).Skip(page * pageSize).Take(pageSize);
Thanks in advance
One of the nice things about IQueryable<T> and IEnumerable<T> is the fact that they are so abstract, you can easily chain them together. One solution could be to structure your code like this:
using (TestContext tEntities = new TestContext())
{
var filterExp = Exp.ExpressionBuilder.GetExpression<Client>(filters);
var filteredCollection = tEntities.Client.Where(filterExp);
IQueryable<Client> queryResult = tEntities.Client;
if (filterExp != null)
{
queryResult = queryResult.Where(filterExp);
}
//do something else with queryResult
}
This way you can continue using queryResult without having to know, or even care, about whether or not filterExp was applied.
For the second example, shuffling it around could look something like this:
var query = from p in context.Product
.Include(x => x.Type)
.Include(x => x.Category)
.Include(x => x.WareHouse)
.Include(x => x.Photos);
if (deleg != null)
{
query = query.Where(deleg);
}
query = from product in query
join f in context.Favorite on product.Id equals f.ProductFid into fg
from fgi in fg.Where(f => f.UserFid == userId).DefaultIfEmpty();
orderby product.Id descending
select new ProductngDto()
{
ProductItem = product,
FavoriteId = fgi != null ? fgi.Id : (long?)null
}).Skip(page * pageSize).Take(pageSize);
Another option you have, is to check the filterExp for null and assign an "always true" lambda to it.
If you control the GetExpression<T> method, you could add it in there as a last step. If you don't control it, you could do the null checking in the methods where you use it's result.
This will allow you to keep your code looking a bit cleaner, but the trade-off is a small performance hit for having to evaluate the lambda all the time.
Depending on how your expressions are typed, this might be as easy as:
if (filterExp == null)
{
filterExp = (_) => true;
}
SELECT
[TimeStampDate]
,[User]
,count(*) as [Usage]
FROM [EFDP_Dev].[Admin].[AuditLog]
WHERE [target] = '995fc819-954a-49af-b056-387e11a8875d'
GROUP BY [Target], [User] ,[TimeStampDate]
ORDER BY [Target]
My database table has the columns User, TimeStampDate, and Target (which is a GUID).
I want to retrieve all items for each date for each user and display count of entries.
The above SQL query works. How can I convert it into LINQ to SQL? Am using EF 6.1 and my entity class in C# has all the above columns.
Create Filter basically returns an IQueryable of the entire AuditLogSet :
using (var filter = auditLogRepository.CreateFilter())
{
var query = filter.All
.Where(it => it.Target == '995fc819-954a-49af-b056-387e11a8875d')
.GroupBy(i => i.Target, i => i.User, i => i.TimeStamp);
audits = query.ToList();
}
Am not being allowed to group by on 3 columns in LINQ and I am also not sure how to select like the above SQL query with count. Fairly new to LINQ.
You need to specify the group by columns in an anonymous type like this:-
var query = filter.All
.Where(it => it.Target == '995fc819-954a-49af-b056-387e11a8875d')
.GroupBy(x => new { x.User, x.TimeStampDate })
.Select(x => new
{
TimeStampDate= x.Key.TimeStampDate,
User = x.Key.User,
Usage = x.Count()
}).ToList();
Many people find query syntax simpler and easier to read (this might not be the case, I don't know), here's the query syntax version anyway.
var res=(from it in filter.All
where it.Target=="995fc819-954a-49af-b056-387e11a8875d"
group it by new {it.Target, it.User, it.TimeStampDate} into g
orderby g.Key.Target
select new
{
TimeStampDate= g.Key.TimeStampDate,
User=g.Key.User,
Usage=g.Count()
});
EDIT: By the way you don't need to group by Target neither OrderBy, since is already filtered, I'm leaving the exact translation of the query though.
To use GroupBy you need to create an anonymous object like this:
filter.All
.Where(it => it.Target == '995fc819-954a-49af-b056-387e11a8875d')
.GroupBy(i => new { i.Target, i.User, i.TimeStamp });
It is unnecessary to group by target in your original SQL.
filter.All.Where( d => d.Target == "995fc819-954a-49af-b056-387e11a8875d")
.GroupBy(d => new {d.User ,d.TimeStampDate} )
.Select(d => new {
User = d.Key.User,
TimeStampDate = d.Key.TimeStampDate,
Usage = d.Count()
} );
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
}
I have this expression, which generates single query to the database:
db = new MyDataContext();
var productInCity = db.Products
.Where(n => n.id == 2)
.Select(k => new ProductInCityDto()
{
ProductName = k.ProductName,
CityName = k.Store.City.Name,
CountryName = k.Store.City.Country.Name
.
.
.
})
.FirstOrDefault();
I want to make this code cleaner, by putting the mapping in a function, extension method or in the object's constructor, something like this:
db = new MyDataContext();
var productInCity = db.Products
.Where(n => n.id == 2)
.Select(k => new ProductInCityDto(k))
.FirstOrDefault();
But, in this case, multiple queries to the DB are generated (I use LinqToSql Profiler).
Is there a way to isolate the mapping (Select statement) in order to achieve better code readability?
YES, if you look at the actual signature of the Select extension method on IQueryable you will find that it does not take a function but an Expression>.
So, just do that...
Expression<Func<Product, ProductInCityDto>> MyMappingExpression
{
get
{
return product => new ProductInCityDto
{
...
}
}
}
and then
db = new MyDataContext();
var productInCity = db.Products.Where(n => n.id == 2)
.Select(MyMappingExpression)
.FirstOrDefault();
If you need to use MyMappingExpression in process you will likely want to convert it to a
Func<Product, ProductInCityDto>
by calling the Expression.Compile() method.
Instead of creating mappings by hand, you may use AutoMapper. But if you do not want to create the mapping using a third party tool just change the query to the following;
var productInCity = new ProductInCity(
db.Products.Include("Store").SingleOrDefault(n => n.id == 2));