Linq union with group by perrformnces issue - c#

I have 3 Tables defined as below:
public Class Product
{
public long Key {get; set;}
public CountryKey {get; set;}
}
public Class ProductCountry
{
pullic long Key {get; set;}
public long ProductKey {get; set;}
public LocalProductKey {get; set;}
public long CountryKey {get; set;}
}
public Class Country
{
public long Key {get; set;}
public string Name {get; set;}
}
I'm using EF database first, for each class we have a view to get data from database ( VW_Product to get the products)
I have a method filtering a collection of product depends on a criteria.
IQueryable<VW_Product> query1 = FilterQuery(Object criteria); gets all products matching the criteria;
Now I want concat the filtred collection by adding the folowing collection:
var countriesKey = new List<long>() {45, 36, 6974, 366,....};
var keys = Context.VW_ProductCountries
.GroupBy(pc => pc.ProductKey)
.Where(grp => grp.Any(pc => countriesKey.Contains(pc.CountryKey) && !grp.Any(x => x.LocalProductKey != null)))
.SelectMany(grp => grp.Select(pc => pc.Productkey))
.Distinct();
var query2 = Context.VW_Product.Where(p => keys.Contains(p.ProductKey));
var result = query1.Concat(query2);
Is the another way to improve this query because it takes a lot of time to execute.

Try adding a .ToList() after .Distinct() to transform the list of keys from an ObjectQuery to an in-memory object. In query2, you're running multiple .Contains() against the 'keys' ObjectQuery, possibly executing the query every time.
Also, i presume
var result = query1.Concat(query1);
should be
var result = query1.Concat(query2);

Related

Select&Distinct into new object with DLinq

Suppose that I have list of following objects
public class OrderInfo
{
public string OrderNo {get; set;}
public DateTime OrderDate {get; set;}
public decimal OrderAmount {get; set;}
public decimal OrderPrice {get; set;}
}
as
List<OrderInfo> data = new List<OrderInfo>();
and this list has necessary number of items in it.
I need to select distinct OrderNo, OrderDate pairs from this List. In Linq I can write a query as:
var q = (from x in data
group x by new { x.OrderNo, x.OrderDate }
into grp
select new
{
grp.Key.OrderNo,
grp.Key.OrderDate
}).Distinct();
The thing is that I am writing a generic method so the object type and properties are unknown. So I have to write this with DLinq.
If I have a list of List<T> and a string[] { "OrderNo", "OrderDate" } how can I apply this example with DLinq at the run-time?
It was my fault. I was Googling for "DLinq" but not "Dynamic LinQ". Actually the answer was quite simple :
var q = data.Select("new (OrderNo, OrderDate)").Distinct();

EF & LINQ: Get object from database cell

I use Entity Framework Code First and I have three tables (for example):
public class Motorbike()
{
public int Id {get; set;}
public string Producent {get; set;}
public Engine Motor {get; set;}
public Tire Tires {get; set;}
}
public class Engine()
{
public int Id {get; set;}
public int Power {get; set;}
public decimal Price {get;set;}
}
public class Tire()
{
public int Id {get; set;}
public int Size {get; set;}
public decimal Price {get; set;}
}
It's just example, in fact it's more complicated.
Entity Frmaework generates table for me, but tables Motorbike has column: Id, Power, Engine_Id (where storing only number - id engine, not whole object) and Tire_Id (where storing only number - id tire, not whole object).
I know how to insert data - just create new Motorbike object, save to his fields data (for Engine and Tire fields I save whole objects not only id) and use .Add() method from my context.
But how to get data for row where motorbike id is (for example) 1?
I've tried something like this:
List<Motorbike> motorbikes= new List<Motorbike>();
var list = _context.Motorbike.Where(p => p.Id == 1);
motorbikes.AddRange(list);
but always I've got null for Engine and Tire fields (fields Id and Producent are fill properly).
Use Include to load related entities like:
var list = _context.Motorbike
.Include(m=> m.Engine)
.Include(m=> m.Tire)
.Where(p => p.Id == 1);
See: Entity Framework - Loading Related Entities
You're looking for the Include() method.
List<Motorbike> motorbikes = _context.Motorbike
.Include(p => p.Engine)
.Include(p => p.Tire)
.Where(p => p.Id == 1)
.ToList();

Convert Sql query to Lambda Expression - Filter Child Objects and Return Parent Objects

