Entity Framework + conditionally appended Where() clauses - entity-framework

This is driving me nuts. What am I missing here. I'm using EF and if I have code like the following:
using (LexiconEntities ctx = new LexiconEntities())
{
var query = from w in ctx.Words
select new WordEntryDataModel
{
Word = w.Anagram,
NumOfAnagrams = w.NumAnagrams.Value,
Length = w.Length.Value,
...
};
SearchCriterion c1 = new SearchCriterion();
SearchCriterion c2 = new SearchCriterion();
c1.MinValue = 3;
c1.MaxValue = 3;
c2.MinValue = 4;
c2.MaxValue = 4;
query = query.Where(w => w.Length >= c1.MinValue && w.Length <= c1.MaxValue);
query = query.Where(w => w.NumOfAnagrams >= c2.MinValue && w.NumOfAnagrams <= c2.MaxValue);
...
}
And when I debug the query, I get the proper results (8 records). This also works as expected in Linqpad (which frickin' rocks).
But if I construct the search criteria as a List of criterion objects, and I dynamically add on Where() clauses by iterating over the search criteria as follows:
foreach (SearchCriterion c in criteria.SearchCriteria)
{
switch (c.Type)
{
case SearchCriterionType.WordLength:
query = query.Where(w => w.Length >= c.MinValue && w.Length <= c.MaxValue);
break;
case SearchCriterionType.NumberOfAnagrams:
query = query.Where(w => w.NumOfAnagrams >= c.MinValue && w.NumOfAnagrams <= c.MaxValue);
break;
...
case SearchCriterionType.NumberOfVowels:
query = query.Where(w => w.NumOfVowels >= c.MinValue && w.NumOfVowels <= c.MaxValue);
break;
}
}
...
I get the totally different (and incorrect) results. I've debugged the switch statement and my search criteria has two properly constructed criterion objects set to correct values. There's something about the conditionally added where clauses that my query doesn't like.
What am I doing wrong?

Closure. Assign c to a local variable within the loop. Also see my SO answer here

Related

Accent insensitive in filter backend to datatable in ASP.NET MVC 5

I made a method on the back-end side to handle the filter of my datatable.
On the other hand, this one does not manage the accents of the French language, so if I have "école" and I write "ecole" it cannot find it.
I found this method on another question on stackoverflow
public static String RemoveDiacritics(this String s)
{
String normalizedString = s.Normalize(NormalizationForm.FormD);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < normalizedString.Length; i++)
{
Char c = normalizedString[i];
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
{
stringBuilder.Append(c);
}
}
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
}
and it works, but only for part of my problem. It works on the letter or the word that is written in the search, but I am not able to apply it in my linq query, so with the .RemoveDiacritics() method my "école" becomes "ecole", but I don't am not able to apply it in the column of my table and it always looks for "école".
Here the code for the search:
if (search != null)
{
int n;
search = search.Trim();
var isNumeric = int.TryParse(search, out n);
if (isNumeric)
{
IdFilter = n;
query = query.Where(x => x.UsagerId == IdFilter || x.Niveau == IdFilter);
}
else if (search != "")
{
// this line work
textFilter = search.ToLower().RemoveDiacritics();
// This is the full line, but absolutely takes the accents out to get the right information out
// query = query.Where(x => x.Nom.ToLower().Contains(textFilter) || x.Prenom.ToLower().Contains(textFilter) || x.Username.ToLower().Contains(textFilter) || x.Email.ToLower().Contains(textFilter) || x.EtabNom.ToLower().Contains(textFilter) || x.ActifStatut.ToLower().Contains(textFilter));
// This is the line that will replace the line above, which I try and it doesn't work ( this part: x.Prenom.ToLower().RemoveDiacritics())
query = query.Where(x => x.Prenom.ToLower().RemoveDiacritics().Contains(textFilter));
}
}
This is the basic query:
IQueryable<ListeUsagers> query = (from u in db.USAGERs
join e in db.ETABLISSEMENTs on u.USAGER_INST equals e.ETAB_CODE
where u.USAGER_INST == instId && u.USAGER_NIVEAU > 3 && u.USAGER_NIVEAU < 5 //&& u.USAGER_ACTIF == 1
select new ListeUsagers()
{
UsagerId = u.USAGER_id,
Nom = u.USAGER_NOM,
Prenom = u.USAGER_PRENOM,
EtabCode = e.ETAB_CODE,
EtabNom = e.ETAB_NOM_COURT,
EtabType = e.ETAB_TYPE,
Niveau = u.USAGER_NIVEAU,
Username = u.USAGER_USERNAME,
UserPassword = u.USAGER_MP,
DateCreation = u.USAGER_DATE_INSC,
Sexe = u.USAGER_SEXE,
Lang = u.USAGER_LANGUE,
Telephone = u.USAGER_TELEPHONE,
Email = u.USAGER_EMAIL,
FonctionTravail = u.USAGER_FONCTION,
LieuTravail = u.USAGER_LIEUTRAVAIL,
Note = u.USAGER_NOTE,
Actif = u.USAGER_ACTIF,
ActifStatut = u.USAGER_ACTIF == 0 ? "Inactif" : "Actif"
});
This is the error:
LINQ to Entities does not recognize the method 'System.String RemoveDiacritics(System.String)' method, and this method cannot be translated into a store expression.
There's built-in functionality to do this in entityframework: https://learn.microsoft.com/en-us/ef/core/miscellaneous/collations-and-case-sensitivity if you're using EF 5+
You'll want an accent insensitive collation ("AI", not "AS" in the examples on that page.)

