IQueryable vs ICollection (List) - c#

I have such a case study:
ToList() case:
List<CategoryType> categories = (from c in categoryTypes where c.IsSysParam == isSysParamCategory select new CategoryType { Code = c.Code, CreateDate = c.CreateDate, EditDate = c.EditDate, IsProductCategory = c.IsProductCategory, IsSysParam = c.IsSysParam, Name = c.Name, TypeId = c.TypeId, ValueTypes = new List<ValueType>() }).ToList();
List<ValueType> valueTypeList = new List<ValueType>();
foreach (var c in categories.ToList())
{
valueTypeList = categoryTypes.Where(x => x.TypeId == c.TypeId).SelectMany(v => v.ValueTypes).Where(v => v.ParentValueId == null).ToList();
c.ValueTypes = valueTypeList;
}
IQueryable case:
When I change in first query - List<CategoryType> to IQueryable<CategoryType> and remove ToList() from the end of query then I dont have any result:
Question:
I am asking for an explanation, I do not understand why this is happening. I know that the IQueryable makes some part of the work on the database side.
Edit:
The code is working, pictures shows the final effect.
I have:
public IQueryable<CategoryType> CategoryTypePagination { get; set; }
and in the end of ToList() case:
this.CategoryTypePagination = categories.AsQueryable();
in IQueryable case just removed .AsQueryable()

You have to look at "Deferred Query Execution" and "Immediate Query Execution"

Accrodingly to this, IQueryable uses something called lazy loading.
So the results of IQueryable aren't loaded until they are first used, for example in Sum, ToList or ToArray methods, while ToList requieres data to be loaded. Thus you see the difference after initailizing both objects.

Related

Linq to entities - SQL Query - Where list contains object with 2 properties (or more)

