Linq with a long where clause - c#

Is there a better way to do this? I tried to loop over the partsToChange collection and build up the where clause, but it ANDs them together instead of ORing them. I also don't really want to explicitly do the equality on each item in the partsToChange list.
var partsToChange = new Dictionary<string, string> {
{"0039", "Vendor A"},
{"0051", "Vendor B"},
{"0061", "Vendor C"},
{"0080", "Vendor D"},
{"0081", "Vendor D"},
{"0086", "Vendor D"},
{"0089", "Vendor E"},
{"0091", "Vendor F"},
{"0163", "Vendor E"},
{"0426", "Vendor B"},
{"1197", "Vendor B"}
};
var items = new List<MaterialVendor>();
foreach (var x in partsToChange)
{
var newItems = (
from m in MaterialVendor
where
m.Material.PartNumber == x.Key
&& m.Manufacturer.Name.Contains(x.Value)
select m
).ToList();
items.AddRange(newItems);
}
Additional info: I am working in LINQPad and this is a LinqToSql query. Here MaterialVendor is both a class and a DataContext Table.
Edit: LinqToSql details.
This seems to be the best method that I have found for both readability and reducing the complexity. It also has the added benefit of not having the collection type defined explicitly. That means I can vary what comes back with an anonymous type.
var predicate = PredicateBuilder.False<MaterialVendor>();
foreach (var x in partsToChange)
{
var item = x;
predicate = predicate.Or (m =>
m.Material.PartNumber == item.Key
&& m.Manufacturer.Name.Contains(item.Value));
}
var items = from m in MaterialVendor.Where(predicate)
select m;

[Edit] Even better, since partsToChange is a Dictionary:
var items = MaterialVendor.Where(m =>
m.Manufacturer.Name.Contains(partsToChange[m.Material.PartNumber])
).ToList();

Look into PredicateBuilder
This will allow you to build a Linq to sql expression within a loop, adding the clauses with AND / OR where necessary, then execute it once at the end.

The where clause size doesn't really matter. The querying within a loop is the part that drives maintainability and performance down.
List<MaterialVendor> items =
(
from z in MaterialVendor
let partKey = z.Material.PartNumber
where partsToChange.ContainsKey(partKey)
let partValue = partsToChange[partKey]
where z.Manufacturer.Name.Contains(partValue)
select z
).ToList();
Now that we know that linq to sql is involved... here's a mixed mode query.
List<string> keys = partsToChange.Keys.ToList();
List<MaterialVendor> items =
(
from z in MaterialVendor
let partKey = z.Material.PartNumber
where keys.Contains(partKey)
select new {MatVendor = z, Name = z.Manufacturer.Name, Key = partKey}
).AsEnumerable()
.Where(x => x.Name.Contains(partsToChange[x.partKey]))
.Select(x => x.MatVendor)
.ToList();

Related

SQL to LINQ expres

I'm trying to convert a SQL expression to Linq but I can't make it work, does anyone help?
SELECT
COUNT(descricaoFamiliaNovo) as quantidades
FROM VeiculoComSeminovo
group by descricaoFamiliaNovo
I try this:
ViewBag.familiasCount = db.VeiculoComSeminovo.GroupBy(a => a.descricaoFamiliaNovo).Count();
I need to know how many times each value repeats, but this way it shows me how many distinct values ​​there are in the column.
You can try:
var list = from a in db.VeiculoComSeminovo
group a by a.descricaoFamiliaNovo into g
select new ViewBag{
familiasCount=g.Count()
};
or
var list = db.VeiculoComSeminovo.GroupBy(a => a.descricaoFamiliaNovo)
.Select (g => new ViewBag
{
familiasCount=g.Count()
});
If you need column value:
new ViewBag{
FieldName=g.Key,
familiasCount=g.Count()
};
You don't need the GROUP BY unless there are fields other than the one in COUNT. Try
SELECT
COUNT(descricaoFamiliaNovo) as quantidades
FROM VeiculoComSeminovo
UPDATE, from your comment:
SELECT
COUNT(descricaoFamiliaNovo) as quantidades,
descricaoFamiliaNovo
FROM VeiculoComSeminovo
GROUP BY descricaoFamiliaNovo
That's it as SQL. In LINQ it is something like:
var reponse = db.VeiculoComSeminovo.GroupBy(a => a.descricaoFamiliaNovo)
.Select ( n => new
{Name = n.key,
Count = n.Count()
}
)
Not tested.
Ty all for the help.
I solved the problem using this lines:
// get the objects on db
var list = db.VeiculoComSeminovo.ToList();
// lists to recive data
List<int> totaisFamilia = new List<int>();
List<int> totaisFamiliaComSN = new List<int>();
// loop to cycle through objects and add the values ​​I need to their lists
foreach (var item in ViewBag.familias)
{
totaisFamilia.Add(list.Count(a => a.descricaoFamiliaNovo == item && a.valorSeminovo == null));
totaisFamiliaComSN.Add(list.Count(a => a.descricaoFamiliaNovo == item && a.valorSeminovo != null));
}
The query was a little slow than I expected, but I got the data