How to write this Query in LINQ

I have one LINQ query with foreach loop. Everything is fine. But it takes more time to get the value. So anybody suggest me how can i do this in LINQ query itself.
Code
NormValue = "";
c = 0;
var NormValuelist = db.BCont.Where(x => x.BId == BId && x.TNo == Tag).ToList();
foreach (var item in NormValuelist)
{
if (c == 0)
NormValue = item.NormValue;
else
NormValue += " " + item.NormValue;
c = 1;
}
Thanks
You can rewrite this query with string.Join to avoid creating multiple string objects in a loop, like this:
string NormValue = string.Join(" ", db.BCont.Where(x => x.BId == BId && x.TNo == Tag));
The number of round-trips to DB will remain the same, but the creation of List<string> and the partially concatenated string objects will be optimized out.
In addition to using String.Join, you could also use Enumerable.Aggregate:
var NormValueList =
db.BCont.Where(x => x.Bid == BId && x.TNo == Tag)
.Select(x => x.NormValue)
.Aggregate((s, x) => s + " " + x);
If you are having large items in "NormValuelist" then it would be better to use StringBuilder instead of string(NormValue)

How can I embed optional Where parameters in a Linq to EF statement?

Let's say we have a description field on my form with optional check boxes. The check boxes represent which fields to search when doing the lookup. Right now I have a matrix of look ups that call their unique version of where clause. It works but I think it smells a bit.
Here is an excerpt
// Look for part numbers decide how many fields to search and use that one.
// 0 0 X
if (!PartOpt[0] && !PartOpt[1] && PartOpt[2])
{
query = query.Where(p => (p.PartNumAlt2.Contains(partSearchRec.inventory.PartNum)));
}
// 0 X 0
if (!PartOpt[0] && PartOpt[1] && !PartOpt[2])
{
query = query.Where(p => (p.PartNumAlt.Contains(partSearchRec.inventory.PartNum)));
}
// 0 X X
if (!PartOpt[0] && PartOpt[1] && PartOpt[2])
{
query = query.Where(p => (p.PartNumAlt.Contains(partSearchRec.inventory.PartNum)
|| p.PartNumAlt2.Contains(partSearchRec.inventory.PartNum)));
}
// X 0 0
if (PartOpt[0] && !PartOpt[1] && !PartOpt[2])
{
query = query.Where(p => (p.PartNum.Contains(partSearchRec.inventory.PartNum)));
}
. . .
This goes on for a while and seems to be prone to coding errors. In each case we are looking for the same information in any of the selected fields. If I was doing this in SQL I could simply build up the WHERE clause as needed.
Once again I rubber ducked my way to an answer. Rather than throw the question away, here is what I came up with. Is it efficient?
if (partSearchRec.optPartNum || partSearchRec.optAltPartNum1 || partSearchRec.optAltPartNum2)
{
query = query.Where(p => (
(partSearchRec.optPartNum && p.PartNum.Contains(partSearchRec.inventory.PartNum))
|| (partSearchRec.optAltPartNum1 && p.PartNumAlt.Contains(partSearchRec.inventory.PartNum))
|| (partSearchRec.optAltPartNum2 && p.PartNumAlt2.Contains(partSearchRec.inventory.PartNum))));
}
Basically if any of the check boxes are set we will execute the query. Each line of the query will be processed only if the check box was checked. If the left side of an AND is false it doesn't process the right.
This is an aera that Delphi's with statement would be handy. I also learned that you can't use an array inside the LINQ statement.

