Example scenario:
Two tables: order and orderItem, relationship One to Many.
I want to select all orders that have at least one orderItem with price 100 and at least one orderItem with price 200.
I can do it like this:
var orders = (from o in kontextdbs.orders
join oi in kontextdbs.order_item on o.id equals oi.order_id
join oi2 in kontextdbs.order_item on o.id equals oi2.order_id
where oi.price == 100 && oi2.price == 200
select o).Distinct();
But what if those conditions are user generated?
So I dont know how many conditions there will be.
You need to loop through all the values using a Where and Any method like this:
List<int> values= new List() { 100, 200 };
var orders = from o in kontextdbs.orders
select o;
foreach(int value in values)
{
int tmpValue = value;
orders = orders.Where(x => kontextdbs.order_item.Where(oi => x.id == oi.order_id)
.Any(oi => oi.price == tmpValue));
}
orders = orders.Distinct();
List<int> orderValues = new List() { 100, 200 };
ObjectQuery<Order> orders = kontextdbs.Orders;
foreach(int value in orderValues) {
orders = (ObjectQuery<Order>)(from o in orders
join oi in kontextdbs.order_item
on o.id equals oi.order_id
where oi.price == value
select o);
}
orders = orders.Distinct();
ought to work, or at least that's the general pattern - you can apply extra queries to the IObjectQueryables at each stage.
Note that in my experience generating dynamic queries like this with EF gives terrible performance, unfortunately - it spends a few seconds compiling each one into SQL the first time it gets a specific pattern. If the number of order values is fairly stable though then this particular query ought to work OK.
Related
I have one list, read from file:
var lsData = ReadExcelFile<CustomerEntity>(path);
And one Object (loaded into memory):
lsCustomer = await CustomerService.GetAll()
.Where(c => c.isDeleted == null || !c.isDeleted.Value)
.OrderBy(c=> c.Code)
.ToListAsync();
And the join command:
var lsDuplicateEmail =
(from imp in lsData
join cust in lsCustomer
on ImportHelpers.GetPerfectStringWithoutSpace(imp.Email) equals ImportHelpers.GetPerfectStringWithoutSpace(cust.Email)
into gjoin
from g in gjoin.DefaultIfEmpty()
select new
{
ImportItem = imp,
CustomerItem = g,
}
into result
where !string.IsNullOrEmpty(result.ImportItem.Email) && result.CustomerItem != null
&& !ImportHelpers.CompareString(result.ImportItem.Code, result.CustomerItem.Code)
select result);
var lsDuplicateEmailInSystem = lsDuplicateEmail.Select(c => c.ImportItem.Code).Distinct().ToList();
I perform test with lsData list about 2000 records, lsCustomer about 200k records.
The Customer Email field is not indexed in the DB.
The join command executes with about 10s (even though the result is 0 records), too slow.
I've looked around and can't seem to index the email field in lsCustomer. I know the reason for the slowness is because the complexity is O(n*m).
Is there any way to improve performance?
Try the following code. Instead of GroupJoin, which is not needed here I have used Join. Also moved filters up in query.
var lsDuplicateEmail =
from imp in lsData
where !string.IsNullOrEmpty(imp.Email)
join cust in lsCustomer
on ImportHelpers.GetPerfectStringWithoutSpace(imp.Email) equals ImportHelpers.GetPerfectStringWithoutSpace(cust.Email)
where !ImportHelpers.CompareString(imp.Code, cust.Code)
select new
{
ImportItem = imp,
CustomerItem = cust,
};
Also show GetPerfectStringWithoutSpace implementation, maybe it is slow.
Another possible solution is to swap lsData and lsCustomer in query, maybe lookup search is not so fast.
I try to create groupings of lastReadings, where I can create client’s and competitor’s prices. I try below code, but this approach does not eliminate the readings that are only Client’s. With below table I need to eliminate the readingsId 5 and 6, for products C and D as there is no match and pass further only the comparable ones.
readingId ProductId Distributor Price
1 A Competitor 8.0
2 A Client 8.1
3 B Competitor 8.3
4 B Client 8.4
5 C Client 8.8
6 D Client 8.9
Below is what I get so far:
private IEnumerable<PriceComparison> getPriceComparisons(string competitor)
{
IEnumerable<IGrouping<string, LatestReading>> groupingsByProductId =
from latestReading in LatestReadings
group latestReading by latestReading.ProductId;
IEnumerable<PriceComparison> priceComparisons
= from grouping in groupingsByProductId
select new PriceComparison
{
ProductId = grouping.Key,
MyPrice = (from latestReading in grouping
where latestReading.Distributor == Client
select latestReading.Price).FirstOrDefault(),
CompetitorPrice = (from latestRading in grouping
where latestRading.Distributor == competitor
select latestRading.Price).FirstOrDefault()
};
return priceComparisons;
}
Actually as I write this post, I concluded that for the additional created groupings with "no competitor", competitor price is there as 0, so later I can easily eliminate such groupings and the code works. But somehow this approach of creating "empty" groupings does not feel right, is there a better way to only focus on the groupings excluding products C and D?
I think the Join is more suitable for what you are trying to achieve, like this
var priceComparison =
from a in LatestReadings.Where(r => r.Distributor == Client)
join b in LatestReadings.Where(r => r.Distributor == Competitor)
on a.ProductId equals b.ProductId
select new PriceComparison
{
ProductId = a.ProductId,
MyPrice = a.Price,
CompetitorPrice = b.Price
};
You can do something like this:
//...
IEnumerable<PriceComparison> priceComparisons =
from grouping in groupingsByProductId
where grouping.Any(p => p.Distributor == Client)
&& grouping.Any(p => p.Distributor == competitor)
select new PriceComparison
{
//..
};
This makes sure that you get only the groups that have a price for both Client and Competitor.
How do you suppose I tackle this? Basically, I have this inital query:
var orders = (from order in _dbContext.Orders
join orderDetail in _dbContext.OrderDetails on order.ID equals orderDetail.OrderID
where order.StoreID == storeID
select new Order
{
ID = order.ID,
No = order.ID,
Type = "", // Notice that this is empty; this one needs updating
Quantity = order.Quantity,
// more properties here
}).AsQueryable();
After this query, I need to loop through the result and update the Type property based on different criteria like this:
string type = "";
foreach (OrderDetailDto order in orders)
{
if (order.UserID != null)
type = "UserOrder";
else if (order.UserID == null)
type = "NonUserOrder";
else if (order.Cook == null && (order.Option == "fiery"))
type = "FieryCook";
else if (check if this has corresponding records in another table) // this part I don't know how to effectively tackle
type = "XXX";
// Update.
order.Type = type;
}
The problem is one of my criteria needs me to check if there are existing record in the database. I would use JOIN but if I have to loop thru several hundreds or thousands of records and then JOIN each one of them then check on db just to get one value, I think that would be very slow.
I can't do the JOIN on the initial query because I might do a different JOIN based on a different criterion. Any ideas?
You could just join all the lookup tables you might possibly need in left join type way:
from o in Orders
from c in Cooks.Where(x => x.OrderId == m.OrderId).DefaultIfEmpty()
from u in Users.Where(x => x.OrderId == o.OrderId).DefaultIfEmpty()
select new
{
Order = m,
Cook = c,
User = u
}
or depending on your usage patterns you could build the required tables into local Lookups or Dictionaries for linear time searching thereafter:
var userDict = Users.ToDictionary(x => x.UserId);
var userIdDict = Users.Select(x => x.UserId).ToDictionary(x => x);
var cooksLookup = Cooks.ToLookup(x => x.Salary);
i've got a List of objects, lets call them Product, which each of them contains a bunch of properties and also a List of Version (which are also objects).
Version also has a bunch of properties and does contain a List of Customer (which again are objects).
Customer again has properties, one of them is its ID (=Guid).
What i try to do is to make a List of Product, selected by a certain ID of its Product.VersionList.Version.ID.
I would prefere a join query, but every efficient way is welcome. I tried so far this, but because i have only a single ID to compare with, i don't know how to construct the join.
lp = List<Entity.Product>;
g = GetGuid();
var query = from product in Entity.ProductCollection
join g in g
on product.Version.Where(x => x.id == g)
select product;
lp.AddRange(query);
I'm guessing you mean:
var query = from product in Entity.ProductCollection
where product.Version.Any(x => x.id == g)
select product;
i.e. select all the products that have a version where the id matches the guid you were thinking of.
Note that joining to the versions would cause product duplication if any product has multiple matching versions.
Try this .... May be you wants more deep digging on it..
var query = from Product product in pc
from varsion in product.Version
let v= varsion as Entity.Version
where v.id == g
select product;
var query = Entity.ProductCollection.Where(p => p.Version.Any(v => v.Id == g));
You can use Any rather than having to do a self join.
Let's say I have a table of Orders and a table of Payments.
Each Payment relates to a given order: Payment.orderId
I want to query my orders:
var query = from o in db.Orders where o.blah = blah select o;
But I also need the total paid for each order:
var query = from o in db.Orders
where o.blah = blah
select new
{
Order = o,
totalPaid = (from p in db.Payments
where p.orderId == o.id
select p.Amount).Sum()
};
LINQ to SQL generates exactly the SQL query I want.
My problem is that I'm adding payments support to an existing app. Therefore, to minimize code impact, I'd like totalPaid to be a property of my Order class.
I thought of adding a "manual" property and tried to fill it at query time.
But writing the select clause is where I'm stuck:
var query = from o in db.Orders
where o.blah = blah
select "o with o.totalPaid = (from p in db.Payments <snip>).Sum()"
How can I do that?
Normally, your Order class will have a navigation property called Payments. Utilizing this, your TotalPaid property would look like this:
public double TotalPaid
{
get { return Payments.Sum(x => x.Amount); }
}
This solves another problem: This property is always up-to-date. Your approach would be outdated as soon as a new payment is added to the order.
If being up-to-date isn't so important but reducing the number of round trips to the database, you can use this code:
private double? _totalPaid;
public double TotalPaid
{
get
{
if(!_totalPaid.HasValue)
_totalPaid = Payments.Sum(x => x.Amount);
return _totalPaid.Value;
}
}
You can add a payments EntitySet int the Orders class that points to the Payments class, as well as declare the TotalPaid property as suggested from Daniel Hilgarth.
But when you query the database for the Orders, LinqToSql will make 1 query for each order, in order to get the sum of payments. The workaround is to use the DataContext.LoadWith() method like this:
DataContext db = new Datacontext(connStr);
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Orders>(o=>o.Payments);
db.LoadOptions = dlo;
var query = from o in db.Orders
where o.blah = blah
//this will load the payments with the orders in a single query