LINQ Union Allways returning empty sets - asp.net-mvc

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

Related

Performance issue with Reflection

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')");
}

Unable to catch if a list item name is missing

I have the following code which groupBY my table and select the count based on the model name:-
var IT360Counts = entities.Resources.Where(a => String.IsNullOrEmpty(a.ASSETTAG) && (a.SystemInfo.ISSERVER == true))
.GroupBy(a => a.SystemInfo.MODEL.ToLower())
.Select(g => new
{
Action = g.Key.ToLower(),
ItemCount = g.Count()
}).ToLookup(a => a.Action);
Then i will referecne the var content such as :-
IT360RouterNo = IT360Counts["router"] == null ? 0 : IT360Counts["router"].SingleOrDefault().ItemCount,
The above will work well, unless when the first query does not have any router, then the second statement will always return null exception. so my question is weather there is a way to catch if IT360Counts["router"] exists sor not ?
Thanks
This will happen when IT360Counts["router"] is not null but an empty list. In that case IT360Counts["router"].SingleOrDefault() will return null, so when accessing its ItemCount property you will get a null exception.
This happens because the indexer in the Lookup returns an empty list when the key is not found. See remarks section in msdn. Try checking if the lookup contains the key, IT360Counts.Contains("router"). This way you can do:
IT360RouterNo = IT360Counts.Contains("router") ? IT360Counts["router"].SingleOrDefault().ItemCount : 0,
As a side note, have you also considered using ToDictionary instead of ToLookup? The dictionary key would be your Action and the value the ItemCount, so when retrieving the values you just get the value in the dictionary for a key like "router". If you are you always doing .SingleOrDefault().ItemCount and never expect more than one item with the same Action, you may be better using a dictionary.
For the sake of completion this idea would be:
var IT360Counts = entities.Resources.Where(a => String.IsNullOrEmpty(a.ASSETTAG) &&(a.SystemInfo.ISSERVER == true))
.GroupBy(a => a.SystemInfo.MODEL.ToLower())
.Select(g => new
{
Action = g.Key.ToLower(),
ItemCount = g.Count()
}).ToDictionary(a => a.Action, a => a.ItemCount);
IT360RouterNo = IT360Counts.ContainsKey("router") ? IT360Counts["router"] : 0,
Hope it helps!

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

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

How to specify a condition in an Entity Framework join?

I have a Blogs table related to BlogComments table with a FK.
I need to get, through Linq, all the BlogComments items that match a certain flag
If i do:
db.Blogs.Where(b => b.BlogComments.Where(bc=>bc.Where(bc.Flag1==true));
I get "Cannot implicity convert type IEnumerable to bool"
Which is the best way to solve this problem?
Because this expression:
b.BlogComments.Where(...)
returns an IEnumerable (of BlogComments), but you are then passing it into this method:
db.Blogs.Where(...)
which expects a function that returns a bool, not an IEnumerable.
You probably need something like this:
var blogId = 5;
db.BlogComments.Where(bc => bc.BlogId == blogId && bc.Flag1 == true)
If you need to select comments from multiple blogs, then you could try using Contains:
var blogIds = new [] {1,2,3,4,5};
db.BlogComments.Where(bc => blogIds.Contains(bc.BlogId) && bc.Flag1 == true)
If you want to place criteria on the set of blogs, as well as the comments, then you could do this in one query using a join:
var query = from b in db.Blogs
join c in db.BlogComments on c.Blog equals b
where b.SomeField == "some value"
&& c.Flag1 == true
select c;
You could write it in LINQ form.
var blogs = from b in db.Blogs
join c in db.BlogComments
on b.BlogId equals c.BlogId
where c.Flag1
select b;
If you have a composite key you can write
on new { A = b.BlogKey1, B = b.BlogKey2 }
equals new { A = c.CommentKey1, B = c.CommentKey2 }
If it were me, I would just have another DbSet in your DbContext.
DbSet<BlogComment> BlogComments
and just search through there without going through Blogs.
db.BlogComments.Where(bc => bc.Flag1 == true);
If anyone knows if there's anything wrong in doing so, then I'm all ears :)

Resources