Performance issue with Reflection - asp.net-mvc

I am trying to make a Dynamic search for EF Core. I made the whole thing in a loop like this:
foreach (var i in vm.SearchProperties)
{
if (result == null)
result = db.MyTable.Where(x => x.GetType().GetProperty(i).GetValue(x, null).ToString().ToLower().StartsWith("MySearchString"));
else
result = result.Where(x => x.GetType().GetProperty(i).GetValue(x,null).ToString().ToLower().StartsWith(i.Suchfeld.ToLower(“mySearchString”)));
}
Before I added the reflection part it runs pretty fast. As soon I added the Reflection to it, it got slowed down by a factor of 1000. Any ideas how I get it speeded up or around the reflection part.

Your expression is too complex to be interpreted by the LINQ to SQL converter, so it is being compiled and executed on every item in your table, so it's no surprise it executes exceedingly slow.
You need to build an expression tree based on which properties you want to search, then construct an Expression<Func<MyType, bool>> to pass to your Where(...) method. That way the LINQ to SQL converter recognizes it.
Try this:
ParameterExpression param = Expression.Parameter(typeof(MyType));
MethodInfo stringStartsWith = typeof(string).GetMethods().First(m => m.Name == "StartsWith" && m.GetParameters().Length == 1);
PropertyInfo firstProp = typeof(MyType).GetProperty(vm.SearchProperties.First());
MemberExpression firstMembAccess = Expression.Property(param, firstProp);
MethodCallExpression firstStartsWithExpr = Expression.Call(firstMembAccess, stringStartsWith, Expression.Constant(mySearchString));
Expression current = firstStartsWithExpr;
foreach (string s in vm.SearchProperties.Skip(1))
{
PropertyInfo prop = typeof(MyType).GetProperty(s);
MemberExpression membAccess = Expression.Property(param, prop);
MethodCallExpression startsWithExpr = Expression.Call(membAccess, stringStartsWith, Expression.Constant(mySearchString));
current = Expression.OrElse(current, startsWithExpr);
}
Expression<Func<MyType, bool>> mySearchExpression = Expression.Lambda<Func<MyType, bool>>(current, param);
result = db.MyTable.Where(mySearchExpression);
Note: MyType refers to whatever your entity type is.

As people already answered, reflection cannot be used in IQueryable.
Data are loaded in the memory and the Where clause is performed.
It's similar as if you have done the following code (adding ToList() before the where clause):
foreach (var i in vm.SearchProperties)
{
if (result == null)
result = db.MyTable.ToList().Where(x => x.GetType().GetProperty(i).GetValue(x, null).ToString().ToLower().StartsWith("MySearchString"));
else
result = result.ToList().Where(x => x.GetType().GetProperty(i).GetValue(x,null).ToString().ToLower().StartsWith(i.Suchfeld.ToLower(“mySearchString”)));
}
#Mr Anderson is right. You need to use expression tree instead of reflection.
However, using expression tree may be sometimes to much complex to build.
Disclaimer: I'm the owner of the project Eval-Expression.NET
This project allows you to evaluate and execute dynamic expression at runtime.
In short, you can pass a dynamic string to the where clause.
Wiki: Eval-Expression.NET - LINQ Dynamic
foreach (var i in vm.SearchProperties)
{
if (result == null)
result = db.MyTable.Where(x => "x." + i + ".ToLower().StartsWith('MySearchString')");
else
result = result.Where(x => "x." + i + ".ToLower().StartsWith('MySearchString')");
}

Related

Searching a List of objects for a particular object in dart using "where"

I would like to obtain an object from a List based on a specific search criteria of its member variable
this is the code I am using
class foo
{
foo(this._a);
int _a;
}
List<foo> lst = new List<foo>();
main()
{
foo f = new foo(12);
lst.add(f);
List<foo> result = lst.where( (foo m) {
return m._a == 12;
});
print(result[0]._a);
}
I am getting the error and not sure how to resolve this
Uncaught exception:
TypeError: Instance of 'WhereIterable<foo>': type 'WhereIterable<foo>' is not a subtype of type 'List<foo>'
I am trying to search for an object whose member variable a == 12. Any suggestions on what I might be doing wrong ?
The Iterable.where method returns an iterable of all the members which satisfy your test, not just one, and it's a lazily computed iterable, not a list. You can use lst.where(test).toList() to create a list, but that's overkill if you only need the first element.
You can use lst.firstWhere(test) instead to only return the first element, or you can use lst.where(test).first to do effectively the same thing.
In either case, the code will throw if there is no element matched by the test.
To avoid throwing, you can use var result = lst.firstWhere(test, orElse: () => null) so you get null if there is no such element.
Another alternative is
foo result;
int index = lst.indexWhere(test);
if (index >= 0) result = lst[index];
The answer is simple. Iterable.where returns an Iterable, not a List. AFAIK this is because _WhereIterable does its computations lazily.
If you really need to return a List, call lst.where(...).toList().
Otherwise, you can set result to be an Iterable<foo>, instead of a List<foo>.
or you can go crazy and do this:
bool checkIfProductNotFound(Map<String, Object> trendingProduct) {
bool isNotFound = this
._MyProductList
.where((element) => element["id"] == trendingProduct["id"])
.toList()
.isEmpty;
return isNotFound ;
}