I found a post exactly my problem, but does not have a correct answer.
Click here
"I want filtered out" parent objects Which have children que meet the criteria. Parent shouldnt be returned only BUT Those children Which meet the criteria."
Follows the sql query I want to convert lambda:
SELECT
*
FROM
RO
INNER JOIN
ROE
ON ROE.ROId = RO.Id
WHERE RO.TreeNode LIKE '.66.%' AND ROE.EnvironmentId = 3
I'm working entity framework. I have two entities: RO and ROE, follows entity struct:
public class RO
{
int ROId {get; set}
string ROName {get; set}
string TreeNode {get; set}
EntityCollection<ROE> ROEs {get; set}
}
public class ROE
{
int ROEId {get; set}
int ROId {get; set}
int environmentId {get; set}
string ROName {get; set}
}
I want filtered RO objects which have TreeNode StartsWith '.66.' AND children meet criteria EnvironmentId = 3. But returns a List each RO haves filtered List.
I'm trying:
var ros = ROs.Where(ro => ro.TreeNode.StartsWith('.66.') &&
ro.ROEs.Any(roe => roe.EnvironmentId == environmentId))
But this expression don't working, because the ROEs Collection isn't filtered.
My question was answered. But my problem is the "Includes" because it does not work when I try to recover some child collections. For example:
var ros = context.ROS.Include("T").Include("T.TOEs").Include("ROEs.EL")
.Where(ro => ro.TreeNode.StartsWith('.66.'))
.Select(ro => new
{
ro,
// "T" Object is working, but the child collection "T.TOEs" isn't working
T = ro.T,
// "ROEs" Collection is working, but the child collection "ROEs.EL" isn't working
ROEs = ro.ROEs.Where(roe => roe.EnvironmentId == environmentId)
})
.AsEnumerable()
.Select(x => x.ro)
.ToList();

Filter Sub-Collection of Collection

I have the following classes:
class Customer
{
public int IdCustomer {get; set}
public string Name {get; set;}
public virtual ICollection<Expertise> Expertises {get; set;}
}
class Expertise
{
public int IdExpertise {get; set;}
public string Description {get; set;}
public virtual ICollection<SubExpertise> SubExpertises {get; set;}
}
class SubExpertise
{
public int IdSubExpertise { get; set;}
public int IdExpertise {get; set;}
public string Description {get; set;}
public virtual Expertise Expertise {get; set;}
}
How I can filter a SubExpertise using a IQueryable, and keep the filter condition? Basically I want to filter by SubExpertise, but keeping the possibilty to add more filters in the query
Example: I wanna all customers that have a SubExpertise 'x' or 'y'. Keep in mind that SubExpertise is a sub-collection of a collection (Expertise). And that I can have new filters after this.
I think you're after a method to build the expressions dynamically. You could use a predicate builder to do the job. My favorite is this one.
Using this little gem you can do this:
var pred = PredicateBuilder.Create<SubExpertise>(s => s.Description == "x");
pred = pred.Or(s => s.Description == "y");
var customers = db.Customers
.Where(c => c.Expertises
.Any(e => e.SubExpertises.AsQueryable()
.Any(pred)));
e.SubExpertises must be cast to IQueryable because it's compile-time type is ICollection, and the extension method Any that fits ICollection doesn't accept an Expression but a Func, so it doesn't compile.
I use a pattern like this for resuable filters, defining the filters as Expressions
public static class SubExpertiseFilter
{
public static Expression<SubExpertise, bool> XorY =
se => se.Description == "X" || se.Description == "Y";
}
Then if I want to apply it:
public IQueryable<Expertise> ApplyXorYFilter(IQueryable<Expertise> expertises, bool appyXorY)
{
if(applyXorY)
{
expertises = expertises.Any(e => e.Subexpertises.AsQueryable().Any(SubExpertiseFilter.XorY);
}
return expertises;
}
Or just do it inline where you need it.
The "AsQueryable" is needed to use the expression inside another expression so that EF is happy with it, otherwise the compiler expects a function there and that isn't something EF can turn into SQL.
Suppose you have a list customers
var filteredCustomers = from customer in customers
where customer.Expertises.Any(e => e.Subexpertises.Any(s => IsXOrY(s)))
select customer;
You can add any filter conditions in IsXOrY: (and you should rename it)
bool IsXOrY(SubExpertise subExpertise)
{
check if the SubExpertise is 'x' or 'y'
check if the SubExpertise meets other criteria
return true if this SubExpertise meets all your criteria
}

Any of the properties equals any of a list of objects

I have a problem in Entity-Framework, using Code-First, that I couldn't solve.
Having entities of the type
public class Product {
public int ID {get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category {
public int ID {get; set;}
public virtual ICollection<Product> Products { get; set; }
// rest omitted
}
in my database, i try to get all Products that have at least one Category from a list of given Categories. I need an Expression as this expression is combined with other expressions later.
Ie. i tried:
var searchFor = new List<Category>{...};
var expression = product => product.Categories.Any(cat => searchFor.Contains(cat))
Executing this later against a DbContext
context.Products.Where(expression).ToList();
creates an exception stating mainly that This context supports primitive types only.
Changing it to
var expression = product => product.Categories.Any(
cat => searchFor.Any(d => d.ID == cat.ID));
to get rid of the object comparison didn't help. I'm stuck. How can I manage that?
You should get rid of List<Category>, replacing it with a list of IDs, like this:
// I'm assuming that ID is of type long; please fix as necessary
var searchFor = new List<long>{...};
var expression = product =>
product.Categories.Any(cat => searchFor.Contains(cat.ID))
If you've already got a list of categories, you can build a list of IDs outside the query:
var searchForIds = searchFor.Select(x => x.ID).ToList();
var query = context.Products
.Where(product => product.Categories
.Any(cat => searchForIds.Contains(cat.ID)));
I don't know that that will work, but it might. (Apologies for the indentation... it's just to avoid scrolling.)

Categories