How to match a list of item to database in C#

I have a list of string
List<string> list = new List<string>();
list.Add("john");
list.Add("David");
list.Add("Sam");
Now I want to check whether my column in database contains these list Items.
var v = db.employee.where(s => s.Content.Contains(list));
My question is how can I match all list items to database column in just one query without using any loop. The query must return result if single list item is matched with column. The query I mentioned above not working. Please help me to solve this.
This will only work with the assumption that your db is an Entity Framework DbContext and that s.Content is a string. If you're using some other ORM then it may not work.
List<string> list = new List<string>();
list.Add("john");
list.Add("David");
list.Add("Sam");
var v = db.employee.Where(s => list.Contains(s.Content)).ToList();
You can do this:
List<string> list = new List<string>();
list.Add("john");
list.Add("David");
list.Add("Sam");
var v = db.employee
.ToList()
// This filtering doesn't happen in your SQL server.
.Where(s => list.Any(x => s.Content.Contains(x)));
You can try to omit .ToList() line but I'm afraid your ORM will not know how to convert the query to SQL.
If you want the whole query to be executed in SQL you should consider building the query this way:
var query = db.employee.AsQueryable();
query = list.Aggregate(query,
(query, name) => query.Where(empl => empl.Content.Contains(name)));
what about:
List<string> list = new List<string> {"john", "Warwick", "Sam"};
var vresult = _db.employee.Where(x => list.Contains(x.Content)).ToList();
please indicate what you are expecting...? like how is it not working?
If return is different than null, exists.
public List<Employee> CheckIfExists(List<string> nameList)
{
if (nameList == null)
{
return null;
}
string inClause = "";
foreach (var item in nameList)
{
inClause = string.Format("{0},{1}", inClause, item);
}
return db.employee.SqlQuery("SELECT * FROM employee WHERE employee.Name IN (#p0)", inClause).ToList();
}
Each element of list will be passed as an argument to s.Content.Contains
and here s is an employee record.
var v = db.employee.Where(s => list.Any(s.Content.Contains));
This might help you,
list with name= {"john", "David", "Sam"}
you want to check if John/David/Sam exists in the employee table.
The below query is to check if the particular name exists in employee or not
var _data = _db.employee.Where(emp => list.Any(li => li.name == emp.name)).ToList();
LINQ in query syntax:
var result = from o in db.employee
where list.Contains(o.Content)
select o;

LINQ grouping - get other, not grouped properties