MongoDB - combining multiple Numeric Range queries (C# driver)

*Mongo Newbie here
I have a document containing several hundred numeric fields which I need to query in combination.
var collection = _myDB.GetCollection<MyDocument>("collection");
IMongoQuery mongoQuery; // = Query.GT("field", value1).LT(value2);
foreach (MyObject queryObj in Queries)
{
// I have several hundred fields such as Height, that are in queryObj
// how do I build a "boolean" query in C#
mongoQuery = Query.GTE("Height", Convert.ToInt16(queryObj.Height * lowerbound));
}
I have several hundred fields such as Height (e.g. Width, Area, Perimeter etc.), that are in queryObj how do I build a "boolean" query in C# that combines range queries for each field in conjunction.
I have tried to use the example Query.GT("field", value1).LT(value2);, however the compiler does not accept the LT(Value) construct. In any event I need to be able to build a complex boolean query by looping through each of the numeric field values.
Thanks for helping a newbie out.
EDIT 3:
Ok, it looks like you already have code in place to build the complicated query. In that case, you just needed to fix the compiler issue. Am assuming you want to do the following (x > 20 && x < 40) && (y > 30 && y < 50) ...
var collection = _myDB.GetCollection<MyDocument>("collection");
var queries = new List<IMongoQuery>();
foreach (MyObject queryObj in Queries)
{
//I have several hundred fields such as Height, that are in queryObj
//how do I build a "boolean" query in C#
var lowerBoundQuery = Query.GTE("Height", Convert.ToInt16(queryObj.Height * lowerbound));
var upperBoundQuery = Query.LTE("Height", Convert.ToInt16(queryObj.Height * upperbound));
var query = Query.And(lowerBoundQuery, upperBoundQuery);
queries.Add(query);
}
var finalQuery = Query.And(queries);
/*
if you want to instead do an OR,
var finalQuery = Query.Or(queries);
*/
Original Answer.
var list = _myDb.GetCollection<MyDoc>("CollectionName")
.AsQueryable<MyDoc>()
.Where(x =>
x.Height > 20 &&
x.Height < 40)
.ToList();
I have tried to use the example Query.GT("field", value1).LT(value2);,
however the compiler does not accept the LT(Value) construct.
You can query MongoDB using linq, if you are using the official C# driver. That ought to solve the compiler issue I think.
The more interesting question I have in mind is, how are you going to construct that complicated boolean query?
One option is to dynamically build an Expression and then pass that to the Where
My colleague is using the following code for something similar...
public static IQueryable<T> Where<T>(this IQueryable<T> query,
string column, object value, WhereOperation operation)
{
if (string.IsNullOrEmpty(column))
return query;
ParameterExpression parameter = Expression.Parameter(query.ElementType, "p");
MemberExpression memberAccess = null;
foreach (var property in column.Split('.'))
memberAccess = MemberExpression.Property
(memberAccess ?? (parameter as Expression), property);
//change param value type
//necessary to getting bool from string
ConstantExpression filter = Expression.Constant
(
Convert.ChangeType(value, memberAccess.Type)
);
//switch operation
Expression condition = null;
LambdaExpression lambda = null;
switch (operation)
{
//equal ==
case WhereOperation.Equal:
condition = Expression.Equal(memberAccess, filter);
lambda = Expression.Lambda(condition, parameter);
break;
//not equal !=
case WhereOperation.NotEqual:
condition = Expression.NotEqual(memberAccess, filter);
lambda = Expression.Lambda(condition, parameter);
break;
//string.Contains()
case WhereOperation.Contains:
condition = Expression.Call(memberAccess,
typeof(string).GetMethod("Contains"),
Expression.Constant(value));
lambda = Expression.Lambda(condition, parameter);
break;
}
MethodCallExpression result = Expression.Call(
typeof(Queryable), "Where",
new[] { query.ElementType },
query.Expression,
lambda);
return query.Provider.CreateQuery<T>(result);
}
public enum WhereOperation
{
Equal,
NotEqual,
Contains
}
Currently it only supports == && !=, but it shouldn't be that difficult to implement >= or <= ...
You could get some hints from the Expression class: http://msdn.microsoft.com/en-us/library/system.linq.expressions.expression.aspx
EDIT:
var props = ["Height", "Weight", "Age"];
var query = _myDb.GetCollection<MyDoc>("CName").AsQueryable<MyDoc>();
foreach (var prop in props)
{
query = query.Where(prop, GetLowerLimit(queryObj, prop), WhereOperation.Between, GetUpperLimit(queryObj, prop));
}
// the above query when iterated over, will result in a where clause that joins each individual `prop\condition` with an `AND`.
// The code above will not compile. The `Where` function I wrote doesnt accept 4 parameters. You will need to implement the logic for that yourself. Though it ought to be straight forward I think...
EDIT 2:
If you don't want to use linq, you can still use Mongo Query. You will just need to craft your queries using the Query.And() and Query.Or().
// I think this might be deprecated. Please refer the release notes for the C# driver version 1.5.0
Query.And(Query.GTE("Salary", new BsonDouble(20)), Query.LTE("Salary", new BsonDouble(40)), Query.GTE("Height", new BsonDouble(20)), Query.LTE("Height", new BsonDouble(40)))
// strongly typed version
new QueryBuilder<Employee>().And(Query<Employee>.GTE(x => x.Salary, 40), Query<Employee>.LTE(x => x.Salary, 60), Query<Employee>.GTE(x => x.HourlyRateToClients, 40), Query<Employee>.LTE(x => x.HourlyRateToClients, 60))

T-SQL (Order by Case) to LinQ

I have the folowwing statement and I would like to have a LINQ equivalent:
SELECT *
FROM People
where Name like '%something%'
ORDER BY CASE
WHEN Name LIKE 'something%' then 1
WHEN Name LIKE '%something%' then 2
ELSE 3 END
Basically, I'm retrieving all the rows which contains a value (in this case 'something') and I'm ordering them: first the ones starting with that value, and then the remaining.
Any idea on how to do that in LinQ?
I've came out with the following solution.
var dc = new EntityContext();
var result = dc
// Condition part
.People.Where(x => x.Name.IndexOf("Foo") > -1) // This part is translated to like
// projection part
.Select(x => new { Person = x, Weight = x.Name.IndexOf("Bar") > -1 ? 1 : (x.Name.IndexOf("Baz") ? 2 : 0)})
// Order
.OrderBy(x => x.Weight)
// Final projection
.Select(x => x.Person);
I guess everything is self explanatory. First you select under your condition, then create a new object with weights necessary, then order it and finally take the necessary people.
I am not able to verify this, but something like this might work. The code can definitely be optimized/cleaned up, but in theory this just might work :)
The only question is whether the contains in the comparable delegate will translate the way it does in the Where. So, you might need to use an IndexOf or similar (as Oybek implemented)
var queryResult =
people
.Where(person=>person.name.Contains(#"/something/"))
.OrderBy(person=>person.Name,
delegate(string name1, string name2)
{
int result1, result2;
if(name1.Contains(#"something/")) result1 = 1;
else if(name1.Contains(#"/something/")) result1 = 2;
else result1 = 3;
if(name2.Contains(#"something/")) result2 = 1;
else if(name2.Contains(#"/something/")) result2 = 2;
else result2 = 3;
return result1.CompareTo(result2);
})