Asp.net MVC 4 Database Query returning sql query.

I am trying to query my database to get a specific data from my database. however when I convert the query to string it doesn't return the select value, instead it returns the whole SQL Query in a string. I am stumped on why this is happening
public ActionResult StudiedModules()
{
Model1 studiedModules = new Model1();
List<StudiedModulesModel> listModules = new List<StudiedModulesModel>();
using (EntityOne context = new EnitityOne())
{
foreach(var module in context.StudiedModules){
studiedModules.School = context.ModuleDatas.Where(p=>p.ModuleCode == module.ModuleCode).Select(u=>u.School).ToString();
studiedModules.Subject = context.ModuleDatas.Where(p=>p.ModuleCode == module.ModuleCode).Select(u=>u.Subject).ToString();
}
}
var data = listModules;
return View(data);
}
Calling ToString() on an Entity Framework Linq query like that will in fact return the SQL Query. So as it's written, your code is doing exactly what you wrote it to do.
If you want to select the first result from the IQueryable<T>, then you need to call First() before your ToString(). So, try changing
studiedModules.School = context.ModuleDatas.Where(p=>p.ModuleCode == module.ModuleCode).Select(u=>u.School).ToString();
studiedModules.Subject = context.ModuleDatas.Where(p=>p.ModuleCode == module.ModuleCode).Select(u=>u.Subject).ToString()
to
studiedModules.School = context.ModuleDatas.Where(p=>p.ModuleCode == module.ModuleCode).Select(u=>u.School).First().ToString();
studiedModules.Subject = context.ModuleDatas.Where(p=>p.ModuleCode == module.ModuleCode).Select(u=>u.Subject).First().ToString()
There are a whole lot more methods available depending on what you're trying to accomplish. If you want to get a list, use ToList(), or as Uroš Goljat pointed out, you can get a comma-separated list of values using the Aggregate( (a, b)=> a + ", " + b) method.
How about using Aggregate( (a, b)=> a + ", " + b) instead of ToString().
Regards,
Uros

LINQ query with omitted user input

so I have a form with several fields which are criteria for searching in a database.
I want to formulate a query using LINQ like so:
var Coll = (from obj in table where value1 = criteria1 && value2 = criteria2...)
and so on.
My problem is, I don't want to write it using If statements to check if every field has been filled in, nor do I want to make separate methods for the various search cases (criteria 1 and criteria 5 input; criteria 2 and criteria 3 input ... etc.)
So my question is: How can I achieve this without writing an excessive amount of code? If I just write in the query with comparison, will it screw up the return values if the user inputs only SOME values?
Thanks for your help.
Yes, it will screw up.
I would go with the ifs, I don't see what's wrong with them:
var query = table;
if(criteria1 != null)
query = query.Where(x => x.Value1 == criteria1);
if(criteria2 != null)
query = query.Where(x => x.Value2 == criteria2);
If you have a lot of criteria you could use expressions, a dictionary and a loop to cut down on the repetitive code.
In an ASP.NET MVC app, chances are your user input is coming from a form which is being POSTed to your server. In that case, you can make use of strongly-typed views, using a viewmodel with [Required] on the criteria that MUST be provided. Then you wrap your method in if (ModelState.IsValid) { ... } and you've excluded all the cases where the user hasn't given you something they need.
Beyond that, if you can collect your criteria into a list, you can filter it. So, you could do something like this:
filterBy = userValues.Where(v => v != null);
var Coll = (from obj in table where filterBy.Contains(value1) select obj);
You can make this more complex by having a Dictionary (or Lookup for non-unique keys) that contains a user-entered value along with some label (an enum, perhaps) that tells you which field they're filtering by, and then you can group them by that label to separate out the filters for each field, and then filter as above. You could even have a custom SearchFilter object that contains other info, so you can have filters with AND, NOT and OR conditions...
Failing that, you can remember that until you trigger evaluation of an IQueryable, it doesn't hit the database, so you can just do this:
var Coll = (from obj in table where value1 == requiredCriteria select obj);
if(criteria1 != null)
{
query = query.Where(x => x.Value1 == criteria1);
}
//etc...
if(criteria5 != null)
{
query = query.Where(x => x.Value5 == criteria5);
}
return query.ToList();
That first line applies any criteria that MUST be there; if there aren't any mandatory ones then it could just be var Coll = table;.
That will add any criteria that are provided will be applied, any that aren't will be ignored, you catch all the possible combinations, and only one query is made at the end when you .ToList() it.
As I understand of your question you want to centralize multiple if for the sake of readability; if I were right the following would be one of some possible solutions
Func<object, object, bool> CheckValueWithAnd = (x, y) => x == null ? true : x==y;
var query = from obj in table
where CheckValue(obj.value1, criteria1) &&
CheckValue(obj.value2, criteria2) &&
...
select obj;
It ls flexible because in different situations or scenarios you can change the function in the way that fulfill your expectation and you do not need to have multiple if.
If you want to use OR operand in your expression you need to have second function
Func<object, object, bool> CheckValueWithOr = (x, y) => x == null ? false : x==y;

