Suppose I have a list of {City, State}. It originally came from the database, and I have LocationID, but by now I loaded it into memory. Suppose I also have a table of fast food restaurants that has City and State as part of the record. I need to get a list of establishments that match city and state.
NOTE: I try to describe a simplified scenario; my business domain is completely different.
I came up with the following LINQ solution:
var establishments = from r in restaurants
from l in locations
where l.LocationId == id &&
l.City == r.City &&
l.State == r.State
select r
and I feel there must be something better. For starters, I already have City/State in memory - so to go back to the database only to have a join seems very inefficient. I am looking for some way to say {r.City, r.State} match Any(MyList) where MyList is my collection of City/State.
UPDATE
I tried to update based on suggestion below:
List<CityState> myCityStates = ...;
var establishments =
from r in restaurants
join l in myCityStates
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
and I got the following compile error:
Error CS1941 The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.
UPDATE 2
Compiler didn't like anonymous class in the join. I made it explicit and it stopped complaining. I'll see if it actually works in the morning...
It seems to me that you need this:
var establishments =
from r in restaurants
join l in locations.Where(x => x.LocationId == id)
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
Well, there isn't a lot more that you can do, as long as you rely on a table lookup, the only thing you can do to speed up things is to put an index on City and State.
The linq statement has to translate into a valid SQL Statement, where "Any" would translate to something like :
SELECT * FROM Restaurants where City in ('...all cities')
I dont know if other ORM's give better performance for these types of scenarios that EF, but it might be worth investigating. EF has never had a rumor for being fast on reads.
Edit: You can also do this:
List<string> names = new List { "John", "Max", "Pete" };
bool has = customers.Any(cus => names.Contains(cus.FirstName));
this will produce the necessary IN('value1', 'value2' ...) functionality that you were looking for
Related
I have an issue of using group by in LINQ to SQL statement.
The cod I have is
var combinedItems = (from article in articles
join author in authors
on article.AuthorId equals author.Id into tempAuthors
from tempAuthor in tempAuthors.DefaultIfEmpty()
select new { article , author = tempAuthor});
var groups1 = (from combinedItem in combinedItems
group combinedItem by combinedItem.article into g
select g.Key).ToList();
var groups2 = (from combinedItem in combinedItems
group combinedItem by combinedItem.article.Id into g
select g.Key).ToList();
I tried to group in two different ways. The first way, I group by an object and the second way I just group by a field in one of the objects.
When I run groups1, I got an error saying need to evaluate in client side, while when I use groups2, it works all good. Can I ask what could be wrong? If I want to group by object, is there any way to do it?
In case you want to group by object, as you've not overridden Equals and GetHashCode() in your Article class or implemented IEqualityComparer<Article> you're just getting the default comparison, which checks if the references are equal. So what you need is something like this:
class GroupItemComparer : IEqualityComparer<Article>
{
public bool Equals(Article x, Article y)
{
return x.Id == y.Id &&
x.Name == y.Name;
}
public int GetHashCode(Article obj)
{
return obj.Id.GetHashCode() ^
obj.Name.GetHashCode();
}
}
And then you need to change your query to lambda expression:
var groups1 = combinedItems.GroupBy(c => c.article , new GroupItemComparer())
.Select(c => c.Key).ToList();
In case you got any exception regarding translation your method to SQL, you can use AsEnumerable or ToList methods before your GroupBy method, with this methods after data is loaded, any further operation is performed using Linq to Objects, on the data already in memory.
As others have pointed out, the GroupBy is using reference equality by default, and you could get around it by specifying one or more properties to group by. But why is that an error?
The whole point of the query is to translate your Linq query into SQL. Since object reference equality on the client can't be easily translated to SQL, the translator doesn't support it and gives you an error.
When you provide one or more properties to group by, the provider can translate that to SQL (e.g. GROUP BY article.Id), and thus the second method works without error.
I need to write some linq (linq-to-sql) for a search page that allows the user to search for cars and optionally include search criteria for the car's parts. The two tables are CAR and CAR_PARTS. Here is what I have so far:
var query = db.CAR;
//if the user provides a car name to search by, filter on car name (this works)
if(model.CarName != "")
{
query = from c in query
where c.Name == model.CarName
select c;
}
//if the user provides a car part name to filter on, join the CAR_PART table
if(model.CarPartName != "")
{
query = from c in query
join parts in db.CAR_PARTS on c.ID equals parts.CarID
where parts.PartName == model.CarPartName
select c;
}
//if the user provides a car part code to filter on, join the CAR_PART table
if(model.CarPartCode != "")
{
query = from c in query
join parts in db.CAR_PARTS on c.ID equals parts.CarID
where parts.PartCode == model.CarPartCode
select c;
}
If the user decides they want to search on both CarPartName and CarPartCode, this logic would result in the CAR_PART table being joined twice. This feels wrong to me, but is this the correct way to handle this?
How would you write this?
It's legal to do so, but whether it makes sense, depends on your datamodel and your desired outcome.
Generally your code does the following if partname and partcode are defined
Join the cars table with the parts table with partname as join condition
Join the result of the first join again with the parts table with partcode as join condition.
Thus, this is equal to a join with join condition car.partname = part.name and car.partcode = part.code. I don't know, whether this is your desired behaviour or not.
There are some cases to distinguish
Joining with AND condition
CASE 1.1: name and code of a part are keys in the parts table
In this case for each name and code are each unique in the parts table, thus for each name there is exactly one code. The double join is not necessary, and may even lead to wrong results, because
if selected name and code identify the same part, it's the first join will already get the desired results
if name and code identifiy different parts, your result will be empty because the condition cannot be fullfilled.
In that situation I would suggest to write is as follows
if (!string.IsNullOrEmpty(model.CarPartName)){
// your join on partname
} else if (!string.IsNullOrEmpty(model.CarPartCode)) {
// your join on partcode
}
CASE 1.2: name and code of a part are NOT keys in the parts table
In this case, neither name nor code may be unique, and for one name there may be different codes and vice versa. Here the double join is necessary and will only return results containing parts which match both, name and code
Joining with OR condition
If on the other hand you want your join condition to be like car.partname = part.name and car.partcode = part.code you have to consider the following cases
CASE 2.1 name and code are keys
Here applies the same as above in case 1.1
CASE 2.2 name and code are NOT keys
Here you can't use the stepwise approach, because the result of the first join will only contain cars, where the name matches. There may be parts where only the code condition matches, but they can never be included in the final result, if they are not contained in the result of the first match. So in this case, you will have to define your query something like this
if (!string.IsNullOrEmpty(model.CarPartName) && !string.IsNullOrEmpty(model.CarPartCode)) {
query = from c in query
join parts in db.CAR_PARTS on c.ID equals parts.CarID
where parts.PartName == model.CarPartName || parts.PartCode == model.CarPartCode
select c;
} else if (!string.IsNullOrEmpty(model.CarPartName)) {
query = from c in query
join parts in db.CAR_PARTS on c.ID equals parts.CarID
where parts.PartName == model.CarPartName
select c;
} else if (!string.IsNullOrEmpty(model.CarPartCode)) {
query = from c in query
join parts in db.CAR_PARTS on c.ID equals parts.CarID
where parts.PartCode == model.CarPartCode
select c;
}
What is wrong in there is actually with proper relations you don't need the join at all. Add that the behavior of LinqToSQL you can write that as:
var query = db.CAR
.Where( c =>
( string.IsNullOrEmpty(model.CarName)
|| c.Name == model.CarName ) &&
( string.IsNullOrEmpty(model.CarPartName)
|| c.Parts.Any( p => p.PartName == model.CarPartName )) &&
( string.IsNullOrEmpty(model.CarPartCode)
|| c.Parts.Any( p => p.PartCode == model.CarPartCode )));
Yours would work provided query is IQueryable (db.CAR.AsQueryable()). The two Linq approaches are similar but not the same. Depending on your real necessity yours might be the correct one or the wrong one. Yours would produce two inner joins, while this one simply create 2 exists check. Assume you have:
Car, Id:5, Name: Volvo
And parts like:
CarID:5, PartName:HeadLights, PartCode:1 ... other details
CarID:5, PartName:HeadLights, PartCode:2 ... other details
CarID:5, PartName:HeadLights, PartCode:3 ... other details
If user asks with model.CarName = "Volvo" and model.PartName = "HeadLights", you would get back the same Volvo 3 times. In second approach, you get back a single Volvo.
HTH
I feel more comfortable with fluent syntax, but I'm sure something similar to the following will work for you. I would check the fields in your model as part of a Select statement and then conditionally join using one field or the other. If neither are set, leave it null.
var query = db.CAR;
if (!string.IsNullOrWhitespace(model.CarName))
{
query = query.Where(car => car.Name == model.CarName);
}
var items = query.Select(car => new
{
Car = car, // maybe better to split this up into different fields, but I don't know what the car object looks like
// I assume your Car entity model has a navigation property to parts:
CarPart = !string.IsNullOrWhitespace(model.CarPartName)
? car.Parts.FirstOrDefault(part => part.PartName == model.CarPartName)
: !string.IsNullOrWhitespace(model.CarPartCode)
? car.Parts.FirstOrDefault(part => part.PartCode == model.CarPartCode)
: null
})
.ToList();
This does mean that the Code will be ignored if the Name is filled in. Reverse it if it needs to be the other way around. Or if you want to use both fields, you can put the string null checks in the Where clause.
I am trying to query a database using LINQ. I am joining TableA with TableB with TableC.
I have zero to many 'keywords' (don't know how many at design time) that I would like to look for within (LIKE '%%') several fields that are spread across the three tables.
Assuming three (3) keywords are entered into my search box:
In T-SQL I would have this -
SELECT tbl0.FieldA, tbl0.FieldB, tbl1.FieldC, tbl1.FieldD, tbl2.FieldE, tbl2.FieldF
FROM tbl0
JOIN tbl1 ON tbl0.KeyField = tbl1.KeyField
JOIN tbl2 ON tbl1.KeyField = tbl2.KeyField
WHERE (tbl0.FieldA LIKE '%{keyword1}%' OR tbl1.FieldC LIKE '%{keyword1}%' OR tbl2.FieldE LIKE '%{keyword1}%' OR tbl0.FieldA LIKE '%{keyword2}%' OR tbl1.FieldC LIKE '%{keyword2}%' OR tbl2.FieldE LIKE '%{keyword2}%' OR tbl0.FieldA LIKE '%{keyword3}%' OR tbl1.FieldC LIKE '%{keyword3}%' OR tbl2.FieldE LIKE '%{keyword3}%')
Question is -- How do I 'dynamically' build this WHERE clause in LINQ?
NOTE #1 -- I do not (for reasons outside the scope of this question) want to create a VIEW across the three tables
NOTE #2 -- Because I am joining in this way (and I am still new to LINQ) I don't see how I can use the PredicateBuilder because I am not sure what TYPE (T) to pass into it?
NOTE #3 -- If it matters ... I am ultimately planning to return a strongly typed list of (custom) objects to be displayed in a GridView.
EDIT - 8/17/2012 - 5:15 PM EDT
The comment below is correct.
"The code the OP is looking for is where any one of the fields contains any one of the keywords."
Thanks everyone!
Here's a solution not using the PredicateBuilder. Just get all the items containing the first keyword and merge it with all the items containing the second keyword and so on. Not knowing anything about the context of the problem I can't tell if this will be efficient or not.
var query = from t0 in db.Table0
join t1 in db.Table1 on t0.KeyField equals t1.KeyField
join t2 in db.Table2 on t1.KeyField equals t2.KeyField
select new
{
t0.FieldA, t0.FieldB,
t1.FieldC, t1.FieldD,
t2.FieldE, t2.FieldF
};
string keyword = keywordsList[0];
var result = query.Where(x => x.FieldA.Contains(keyword) ||
x.FieldC.Contains(keyword) ||
x.FieldE.Contains(keyword));
for (int i = 1; i < keywordsList.Length; i++)
{
string tempkey = keywordsList[i];
result = result.Union(query.Where(x => x.FieldA.Contains(tempkey) ||
x.FieldC.Contains(tempkey) ||
x.FieldE.Contains(tempkey)));
}
result = result.Distinct();
I want to convert this query to LINQ to use with Entity Framework 4.
Select *,
(select SUM(Of_Orders.Contribution) from Of_Orders
where Of_Orders.CauseID = Of_Causes.CauseID) AS Earned
FROM Of_Causes
Can anyone help me to do this.
Thanks in Advance
It cools like the CauseID field represents a foreign key? If it does then you can map an association in your entity model, so that cause.Orders is a property you can access. Side note, you can also rename the entities/properties, they don't have to match table names so you could get rid of the ugly underscores! That would then look like this:
var result = from c in context.Of_Causes
select new {cause, cause.Orders.Sum(o => o.Contribution}
You'd get a few benefits from doing that (if you can), like a cleaner model, and more efficient SQL queries being generated.
If you can't use a foreign key, I'd join the tables manually which again should help performance.
I believe that would look like this but my translation from VB to C# may be dodgy, here's the C#:
var context = new ModelContainer();
var result = from c in context.Of_Cause
join o in context.Of_Orders on c.Id equals o.CauseID into g
select new {Cause = c, Total = g.Sum(o => o.Contribution)};
And here's the VB original in case my translation is off!
Dim result = From cause As Of_Cause In context.Of_Cause
Group Join order As Of_Orders In context.Of_Orders On order.CauseID Equals cause.Id Into total = Sum(order.Contribution)
Select New With {.Cause = cause, .Total = total}
That would be something similar to:
from cause in context.Of_Causes
select new {
cause,
Earned = context.Of_Orders.Where(o => o.CauseID == cause.CauseID).Sum(o => o.Contribution)
};
I've made some assumptions about the types involved, but that should be easy for you to work out.
I have a fairly complicated join query that I use with my database. Upon running it I end up with results that contain an baseID and a bunch of other fields. I then want to take this baseID and determine how many times it occurs in a table like this:
TableToBeCounted (One to Many)
{
baseID,
childID
}
How do I perform a linq query that still uses the query I already have and then JOINs the count() with the baseID?
Something like this in untested linq code:
from k in db.Kingdom
join p in db.Phylum on k.KingdomID equals p.KingdomID
where p.PhylumID == "Something"
join c in db.Class on p.PhylumID equals c.PhylumID
select new {c.ClassID, c.Name};
I then want to take that code and count how many orders are nested within each class. I then want to append a column using linq so that my final select looks like this:
select new {c.ClassID, c.Name, o.Count()}//Or something like that.
The entire example is based upon the Biological Classification system.
Assume for the example that I have multiple tables:
Kingdom
|--Phylum
|--Class
|--Order
Each Phylum has a Phylum ID and a Kingdom ID. Meaning that all phylum are a subset of a kingdom. All Orders are subsets of a Class ID. I want to count how many Orders below to each class.
select new {c.ClassID, c.Name, (from o in orders where o.classId == c.ClassId select o).Count()}
Is this possible for you? Best I can do without knowing more of the arch.
If the relationships are as you describe:
var foo = db.Class.Where(c=>c.Phylum.PhylumID == "something")
.Select(x=> new { ClassID = x.ClassID,
ClassName = x.Name,
NumOrders= x.Order.Count})
.ToList();
Side question: why are you joining those entities? Shouldn't they naturally be FK'd, thereby not requiring an explicit join?