Having the following example:
var myIds = db.Table1.Where(x=>x.Prop2 == myFilter).Select(x=>x.Id).ToList();
var results = db.Table2.Where(x=> myIds.Contains(x.T1)).ToList();
This part is straight forward.
However, now I am facing a "slight" change where my "filter list" has 2 properties instead of only one:
// NOTE: for stackoverflow simplification I use a basic query to
// get my "myCombinationObject".
// In reality this is a much more complex case,
// but the end result is a LIST of objects with two properties.
var myCombinationObject = db.Table3.Where(x=>x.Prop3 == myFilter)
.Select(x=> new {
Id1 = x.T1,
Id2 = x.T2
}).ToList();
var myCombinationObjectId1s = myCombinationObject.Select(x=>xId1).ToList();
var myCombinationObjectId2s = myCombinationObject.Select(x=>xId2).ToList();
// step#1 - DB SQL part
var resultsRaw = db.Tables.Where( x=>
myCombinationObjectId1s.Contains(x.Prop1)
|| myCombinationObjectId2s.Contains(x.Prop2))
.ToList();
// step#2 - Now in memory side - where I make the final combination filter.
var resultsFiltered = resultsRaw.Where( x=>
myCombinationObject.Contains(
new {Id1 = x.Prop1, Id2 = x.Prop2 }
).ToList();
My question: is it even possible to merge the step#2 in the step#1 (query in linq to entities) ?
I've managed once to do what you want, however it is pretty hard and requires changing entity model a bit. You need an entity to map type
new {Id1 = x.Prop1, Id2 = x.Prop2 }
So you need enity having 2 properties - Id1 and Id2. If you have one - great, if not then add such entity to your model:
public class CombinationObjectTable
{
public virtual Guid Id1 { get; set; }
public virtual Guid Id2 { get; set; }
}
Add it to your model:
public DbSet<CombinationObjectTable> CombinationObjectTable { get; set; }
Create new migration and apply it database (database will have now additional table CombinationObjectTable). After that you start to build a query:
DbSet<CombinationObjectTable> combinationObjectTable = context.Set<CombinationObjectTable>();
StringBuilder wholeQuery = new StringBuilder("DELETE * FROM CombinationObjectTable");
foreach(var obj in myCombinationObject)
{
wholeQuery.Append(string.Format("INSERT INTO CombinationObjectTable(Id1, Id2) VALUES('{0}', '{1}')", obj.Id1, obj.Id2);
}
wholeQuery.Append(
db.Tables
.Where( x=>
myCombinationObjectId1s.Contains(x.Prop1)
|| myCombinationObjectId2s.Contains(x.Prop2))
.Where( x=>
combinationObjectTable.Any(ct => ct.Id1 == x.Id1 && ct.Id2 == x.Id2)
).ToString();
);
var filteredResults = context.Tables.ExecuteQuery(wholeQuery.ToString());
Thanks to this your main query stays written in linq. If you do not want to add new table to your db this is as well achievable. Add new class CombinationObjectTable to model, generate new migration to add it and afterwards remove code creating that table from migration code. After that apply migration. This way the db schema won't be changed but EF will think that there is CombinationObjectTable in database. Instead of it you will need to create a temporary table to hold data:
StringBuilder wholeQuery = new StringBuilder("CREATE TABLE #TempCombinationObjectTable(Id1 uniqueidentifies, Id2 uniqueidentifier);");
And when you invoke ToString method on your linq query change CombinationObjectTable to #TempCombinationObjectTable:
...
.ToString()
.Replace("CombinationObjectTable", "#TempCombinationObjectTable")
Other thing worth considering would be using query parameters to pass values in INSERT statements instead of just including them in query yourself - this is of course achievable with EF as well. This solution is not fully ready to apply, rather some hint in which direction you may go for the solution.
Can you do something like this:
var result=
db.Tables
.Where(t=>
db.Table3
.Where(x=>x.Prop3 == myFilter)
.Any(a=>a.T1==t.Prop1 || a.T2==t.Prop2)
).ToList();
If you simply want to avoid the intermediate result (and also creating a second intermediary list) you can do the following
var resultsFiltered = db.Tables.Where( x=>
myCombinationObjectId1s.Contains(x.Prop1)
|| myCombinationObjectId2s.Contains(x.Prop2))
.AsEnumerable() // everything past that is done in memory but isn't materialized immediately, keeping the streamed logic of linq
.Where( x=>
myCombinationObject
.Contains(new {Id1 = x.Prop1, Id2 = x.Prop2 })
.ToList();

Entityframework - Adding Expression to where clause

Let's say I have this method to seach my DB for products that fit a certain keyword:
public List<Product> GetByKeyword(string keyword)
{
using(var db = new DataEntities())
{
var query = db.Products.Where(x => x.Description.Contains(keyword);
return query.ToList();
}
}
This works fine, but somewhere else in my project, I want to get the active products only, still by keyword. I would like to do something like :
...
var result = ProductStore.GetByKeyword("Apple", x => x.isActive == 1);
Therefore, I created this method:
public List<Product> GetByKeyword(string keyword, Func<Product, bool> predicate = null)
{
using(var db = new DataEntities())
{
var query = db.Products.Where(x => x.Description.Contains(keyword);
if(predicate != null)
query = query.Where(x => predicate(x));
return query.ToList();
}
}
While this compiles well, the ToList() call generates a NotSupportedException because LINQ does not support the Invoke method.
Of course, I could to it with another method
i.e. GetActiveByKeyword(string keyword) but then I would have to do one for every possible variation, including the ones I didn't think of...
How do I get this to work? Thanks!
Isn't it just this:
if(predicate != null)
query = query.Where(predicate);
it's just as AD.Net says the reason why it works with Expression before is because if you say that the compiler knows it would be a lambda expression

Is there a neat way of doing a ToList within a LINQ query using query syntax?

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.

Adding a where/order by clause to an IQueryable

I have ths function to query a set of records from the DB:
public IQueryable<PointTransactionViewModel> GetPointTransactions(int UserID)
{
return
(
from PointTransaction p in entities.PointTransaction
join ActivityLog a in entities.ActivityLog
on p.TransactionID equals a.TransactionID
where p.UserID == UserID
select new PointTransactionViewModel
{
ID = p.TransactionID,
Balance = p.Balance,
Points = p.Amount,
RelatedActivityID = a.ID,
When = p.When,
Sender = p.SenderUserInfo.CompleteName
}
);
}
I wish to add an additional cause, like this
var entries = GetPointTransaction(1);
return entries.OrderbyDescending.Where( x => x.When >= start && w.When <= end).
( x => x.When);
However, I seem to need to create a new query from the existing one for this to work. But, I have seem this work before without creating a new query, in the code snippet before:
public PaginatedList(IQueryable<T> source, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
PageSize = pageSize;
TotalCount = source.Count();
TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize);
this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize));
}
Does the code above somehow doesn't need a new query to be created for the IQueryable source object? Was a temporary object created?
Edit
It's strange, but to get it to work I have to do the following:
IQueryable<ActivityLogEntry> log = activityRepo.GetPointTransaction(userID).
Where(x => x.PointsEarned == 50);
return log.ToList();
The following will not work:
var log = = activityRepo.GetPointTransaction(userID);
log.Where( x => x.PointsEarned == 50);
return log.ToList();
There is no error message, just that the where clause seems to be ignored (it is also returning all data which PointsEarned is not 50)
Your entries is of IQueryable type, that's enough and you can add any number of clauses before fetching the data, e.g. before calling the ToList() function.
It doesn't execute the SQL code, just an expression tree will be created until you fetch the whole data with one of the existing methods (again, e.g. the ToList() function).
var query = context.Where(x=>x.id == test);
query = query.Where(anotherCondition1);
query = query.Where(anotherCondition2);
...
var result = query.ToList();
it's equal to
var result = context.Where(x=>x.id == test)
.Where(anotherCondition1)
.Where(anotherCondition2)
....
.ToList()
This is called deferred execution, for more details see the MSDN blog post on LINQ and Deferred Execution.
You do need to create a new object. IQueryable is immutable. Don't worry this is how you are supposed to do it. This is how the queries are formed internally. All the extension methods like "Where" don't actually change the object. They just return a new one.
The code that you claim works should not work. The method doesn't even have a type.
i mean you can write this sample :
opportunites = from opp in oppDC.Opportunities
join org in oppDC.Organizations on opp.OrganizationID equals org.OrgnizationID
select new
{
opp.OpportunityID,
opp.Title,
opp.PostedBy,
opp.Address1,
opp.CreatedDate,
org.OrganizationName
};
if(condition)
{
opportunites = opportunites.Where(opp => opp.Title.StartsWith(title));
}
//------Other Condition you need
if(!String.IsNullOrEmpty(title))
{
opportunites = opportunites.Where(.....);
}
if(!String.IsNullOrEmpty(name))
{
opportunites = opportunites.Where(.....);
}
As others have pointed out, you do not need a new object. Your syntax for OrderByDescending is wrong though, you need to specify the key selector.
var entries = GetPointTransaction(1);
return entries.Where(x => x.When >= start && w.When <= end).OrderbyDescending(x => x.When);

Problem with order by in LINQ

I'm passing from the controller an array generated by the next code:
public ActionResult GetClasses(bool ajax, string kingdom)
{
int _kingdom = _taxon.getKingdom(kingdom);
var query = (from c in vwAnimalsTaxon.All()
orderby c.ClaName
select new { taxRecID = c.ClaRecID, taxName = c.ClaName }).Distinct();
return Json(query, JsonRequestBehavior.AllowGet);
}
The query List should be ordered, but it doesn't work, I get the names of the classes ordered wrong in the array, because I've seen it debugging that the names are not ordered.The view is just a dropdownbox loaded automatically, so I'm almost sure the problem is with the action. Do you see anything wrong?Am I missing something?
I think gmcalab is almost there. The reason it's not working is that Distinct blows away the ordering. So you need Distinct THEN OrderBy. But this means you have to sort by the new attribute name:
var query = (from c in vwAnimalsTaxon.All()
select new { taxRecID = c.ClaRecID, taxName = c.ClaName }
).Distinct().OrderBy(t => t.taxName);
Give this a try:
var query = (from c in vwAnimalsTaxon.All()
select new { taxRecID = c.ClaRecID, taxName = c.ClaName }
).Distinct().OrdeyBy(c => c.ClaName);
In LINQ the Distinct method makes no guarantees about the order of results. In many cases the Distinct causes the OrderBy method to get optimized away. So it's necessary to do the OrderBy last, after the Distinct.
var query = (from c in vwAnimalsTaxon.All()
select new { taxRecID = c.ClaRecID, taxName = c.ClaName })
.Distinct()
.OrderBy(c => c.ClaName);
The select will also blow away the sorting. So either Distinct or Select needs orderby after.

Categories