Cannot compare in linq

I am trying to get the result of the user logged in but receiving this error :
"Cannot compare elements of type 'System.Linq.IQueryable`1'. Only
primitive types, enumeration types and entity types are supported. "
Here is the query I'm applying in my index action:
var viewModel = new PointsViewModel();
viewModel.Point = db.Point.ToList();
viewModel.Redeem = db.Redeem.ToList();
TempData["UserPoints"] = null;
var usrname = (from a in db.Instructors
where a.Email == User.Identity.Name
select new { a.PersonID });
if (usrname.Count().Equals(0))
{
TempData["UserPoints"] = "You have not earn any points yet.";
return View();
}
viewModel.instructor = db.Instructors
.Where(i => i.PersonID.Equals(usrname))// if I directly insert id here then it works properly but I don't want direct inserts
.Single();
PopulateAssignedPointData(viewModel.instructor);
return View(viewModel);
Please help me with this please...I am unable to find any solution on google
It's because you're passing usrname as a parameter to another query. usrname is a query, not a value, so you need to retrieve a value from the query (in this case, by using First(), but you could just as easily use Single() if you like) before you can use it as a parameter in another query. I would recommend the following changes:
if (!usrname.Any())
{
TempData["UserPoints"] = "You have not earn any points yet.";
return View();
}
var personId = usrname.First();
viewModel.instructor = db.Instructors
.Where(i => i.PersonID.Equals(personId))
.Single();
I also changed usrname.Count().Equals(0) to !usrname.Any() as it is more idiomatic (it will use the exists keyword in SQL, rather than count)
Try to use this:
viewModel.instructor = db.Instructors
.Where(i => usrname.Any(u => u.PersonID == i.PersonID))
.Single();

LINQ Union Allways returning empty sets

I'm doing some LINQ to look for keywords typed in a textbox. Each keyword ends with ";" and i need to look for itens that contain at least one of those keys.
I thought that i would be able to achieve this with this loop
IEnumerable<ItemFAQ> allResults = null;
foreach (var item in termosPesquisaIsolados)
{
IEnumerable<ItemFAQ> tempResults =
_dbHelpDesk.ItemFAQ
.Where(x => x.DescricaoResposta.Contains(item) || x.TituloTopico.Contains(item))
.ToList()
.Select(x => new ItemFAQ
{
IdPergunta = x.IdPergunta,
TituloTopico = x.TituloTopico,
DescricaoResposta = x.DescricaoResposta.Substring(0, 100)
});
if (allResults != null)
allResults = allResults.Union(tempResults);
else
allResults = tempResults;
}
At first iteration tempResult returns in a test 3 elements then it passes then to allResult, everything ok with that.
The second iteration, tempResult returns 2 ocurrences... then according to code allResult should receive the Union of AllResult and the TempResults, but no matter what i do, when i use the Union clause the result in an Empty Set.
So 3 + 2 = 0 at the end after using Union.
Do you guys can see the mistake at this peace of code or know some issues that could lead to this error ?
Thanks for your time.
try replacing this line:
allResults = allResults.Union(tempResults);
with:
allResults = allResults.Union(tempResults).ToList();

Resources