Build Where Clause (not sure PredicateBuilder will work for me) - c#

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();

Related

Linq query for Where on the Joined table without needing join

Trying to get a linq query (or lambda syntax) for the following SQL which Selects all "Data" which in the joining table have an Attribute equal to "blob".
EXCEPT: without explictly using the Join, but the
select data.*
from data
join settings on data.DataID = settings.DataID
where settings.Attribute = 'blob'
Explicitly defining the join
from d in dbcontext.Data
join s in dbcontext.Settings on d.DataID equals s.DataID
where s.Attribute == "blob"
select d
but is there a way to use the context dbcontext.Data.Settings
like the following?
from d in dbcontext.Data
where d.Settings.Attribute == "blob"
select d
Settings is a collection Type, so things like .Contains, and .Where come to mind.
using .Contains, my understanding is i would need to pass in an object type
where d.Settings.Contains(new Settings(d.DataID, "blob", null))
but i dont care about the null (Value) matching, just column Settings
some table structures
Data
DataID
Name
Settings
DataID
Attribute
Value
As I understand, you have Settings collection navigation property, so instead of explicit join you could simply use it ("navigate"):
from d in dbcontext.Data
from s in d.Settings
where s.Attribute == "blob"
select d
Alternatively you could use Any extension method which in this case is more appropriate than Contains (although Contains can also be used, but needs to be combined with Select):
dbcontext.Data.Where(d => d.Settings.Any(s => s.Attribute == "blob"))
For completeness, here is the Contains version:
dbcontext.Data.Where(d => d.Settings.Select(s => s.Attribute).Contains("blob"))
If I understand your question correctly, you want to create a LINQ that will grab any DataID that has an attribute of of "Blah" that is stored in another table.
If so this may work.
var dataIDs = Setting.Where(entry => entry.Attribute == "Blah")
.Select(entry => entry.DataID); // gets all DataIDs that match the attribute
var data = Data.Where(entry => entry.DataID in dataIDs); // gets data info based on DataIDs.
It should work, but what you should do instead is do an left join somewhat like
select a.*
from data a
left join settings b
on a.DataID = b.DataID
where b.Attribute = 'blob'
but in LINQ. This query would allow you to fetch all the data for DataIDs that match attribute 'blob. I haven't done it in LINQ so if someone more familiar with left joins with linq could respond that might work better

Linq join on two values

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

Lambda or LINQ for Complex Filter?

I have a need to filter a large collection of objects (in memory) to select only those which meet ALL of the selected categories.
This is essentially the SQL query I'm looking to replicate, but I've been unable to come up with a good C# alternative:
select distinct o.ObjectId
from Object o
join ObjectCategories oc on oc.ObjectId = o.ObjectId
where oc.CategoryId in (1)
and oc.CategoryId in (2)
and oc.CategoryId in (3)
... and so on...
...where 1, 2, and 3 represent the values in an indeterminate number of user-selected categories.
Have your user selected category ID's in a List and then you can use Contains.
select distinct o.ObjectId
from Object o
join ObjectCategories oc on oc.ObjectId = o.ObjectId
where yourCategoryList.Contains(oc=>oc.categoryID);
var results = ObjectCategories.Where(t2 => ObjectList.Any(t1 => t2.Contains(t1)) == true)
you can count the number of matches and if it is equal to the list you are checking against, then you have all the matches.
Consider using Dynamic LINQ. It allows you to use string expressions instead of lambda expressions. You should be able to do what you want using something similar to:
var qry = ObjectCategories.Where(
"ObjectList.CategoryId.Contains(1) AND ObjectList.CategoryId.Contains(2) ..."
);
There is a pretty solid implemention of Dynamic LINQ here: https://github.com/NArnott/System.Linq.Dynamic

Linq To SQL Contains

How can I go about converting this SQL statement to LINQ:
SELECT [Content].[Content], [Content].ListOrder, [Content].ContentTypeId,
[Content].ContentId
FROM [Content] INNER JOIN
GroupContentPermission ON [Content].ContentId = GroupContentPermission.ContentId
WHERE GroupContentPermission.GroupId IN
(SELECT GroupId FROM GroupUser WHERE GroupUser.UserId = 169)
Translation to LINQ is generally pretty straightforward except for one special trick in the case of your query. You can translate your select, where, and from statements in a natural way as shown below. In the case of an IN statement though, you have to get the results from the inner subquery first, and then check if the inner subquery .Contains the value you want to check.
var groups =
(from gu in GroupUser
where gu.UserId == 169
select gu.GroupId).ToList();
var result =
from p in GroupContentPermission
join c in Content on p.ContentId equals c.ContentId
where groups.Contains(p.GroupId)
select new { c.Content, c.ListOrder, c.ContentTypeID, c.ContentId };
// result should contain the same results as the SQL query
Here are some other resources you may find helpful as well (you can find many more resources and tutorials on LINQ if you do a quick google search. There are literally thousands):
Linqer, a SQL to LINQ converter.
LinqPAD, a simple .NET/LINQ tester for rapid experimentation
ScottGu's definitive guide to Using LINQ To SQL
Related SO question: What are some good LINQ resources?, which references a tutorial called 101 LINQ Samples.
Assuming you already link the tables with foreign keys in your model (DBML/EntityFrameworks):
Contents.Where(x => x.GroupContentPermission.GroupUser.UserId == 169).Select(x => new {
x.Content,
x.ListOrder,
x.ContentTypeId,
x.ContentId })
or preferrably just grab the full Content object, and use any column you want:
var contents = Contents.Where(x => x.GroupContentPermission.GroupUser.UserId == 169).ToList();
foreach (var content in contents)
Console.Write(content.Content);

Counting in a Linq Query

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?

Categories