Working on a Linq-to-SQL project and observing some odd behavior with the generated SQL. Basically I have an array of strings, and I need to select all rows where a column starts with one of those strings.
using (SqlConnection sqlConn = new SqlConnection(connString))
{
using (IdsSqlDataContext context = new IdsSqlDataContext(sqlConn))
{
//generated results should start with one of these.
//in real code base they are obviously not hardcoded and list is variable length
string[] args = new string[] { "abc", "def", "hig" };
IQueryable<string> queryable = null;
//loop through the array, the first time through create an iqueryable<>, and subsequent passes union results onto original
foreach (string arg in args)
{
if (queryable == null)
{
queryable = context.IdsForms.Where(f => f.MatterNumber.StartsWith(arg)).Select(f => f.MatterNumber);
}
else
{
queryable = queryable.Union(context.IdsForms.Where(f => f.MatterNumber.StartsWith(arg)).Select(f => f.MatterNumber));
}
}
//actually execute the query.
var result = queryable.ToArray();
}
}
I would expect the sql generated to be functionally equivalent to the following.
select MatterNumber
from IdsForm
where MatterNumber like 'abc%' or MatterNumber like 'def%' or MatterNumber like 'hig%'
But the actual SQL generated is below, notice 'hig%' is the argument for all three like clauses.
exec sp_executesql N'SELECT [t4].[MatterNumber]
FROM (
SELECT [t2].[MatterNumber]
FROM (
SELECT [t0].[MatterNumber]
FROM [dbo].[IdsForm] AS [t0]
WHERE [t0].[MatterNumber] LIKE #p0
UNION
SELECT [t1].[MatterNumber]
FROM [dbo].[IdsForm] AS [t1]
WHERE [t1].[MatterNumber] LIKE #p1
) AS [t2]
UNION
SELECT [t3].[MatterNumber]
FROM [dbo].[IdsForm] AS [t3]
WHERE [t3].[MatterNumber] LIKE #p2
) AS [t4]',N'#p0 varchar(4),#p1 varchar(4),#p2 varchar(4)',#p0='hig%',#p1='hig%',#p2='hig%'
Looks like you're closing over the loop variable. This is a common gotcha in C#. What happens is that the value of arg is evaluated when the query is run, not when it is created.
Create a temp variable to hold the value:
foreach (string arg in args)
{
var temp = arg;
if (queryable == null)
{
queryable = context.IdsForms.Where(f => f.MatterNumber.StartsWith(temp)).Select(f => f.MatterNumber);
}
else
{
queryable = queryable.Union(context.IdsForms.Where(f => f.MatterNumber.StartsWith(temp)).Select(f => f.MatterNumber));
}
}
You can read this Eric Lippert post about closing over a loop variable. As Eric notes at the top of the article, and as #Magus points out in a comment, this has changed in C# 5 so that the foreach variable is a new copy on each iteration. Creating a temp variable, like above, is forward compatible though.
The union is correct, due to you using union in your linq to sql query. The reason they are all hig% is because the lambda f => f.MatterNumber.StartsWith(arg) creates a closure around the loop parameter. To fix, declare a local variable in the loop
foreach (string arg in args)
{
var _arg = arg;
if (queryable == null)
{
queryable = context.IdsForms.Where(f => f.MatterNumber.StartsWith(_arg)).Select(f => f.MatterNumber);
}
else
{
queryable = queryable.Union(context.IdsForms.Where(f => f.MatterNumber.StartsWith(_arg)).Select(f => f.MatterNumber));
}
}
But I agree the union seems unnecessary. If the array of strings to check against is not going to change, then you can just use a standard where clause. Otherwise you could take a look at predicate builder! Check here
How about this ?
queryable = context..IdsForms.Where(f =>
{
foreach (var arg in args)
{
if (f.MatterNumber.StartsWith(arg))
return true;
}
return false;
}).Select(f => f.MatterNumber);
Related
Consider the code below:
StockcheckJobs =
(from job in (from stockcheckItem in MDC.StockcheckItems
where distinctJobs.Contains(stockcheckItem.JobId)
group stockcheckItem by new { stockcheckItem.JobId, stockcheckItem.JobData.EngineerId } into jobs
select jobs).ToList()
let date = MJM.GetOrCreateJobData(job.Key.JobId).CompletedJob.Value
orderby date descending
select new StockcheckJobsModel.StockcheckJob()
{
JobId = job.Key.JobId,
Date = date,
Engineer = (EngineerModel)job.Key.EngineerId,
MatchingLines = job.Count(sti => sti.Quantity == sti.ExpectedQuantity),
DifferingLines = job.Count(sti => sti.Quantity != sti.ExpectedQuantity)
}).ToList()
There is a ToList() in the middle because the GetOrCreateJobData method can't be translated into sql.
As a result I've had to surround the first part of my query in brackets to do this, then I've used an outer query to finish up.
I know I could split this into two variables, but I don't want to do that (this is within an object initialiser too).
Is there some other syntax I can use to increase readability, preferably removing the need for an outer an inner query, when I have to do a ToList (or otherwise get to linq-to-objects) in the middle of a linq query?
In an ideal world I'd like something like this (as close as is possible anyway):
StockcheckJobs =
from stockcheckItem in MDC.StockcheckItems
where distinctJobs.Contains(stockcheckItem.JobId)
group stockcheckItem by new { stockcheckItem.JobId, stockcheckItem.JobData.EngineerId } into jobs
MAGIC_DO_BELOW_AS_LINQ-TO-OBJECTS_KEYWORD_OR_SYNTAX
let date = MJM.GetOrCreateJobData(jobs.Key.JobId).CompletedJob.Value
orderby date descending
select new StockcheckJobsModel.StockcheckJob()
{
JobId = jobs.Key.JobId,
Date = date,
Engineer = new ThreeSixtyScheduling.Models.EngineerModel() { Number = jobs.Key.EngineerId },
MatchingLines = jobs.Count(sti => sti.Quantity == sti.ExpectedQuantity),
DifferingLines = jobs.Count(sti => sti.Quantity != sti.ExpectedQuantity)
};
You can fix the issue of GetOrCreateJobData not being translatable to SQL.
By implementing a custom query translator for the specified method call expression, you can gain control over how LINQ-to-SQL interprets the method. There is a good article explaining this procedure and linking to relevant resources available at: http://www.codeproject.com/Articles/32968/QueryMap-Custom-translation-of-LINQ-expressions
Alternatively, you could refactor the GetOrCreateJobData method to an extension method which builds the same logic with expressions, so that LINQ-to-SQL can interpret it naturally. Depending on the complexity of the method, this may be more or less feasible than my first suggestion.
I would raise two points with the question:
I really don't think there's any readability issue with introducing an extra variable here. In fact, I think it makes it more readable as it separates the "locally executing" code from the code executing on the database.
To simply switch to LINQ-To-Objects, AsEnumerable is preferable to ToList.
That said, here's how you can stay in query-land all the way without an intermediate AsEnumerable() / ToList() on the entire query-expression : by tricking the C# compiler into using your custom extension methods rather than the BCL. This is possible since C# uses a "pattern-based" approach (rather than being coupled with the BCL) to turn query-expressions into method-calls and lambdas.
Declare evil classes like these:
public static class To
{
public sealed class ToList { }
public static readonly ToList List;
// C# should target this method when you use "select To.List"
// inside a query expression.
public static List<T> Select<T>
(this IEnumerable<T> source, Func<T, ToList> projector)
{
return source.ToList();
}
}
public static class As
{
public sealed class AsEnumerable { }
public static readonly AsEnumerable Enumerable;
// C# should target this method when you use "select As.Enumerable"
// inside a query expression.
public static IEnumerable<T> Select<T>
(this IEnumerable<T> source, Func<T, AsEnumerable> projector)
{
return source;
}
}
And then you can write queries like this:
List<int> list = from num in new[] { 41 }.AsQueryable()
select num + 1 into result
select To.List;
IEnumerable<int> seq = from num in new[] { 41 }.AsQueryable()
select num + 1 into result
select As.Enumerable into seqItem
select seqItem + 1; // Subsequent processing
In your case, your query would become:
StockcheckJobs =
from stockcheckItem in MDC.StockcheckItems
where distinctJobs.Contains(stockcheckItem.JobId)
group stockcheckItem by new { stockcheckItem.JobId, stockcheckItem.JobData.EngineerId } into jobs
select As.Enumerable into localJobs // MAGIC!
let date = MJM.GetOrCreateJobData(localJobs.Key.JobId).CompletedJob.Value
orderby date descending
select new StockcheckJobsModel.StockcheckJob()
{
JobId = localJobs.Key.JobId,
Date = date,
Engineer = new ThreeSixtyScheduling.Models.EngineerModel() { Number = localJobs.Key.EngineerId },
MatchingLines = localJobs.Count(sti => sti.Quantity == sti.ExpectedQuantity),
DifferingLines = localJobs.Count(sti => sti.Quantity != sti.ExpectedQuantity)
};
I really don't see this as any sort of improvement, though. Rather, it's pretty heavy abuse of a language feature.
I find that using method syntax makes things clearer, but that's just personal preference. It certainly makes the top half of the query better, but using a let, while possible in method syntax, is a bit more work.
var result = stockcheckItem in MDC.StockcheckItems
.Where(item => distinctJobs.Contains(item.JobId))
.GroupBy(item => new { item.JobId, item.JobData.EngineerId })
.AsEnumerable() //switch from Linq-to-sql to Linq-to-objects
.Select(job => new StockcheckJobsModel.StockcheckJob()
{
JobId = job.Key.JobId,
Date = MJM.GetOrCreateJobData(job.Key.JobId).CompletedJob.Value,
Engineer = (EngineerModel)job.Key.EngineerId,
MatchingLines = job.Count(sti => sti.Quantity == sti.ExpectedQuantity),
DifferingLines = job.Count(sti => sti.Quantity != sti.ExpectedQuantity)
})
.Orderby(item => item.Date)
.ToList()
One option is to do all the SQL compatible work up front into an anonymous type,
var jobs =
(from job in (from stockcheckItem in MDC.StockcheckItems
where distinctJobs.Contains(stockcheckItem.JobId)
group stockcheckItem by new
{ stockcheckItem.JobId, stockcheckItem.JobData.EngineerId }
into jobs
select new
{
JobId = job.Key.JobId,
Engineer = (EngineerModel)job.Key.EngineerId,
MatchingLines =
job.Count(sti => sti.Quantity == sti.ExpectedQuantity),
DifferingLines =
job.Count(sti => sti.Quantity != sti.ExpectedQuantity)
}
).AsEnumerable()
StockcheckJobs = jobs.Select(j => new StockcheckJobsModel.StockcheckJob
{
JobId = j.JobId,
Date = MJM.GetOrCreateJobData(j.JobId).CompletedJob.Value,
Engineer = j.EngineerId,
MatchingLines = j.MatchingLines,
DifferingLines = j.DifferingLines
}).OrderBy(j => j.Date).ToList();
Obviously not tested, but you get the idea.
I have a database table that contains an nvarchar column like this:
1|12.6|18|19
I have a Business Object that has a Decimal[] property.
My LINQ Query looks like this:
var temp = from r in db.SomeTable select new BusinessObject {
// Other BusinessObject Properties snipped as they are straight 1:1
MeterValues = r.MeterValues.Split('|').Select(Decimal.Parse).ToArray()
};
var result = temp.ToArray();
This throws an NotSupportedException: Method 'System.String[] Split(Char[])' has no supported translation to SQL.
That kinda sucks :) Is there any way I can do this without having to add a string property to the business object or selecting an anonymous type and then iterating through it?
My current "solution" is:
var temp = from r in db.SomeTable select new {
mv = r.MeterValues,
bo = new BusinessObject { // all the other fields }
};
var result = new List<BusinessObject>();
foreach(var t in temp) {
var bo = t.bo;
bo.MeterValues = t.mv.Split('|').Select(Decimal.Parse).ToArray();
result.Add(bo);
}
return result.ToArray(); // The Method returns BusinessObject[]
That's kinda ugly though, with that temporary list.
I've tried adding a let mv = r.MeterValues.Split('|').Select(Decimal.Parse).ToArray() but that essentially leads to the same NotSupportedException.
This is .net 3.5SP1 if that matters.
You need to force the select clause to run on the client by calling .AsEnumerable() first:
var result = db.SomeTable.AsEnumerable().Select(r => new BusinessObject {
...
MeterValues = r.MeterValues.Split('|').Select(Decimal.Parse).ToArray()
}).ToList();
You can't use split, but in this scenario you can do the following:
// Database value is 1|12.6|18|19
string valueToFind = "19";
var temp = from r in db.SomeTable.Where(r => ("|" + r.MeterValues + "|").Contains("|" + valueToFind + "|"));
This code adds outer pipes (|) to the database value on the fly inside the query so you can do start, middle, and end value matches on the string.
For example, the above code looks for "|19|" inside "|1|12.6|18|19|", which is found and valid. This will work for any other valueToFind.
You don't need to use a temporary list:
var query = from r in db.SomeTable
select new
{
r.Id,
r.Name,
r.MeterValues,
...
};
var temp = from x in query.AsEnumerable()
select new BusinessObject
{
Id = x.Id,
Name = x.Name,
MeterValues = x.mv.Split('|').Select(Decimal.Parse).ToArray(),
...
};
return temp.ToArray();
Unfortunately its the IQueryable you are using (Linq to SQL) that is not supporting the Split function.
You are really only left with the IEnumerable (Linq to Objects) support for it in this case. You second code snippet is what you need to do, or something like...
var temp = (from r in db.SomeTable select new {
mv = r.MeterValues,
bo = new BusinessObject { // all the other fields }
}).AsEnumerable().Select(blah, blah) ;
I'm writing what I believe should be a relatively straight-forward Windows Form app. I'm using LINQ to SQL, though I have never used it before. We have a SQL Server database, and I'm creating a front-end to access that database. I'm trying to figure out the most efficient way to search for multiple (arbitrary number of) search parameters with it.
In the windows form, I create a dictionary with each search key and its value to search for, and pass it into my search() method. I am trying to find a way to search the database with each of those keys and their associated values. Here is what I am trying to do:
public IQueryable<Product> Search(Dictionary<string, string> searchParams)
{
DBDataContext dc = new DBDataContext();
var query = dc.Products;
foreach (KeyValuePair<string, string> temp in searchParams)
{
query = query.Where(x => x.(temp.Key) == temp.Value);
}
return query;
}
I realize that syntactically x.(temp.Key) is incorrect, but I hope that illustrates what I am trying to do. I was wondering if there is another way to go about what I am trying to do without having to do a giant switch statement (or if/else if tree).
EDIT
So, I revised it a little, but I'm still having issues with it. Here is what I currently have:
public IQueryable<Product> Search(Dictionary<string, string> searchParams)
{
DBDataContext dc = new DBDataContext();
string sQuery = "";
foreach (KeyValuePair<string, string> temp in searchParams)
{
sQuery += temp.Key + "=" + temp.Value + " AND ";
}
var query = dc.Products.Where(sQuery);
return query;
}
According to the LINQ Dynamic Query Library article, this should be OK. Here is the error I'm getting:
The type arguments for method 'System.Linq.Queryable.Where(System.Linq.IQueryable, System.Linq.Expressions.Expression>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Here's an example that works (I just tested it), using the Dynamic LINQ Query Library.
using System.Linq.Dynamic;
// ...
Dictionary<string, string> searchParams = new Dictionary<string,string>();
searchParams.Add("EmployeeID", "78");
searchParams.Add("EmpType", "\"my emp type\"");
IQueryable<Employee> query = context.Employees;
foreach (KeyValuePair<string, string> keyValuePair in searchParams)
{
query = query.Where(string.Format("{0} = {1}", keyValuePair.Key, keyValuePair.Value));
}
List<Employee> employees = query.ToList();
And, to make it absolutely clear that this code actually works here is the actual generated SQL:
FROM [HumanResources].[Employee] AS [t0]
WHERE ([t0].[EmpType] = #p0) AND ([t0].[EmployeeID] = #p1)',N'#p0 nvarchar(11),#p1 int',#p0=N'my emp type',#p1=78
If the Dictionary is not required for some reason, I would make your Search method as follows:
public IQueryable<Product> Search( Func<Product, bool> isMatch )
{
DBDataContext dc = new DBDataContext();
return dc.Products.Where( isMatch ).AsQueryable();
}
Then, you would use the method like so:
Obj.Search( item => item.Property1 == "Hello" && item.Property2 == "World" );
Is there some reason that you can't do that?
[Edit: added AsQueryable()]
[Edit: for Dynamic Query using strings]
Take a look here and see if this helps. I haven't used it, but looks like it's what you're looking for:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
Personally, I would generally prefer the type-safe Expression> approach since this will give you compile time errors...but if strings are needed, then looks like the best way to do it.
Based on the above link, looks like you should be able to do:
query = query.Where( String.Format("{0}={1}",dict.Key,dict.Value) );
[Edit: String Building Example]
So, one of the problems is that your sql query is going to end with an AND at the end of the string but then no condition after it...so, might try changing to this...syntax might be off slightly, but should be right:
public IQueryable<Product> Search(Dictionary<string, string> searchParams)
{
DBDataContext dc = new DBDataContext();
StringBuilder sQuery = new StringBuilder();
foreach (KeyValuePair<string, string> temp in searchParams)
{
if( sQuery.Length > 0 ) sQuery.Append(" AND ");
sQuery.AppendFormat("{0}={1}",temp.Key,temp.Value);
}
var query = dc.Products.Where(sQuery.ToString());
return query;
}
This will only use "AND" on conditions after the first. Hope it helps...
FYI - It's off-topic, but 'why' I used StringBuilder is that string concatenation the way you had it would result in the string being destroyed and a new string being created in memory 4 times per loop...so changed to a StringBuilder since that will create a buffer that can be filled and only resized when necessary.
Instead of using dictionary, you can use this method:
using System.Linq.Expressions;
// ...
public static IQueryable<Product> Search(Expression<Func<Product, bool>> search)
{
DBDataContext dc = new DBDataContext();
return dc.Products.Where(search);
}
You can use it like this:
Search(p => p.Name == "Name" && p.Id == 0);
You also won't be limited to string properties.
have u try put single quote for your string?
based on your foreach loop, there will be extra 'AND' at the end of query
try this instead
string sQuery = searchParam.Select(entry => string.Format("{0} = '{1}'", entry.Key, entry.Value)).Aggregate((current, next) => current + " AND " next);
if you are willing to create a dictionary of searchable terms than I've used something close to the approach below. I've adapted what I do be more closely fit into how you were doing things, I've also changed the names of the tables to fit a datacontext / object model that I have available to me.
The idea is to create a keyed list of terms that your query supports searching by. Then add functions that return an expression which can then be passed into the where clause of your query.
You should of course add some error handling for invalid keys etc.
public IQueryable<Person> Search(Dictionary<string, string> searchParams)
{
DBDataContext dc = new DBDataContext();
var query = dc.Persons.Where(p => true); //do an 'empty predicate' because you want 'query' to be an iqueryable<Person> not a Table<Person>
//build a list of the types of things you can filter on.
var criteriaDefinitions = new Dictionary<string,Func<string,Expression<Func<Person,bool>>>>();
criteriaDefinitions.Add("FirstName",s => p => p.FirstName == s);
criteriaDefinitions.Add("LastName",s => p => p.LastName == s);
//you can do operations other than just equals
criteriaDefinitions.Add("EmailContains",s => p => p.Email.Contains(s));
//you can even create expressions that integrate joins.
criteriaDefinitions.Add("HasContactInCity",s => p => p.Contacts.Any(c => c.City == s));
foreach (KeyValuePair<string, string> temp in searchParams)
{
//grab the correct function out of the dictionary
var func = criteriaDefinitions[temp.Key];
//evaluating the function will return an expression which can passed into the where clause.
var expr = func(temp.Value);
query = query.Where(expr);
}
return query;
}
for linq search queries multiple optional parameters
Can Use This Code:
var query = dx.GetAllJobs().Where(x => x.JobName.Contains(keyword));
if (SecLink != 0)
{
query = query.Where(x => x.SectorLink.Equals(SecLink));
}
if (LocLink != 0)
{
query = query.Where(x => x.LocationLink.Equals(LocLink));
}
if (IndLink != 0)
{
query = query.Where(x => x.IndustryLink.Equals(IndLink));
}
if (VacLink != 0)
{
query = query.Where(x => x.VacancyTypeLink.Equals(VacLink));
}
var lstJobs = query.ToList();
(An earlier question, Recursively (?) compose LINQ predicates into a single predicate, is similar to this but I actually asked the wrong question... the solution there satisfied the question as posed, but isn't actually what I need. They are different, though. Honest.)
Given the following search text:
"keyword1 keyword2 ... keywordN"
I want to end up with the following SQL:
SELECT [columns] FROM Customer
WHERE (
Customer.Forenames LIKE '%keyword1%'
OR
Customer.Forenames LIKE '%keyword2%'
OR
...
OR
Customer.Forenames LIKE '%keywordN%'
) AND (
Customer.Surname LIKE '%keyword1%'
OR
Customer.Surname LIKE '%keyword2%'
OR
....
OR
Customer.Surname LIKE '%keywordN%'
)
Effectively, we're splitting the search text on spaces, trimming each token, constructing a multi-part OR clause based on each , and then AND'ing the clauses together.
I'm doing this in Linq-to-SQL, and I have no idea how to dynamically compose a predicate based on an arbitrarily-long list of subpredicates. For a known number of clauses, it's easy to compose the predicates manually:
dataContext.Customers.Where(
(
Customer.Forenames.Contains("keyword1")
||
Customer.Forenames.Contains("keyword2")
) && (
Customer.Surname.Contains("keyword1")
||
Customer.Surname.Contains("keyword2")
)
);
In short, I need a technique that, given two predicates, will return a single predicate composing the two source predicates with a supplied operator, but restricted to the operators explicitly supported by Linq-to-SQL. Any ideas?
You can use the PredicateBuilder class
IQueryable<Customer> SearchCustomers (params string[] keywords)
{
var predicate = PredicateBuilder.False<Customer>();
foreach (string keyword in keywords)
{
// Note that you *must* declare a variable inside the loop
// otherwise all your lambdas end up referencing whatever
// the value of "keyword" is when they're finally executed.
string temp = keyword;
predicate = predicate.Or (p => p.Forenames.Contains (temp));
}
return dataContext.Customers.Where (predicate);
}
(that's actually the example from the PredicateBuilder page, I just adapted it to your case...)
EDIT:
Actually I misread your question, and my example above only covers a part of the solution... The following method should do what you want :
IQueryable<Customer> SearchCustomers (string[] forenameKeyWords, string[] surnameKeywords)
{
var predicate = PredicateBuilder.True<Customer>();
var forenamePredicate = PredicateBuilder.False<Customer>();
foreach (string keyword in forenameKeyWords)
{
string temp = keyword;
forenamePredicate = forenamePredicate.Or (p => p.Forenames.Contains (temp));
}
predicate = PredicateBuilder.And(forenamePredicate);
var surnamePredicate = PredicateBuilder.False<Customer>();
foreach (string keyword in surnameKeyWords)
{
string temp = keyword;
surnamePredicate = surnamePredicate.Or (p => p.Surnames.Contains (temp));
}
predicate = PredicateBuilder.And(surnamePredicate);
return dataContext.Customers.Where(predicate);
}
You can use it like that:
var query = SearchCustomers(
new[] { "keyword1", "keyword2" },
new[] { "keyword3", "keyword4" });
foreach (var Customer in query)
{
...
}
Normally you would chain invocations of .Where(...). E.g.:
var a = dataContext.Customers;
if (kwd1 != null)
a = a.Where(t => t.Customer.Forenames.Contains(kwd1));
if (kwd2 != null)
a = a.Where(t => t.Customer.Forenames.Contains(kwd2));
// ...
return a;
LINQ-to-SQL would weld it all back together into a single WHERE clause.
This doesn't work with OR, however. You could use unions and intersections, but I'm not sure whether LINQ-to-SQL (or SQL Server) is clever enough to fold it back to a single WHERE clause. OTOH, it won't matter if performance doesn't suffer. Anyway, it would look something like this:
<The type of dataContext.Customers> ff = null, ss = null;
foreach (k in keywords) {
if (keywords != null) {
var f = dataContext.Customers.Where(t => t.Customer.Forenames.Contains(k));
ff = ff == null ? f : ff.Union(f);
var s = dataContext.Customers.Where(t => t.Customer.Surname.Contains(k));
ss = ss == null ? s : ss.Union(s);
}
}
return ff.Intersect(ss);
Can you help me with next: I'd like to add variable count of SqlMethods.Like() in one query?
For example: I've four words in list catCode, how can I create LINQ query with four SqlMethods.Like()?
using (var db = new MappingDataContext(_connection))
{
db.ObjectTrackingEnabled = false;
return (
from r in db.U_CDW_REPORTs
where (catCode.Length > 0 ?
SqlMethods.Like(r.CATEGORY, catCode) : r.CATEGORY != "*")
where r.SI_QTY > 0
orderby r.SI_QTY descending
select new Item
{
...
}).ToList();
}
What you need is dynamically OR-ing SqlMethod.Like operations. You need the PredicateBuilder.
Update: Here is an example of how to use the predicate builder.
string[] searchItems =
new string[] { "Pattern1", "Pattern2", "Pattern3" };
var likeExpression = PredicateBuilder.False<U_CDW_REPORT>();
foreach (string searchItem in searchItems)
{
var searchPattern = "%" + searchItem + "%";
likeExpression = likeExpression.Or(r =>
SqlMethods.Like(r.CATEGORY, searchPattern));
}
return (
from r in db.U_CDW_REPORTs.Where(likeExpression)
where r.SI_QTY > 0
orderby r.SI_QTY descending
select new Item { ... }).ToList();
You mean you have 4 disjoint LIKE clauses?
from entityRow in ctx.MyEntity
where (SqlMethods.Like(entityRow.Col, "%Pattern1%")
|| SqlMethods.Like(entityRow.Col, "%Patte%rn2%")
|| SqlMethods.Like(entityRow.Col, "Pattern3%")
|| SqlMethods.Like(entityRow.Col, "%Patte%rn4%"))
select entityRow;
UPDATE:
In that case, take a look at this: LINQ for LIKE queries of array elements
EDIT: made the code simpler. Also notice the updated explanation below.
You can build upon the query with multiple Where clauses in the following manner. Note that this approach ANDs the Where clauses, meaning all search terms will need to exist. In other words it's similar to ... where (condition1) && (condition2) && (conditionN).
string[] words = { "A", "B", "C" };
var query = dc.Products.AsQueryable(); // gives us an IQueryable<T> to build upon
foreach (var s in words)
{
query = query.Where(p => SqlMethods.Like(p.ProductName, "%" + s + "%"));
}
foreach (var item in query)
{
Console.WriteLine(item.ProductName);
}
The idea is to set the first part of the query up, then loop over the search terms and update the query. Of course you should update your pattern as needed; I used %word% for illustration purposes only.