I have little problem with my LINQ query (nHibernate)
I need to have count of objects znak with equal property Symbol
My query:
var tmp = (from znak in sesja.Query<Znak>()
group znak by znak.Symbol into r
select new { Name= r.Key.Name, SUM= r.Count() });
This query works, but I need to make object contains other properties of znak class.
In this case: select new { Name= r.Key.Name, SUM= r.Count() }); i can make new objects only from r.Key, (Symbol property). But I need other properties in my new object.
Is it possible ?
I recommend using lambda Linq syntax:
var items = sesja.Query<Znak().AsEnumerable();
var newList = items.GroupBy(x=>x.Symbol).Select(
x=> new { Name=x.Key.Name, Count = x.Count(), Items = x.ToList() });
read more about Linq syntax LINQ: Dot Notation vs Query Expression
I think that lambda syntax is more readable and looks much cleaner in code because it's more c# style not sql style.
Of course there will be no difference in IL code, always you can install tools like resharper, they can convert lambda syntax to sql-like syntax.
Try something like
var tmp = (from znak in sesja.Query<Znak>()
group znak by znak.Symbol into r
select new { Name= r.Key.Name, SUM= r.Count(), Items = r.ToList() });
Items property will contain actual objects in the group.

Entity Framework - Join to a List

I need to retrieve a list of entities from my database that matches a list of items in a plain list (not EF). Is this possible with Entity Framework 4.1?
Example:
var list = new List<string> { "abc", "def", "ghi" };
var items = from i in context.Items
where list.Contains(i.Name)
select i;
This works great to return rows that match one property, but I actually have a more complex property:
var list = new List<Tuple<string, string>>
{
new Tuple<string,string>("abc", "123"),
new Tuple<string,string>("def", "456")
};
// i need to write a query something like this:
var items = from i in context.Items
where list.Contains(new Tuple<string,string>(i.Name, i.Type))
select i;
I know that is not valid because it will say it needs to be a primitive type, but is there any way to do what I'm trying to accomplish or will I need to resort to a stored procedure?
You have a few options:
1) You could, of course, write a stored procedure to do what you need and call it.
2) You could read the table into memory and then query the in memory list...that way you don't have to use primitives:
var items = from i in context.Items.ToList()
where list.Contains(new Tuple<string, string>(i.Name, i.Type))
select i;
3) You could also convert your query to use primitives to achieve the same goal:
var items = from i in context.Items
join l in list
on new { i.Name, i.Type } equals
new { Name = l.Item1, Type = l.Item2 }
select i;
I would go with the second option as long as the table isn't extremely large. Otherwise, go with the first.
You need to break it down to sub-properties. For example, something like (this might not compile, i'm not able to test at the moment, but it should give you something to work with):
var items = from i in context.Items
where list.Select(x => x.Item1).Contains(i.Name)
&& list.Select(x => x.Item2).Contains(i.Type)
select i;
You have to think about what the resulting SQL would look like, this would be difficult to do directly in SQL.
My suggestion would be you split out one field of the tuples and use this to cut down the results list, get back the query result then filter again to match one of the tuples e.g.
var list = new List<string> { "abc", "def" };
var list2 = new List<Tuple<string, string>>
{
new Tuple<string,string>("abc", "123"),
new Tuple<string,string>("def", "456")
};
var items = (from i in context.Items
where list.Contains(i.Name)
select i)
.AsEnumerable()
.Where(i => list2.Any(j => j.val1 == i.Name && j.val2 == i.Type);

LINQ to SQL, how can I get a count of the search terms found in a field(s)?

Is there a way to write a query in LINQ to return the count of the search terms found in a field(s)
Basically, I want this to work:
var matches = from t in _db.Books
let score = GetScore(t, searchterms)
where score >= 1
orderby score descending
select t;
public static int GetScore(Book b, params string[] searchterms)
{
int count = 0;
foreach (string term in searchterms)
{
if (b.Title.Contains(term))
count++;
}
return count;
}
But, of course, that can't work.
Can my little GetScore function be translated into LINQ?
Thanks.
EDIT: I would also prefer to have the score accessible. Ideally I will be selecting my results into a SearchResults class (for the View) that would contain some Book info and the Book's score from the query. To update my query, it'd be something like this:
var matches = from t in _db.Books
let score = GetScore(t, searchterms)
where score >= 1
orderby score descending
select new SearchResult
{
Title = t.Title,
Type = "Book",
Link = "Books/Details/" + t.BookID,
Score = score
};
I'm sorry I wasn't more clear originally.
You can't do what you want to do without issuing multiple queries to the database - essentially one per search term. If you are happy to do that, then here is an easy way to do it:
var terms = new [] { "s", "t", "r", "e", "b", "c", };
var ids =
from term in terms
from id in _db.Books
.Where(book => book.Title.Contains(term))
.Select(book => book.Id)
group term by id into gts
orderby gts.Count() descending
select gts.Key;
var selectedIds = ids.Take(50).ToArray();
var query =
from book in _db.Books
where selectedIds.Contains(book.Id)
select book;
I wrote the ids to return a list of ids sorted by those that match the most terms first. This was to most closely get the same kind of result that you wanted in your question. I then decided to use a Take(50) to get the top 50 results. You can obviously change this strategy to suit your needs, but you must end up with an array of ids to use in the final query.
I hope this helps.
EDIT: based on OP's edit.
Here's how to query with the score included:
var terms = new [] { "s", "t", "r", "e", "b", "c", "l", "i", };
var idScores =
from term in terms
from id in _db.Books
.Where(book => book.Title.Contains(term))
.Select(book => book.BookID)
group term by id into gts
select new
{
Id = gts.Key,
Score = gts.Count(),
};
var selectedIds = idScores.Select(x => x.Id).Take(50).ToArray();
var selectedBooks =
from book in _db.Books
where selectedIds.Contains(book.BookID)
select book;
var query =
from b in selectedBooks.ToArray()
join x in idScores on b.BookID equals x.Id
orderby x.Score descending
select new
{
Title = b.Title,
Type = "Book",
Link = "Books/Details/" + b.BookID,
Score = x.Score,
};
If you want to convert your GetScore() function in LINQ then your can change whole LINQ to this:
var matches = from t in _db.Books
where searchterms.Count(c => c == t.Title) >= 1
orderby searchterms.Count(c => c == t.Title)
select t;
Now it will compile successfully but on run time when you will bind this matches to grid or any where it will throw exception "Local sequence cannot be used in LINQ to SQL implementation of query operators except the Contains() operator."
Because "problem is that we are trying to join an SQL table and an in-
memory list. Since you write the query against the SQL table, it goes
through LINQ to SQL, which rightly complains that it cannot do that.
so If you really want to do an in-memory join, then you must use _db.Books.AsEnumerable()
then query will be:
var matches = from t in _db.Books.AsEnumerable()
where searchterms.Count(c => c == t.Title) >= 1
orderby searchterms.Count(c => c == t.Title)
select t;
You have two options:
Do the search in the DB via a Stored procedure that returns a pair (BookId, Score), and then use that to do the query in LINQ2SQL
Use ToList() to execute the query, and avoid getting the "Local sequence cannot be used..." error.
For the second option, the query (in lambda syntax) would be something like
db.Books
.ToList()
.Select(t=> new SearchResult {
Title = t.Title,
Type = "Book",
Link = "Books/Details/" + t.BookID,
Score = GetScore(t, searchTerms)
})
.Where(t => t.Score >=1);
In the latter case, you'll be bringing the whole Book table to memory (and using LINQ2Objects to do the filtering), so I'd rather go for the first one.
if you want to get filtered data tou may build Expression by your self. I make some examle for your task, but you need to improve it for your source, or create some global improvements
let's start from the end. I can't make Expression returned anonymous Type so I create Generic wich I am planning to use as result
public class GRes<T>
{
public T Key { get; set; }
public int Count { get; set; }
}
I'm going to make grouping by any field of object or the object itself, and then on this grouping results call Sum fnction with some lambda which look's like this
x=>0 + IIF(x.Code.Contains(p1), 1, 0) + IIF(x.Code.Contains(p2), 1, 0) ...
where Code - is some field and p1,p2... - your search terms
at last call would be
IQueriable<GRes<Book>> result = context.Books
.GroupBy(d => d).CountIn(searchTerms, "Code")
.Where(r => r.Count > 0)
.OrderByDescending(r => r.Count);
CountIn - is extention :
public static IQueryable<GRes<TKey>> CountIn<TKey, TValue>(this IQueryable<IGrouping<TKey, TValue>> source, IEnumerable<string> values, Expression<Func<TValue,string>> selector)
{
ParameterExpression xExpr = selector.Parameters[0];
Expression propExpr = selector.Body;
MethodInfo mi = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
Expression res = Expression.Constant(0);
foreach (string term in values)
{
Expression value = Expression.Constant(term);
MethodCallExpression methodEpr = Expression.Call(propExpr, mi,value);
Expression tx = Expression.Condition(methodEpr, Expression.Constant(1), Expression.Constant(0));
res = Expression.Add(res, tx);
}
var r0 = Expression.Lambda<Func<pp_Disease, int>>(res, xExpr);
Type groupingType = typeof(IGrouping<TKey, TValue>);
ParameterExpression selPar = Expression.Parameter(groupingType, "i");
MethodInfo mi1 = typeof(Enumerable).GetMethods()
.FirstOrDefault(m => m.Name == "Sum"
&& m.ReturnParameter.ParameterType == typeof(int)
&& m.GetParameters().Count() == 2)
.MakeGenericMethod(typeof(pp_Disease));
Expression r1 = Expression.MemberInit(Expression.New(typeof(GRes<TKey>))
, Expression.Bind(typeof(GRes<TKey>).GetMember("Count")[0], Expression.Call(mi1, selPar, r0))
, Expression.Bind(typeof(GRes<TKey>).GetMember("Key")[0], Expression.Property(selPar, "Key")));
return source.Select(Expression.Lambda<Func<IGrouping<TKey, TValue>, GRes<TKey>>>(r1, selPar));
}
and when this function would be called your get SQL like this:
SELECT
...
FROM ( SELECT
...
FROM ( SELECT
....
SUM([Extent1].[A1]) AS [A1]
FROM ( SELECT
...
0 + (CASE WHEN ([Extent1].[Code] LIKE N'%2%') THEN 1 ELSE 0 END) + (CASE WHEN ([Extent1].[Code] LIKE N'%I%') THEN 1 ELSE 0 END) AS [A1]
FROM (SELECT
...
FROM [dbo].[pp_Disease] AS [pp_Disease]) AS [Extent1]
) AS [Extent1]
GROUP BY [K1], [K2], [K3], [K4]
) AS [GroupBy1]
WHERE [GroupBy1].[A1] > 0
) AS [Project1]
ORDER BY [Project1].[C1] DESC
i removed some fields declarations (EF generated sql huge), most important for this example is case string with parameters that we put in our function
i took your problem as below
books.Add("Robin HOOD");
books.Add("Charles");
books.Add("James");
search.Add("Rob");
search.Add("ood");
search.Add("les");
search.Add("am");
so you want the count of search items which are found in any of the books so if run this example , you will get the correct result.
var temp = searchterms.Where(x=>books.Where(y=>y.Title.Contains(x)).Count()>0);
var matches = from t in _db.Books
let score = searchterms.Where(term => t.Title.Contains(term)).Count()
where score >= 1
orderby score descending
select t;
edit
if its linq to sql i think my solution is devide into 2 part
// just results in db
string terms = searchterms.Aggregate((cur,nex) => cur+"^"+nex);
var results = from t in _db.Books
where terms.Contains(t.Title.Contains)
select t;
// sort results in c#
var sorting = for entry in results
let score = searchterms.Where(term => entry.Title.Contains(term)).Count()
orderby score
select new {......};
edit
oooh sorry little mistake. on first query
string terms = searchterms.Aggregate((cur,nex) => cur+"^"+nex);
var results = from t in _db.Books
where terms.Contains(t.Title.Contains)
select t;
change to
string terms = searchterms.Aggregate((cur,nex) => cur+"^"+nex);
var results = from t in _db.Books
where terms.Contains("^" + t.Title + "^")
select t;
the reason i use this query is 'var results' will gives all the result from db which should be ok becus it's string.Contains. Then sort the result in next query.

Categories

Resources