I have the following code, it stops executing with "StackOverFlow unhandled error".
var Requests= GetList();// return List of Request objects
IQueryable<Order> pos= Enumerable.Empty<Order>().AsQueryable();
if (Requests != null)
{
if (Requests.Count > 0)
{
var GeneralReq = Requests.Select(loc => loc.Id).ToList();
pos = db.Order.Where(loc => loc.Deleted == false && GeneralReq.Any(a => a == loc.Id));
//HERE, stop executing with StackOverFlow Error.
}
}
the problem exactly in here:
pos = db.Order.Where(loc => loc.Deleted == false && GeneralReq.Any(a => a == loc.Id));
Without knowing what GetList() does the likely problem is that it is returning far too many records to safely inject IDs into your .Any() expression. EF will want to turn that .Any() into a query like:
WHERE orders.OrderId IN (22, 25, 45, 46, 52, 66, ...)
This generally isn't efficient and there are limits to the # of IDs you can pass in. It's better to resolve these criteria as a join. I'm not certain if this can result in a stack overflow so I don't believe the code sample you have is complete because without a .ToList() or similar expression, the IQueryable<Order> pos expression would not have materialized yet.
I would look at the relationships that you can resolve between Requests and Orders. If the Request entity as a reference to an Order, then you can alter GetList() to return an IQueryable<Request> then to get the orders:
IQueryable<Request> requests = GetList();
IQueryable<Order> orders = requests.Select(x => x.Order);
From there you can .Select() the details of the orders that you want, and materialize the resulting data.
IQueryable<Request> requests = GetList();
List<OrderViewModel> orderVMs = requests.Select(x => new OrderViewModel
{
OrderId = x.Order.OrderId,
RequestId = x.RequestId,
CustomerName = x.Customer.Name,
OrderNumber = x.Order.OrderNumber,
// ...
}).ToList();
If GetList() can return a significant # or results then use .Take() and .Skip() rather than .ToList() to paginate your results.
Related
I am trying to remove duplicate code throughout my project and I am at a standstill trying to figure this out. What I am trying to do is create a base linq query that will be reused to add things like Where, Take...etc in multiple different methods.
public IQueryable<Object> FooLinq(int id)
{
using (var ctx = new dbEntities())
{
var results =
(from account in ctx.account
join memberProducts in ctx.tblMemberProducts on account.Id equals memberProducts.AccountId
orderby account.date descending
select new{account,memberProducts}).ToList();
return results;
}
}
So that would be by base query above and I would have a seperate method that would reuse VioLinq but this time would use a where clause in it.
public List<IncomingViolations> Foo1(int id)
{
//Linq query FooLinq() where Name == "Bob"
}
You'll need to do two things:
Return the query prior to materializing it.
Make sure the context is still in scope when the final query is materialized.
These two requirements will play off each other somewhat, and there are a number of approaches you can take to meet them.
For example, you could make your method take the context as a parameter, forcing the caller to provide it and manage its lifecycle.
public IQueryable<AccountInfo> FooLinq(DbEntities ctx, int id)
{
return
from account in ctx.account
orderby account.date descending
select new AccountInfo()
{
Name = account.Name,
Mid = account.MemberID,
Date = account.Date,
Address = account.Address,
};
}
public List<IncomingViolations> Foo1(int id)
{
using(var ctx = new dbEntities())
{
//Linq query FooLinq() where Name == "Bob"
return FooLinq(ctx).Where(v => v.Name == "Bob").ToList();
}
}
You could alternatively inject the context as a constructor-injected dependency, and use a DI framework to manage the context's lifecycle.
You can do it as Queryable then add conditions to it.
For example:
public List<account> GetAccountsByName(string name, bool usePaging, int offset = 0, int take = 0) {
var query = GetMyQuery();
query = query.Where(x => x.Name == name);
query = query.OrderBy(x => x.Name);
if(usePaging) {
query = query.Take(take).Skip(offset);
}
query = PrepareSelectForAccount(query);
return query.ToList(); .
}
public IQueryable<account> GetMyQuery(){
return ctx.account.AsQueryable();
}
public IQueryable<account> PrepareSelectForAccount(IQueryAble<account> query){
return query.Select(select new AccountInfo()
{
Name = account.Name,
Mid = account.MemberID,
Date = account.Date,
Address = account.Address,
}
);
}
Sure, but don't call .ToList(), and return IQueryable<T> instead of List<T>. LINQ is based on the concept of deferred execution which means the query is not actually performed until the enumerable is iterated over. Until then, all you have done is built an object which knows how to do the query when the time comes.
By returning an IQueryable<T> from a function that sets up the "basic query," you are then free to tack on additional LINQ methods (such as .Where() or .Take()) to produce a modified query. At this point you are still simply setting up the query; it is actually performed only when you iterate over the enumerable, or call something like .ToList() which does that for you.
I have a Linq statement which has two optional Where clauses but I cannot work out how to implement this linq (at the moment I have 4 linq statements in this method which off course is not good!):
//Params passed into my method:
int StoreId = 0;
bool ShowEnabledTills = true;
var query = (from Transactions in db.Transactions
join Tills in db.Tills on new { TillId = Convert.ToInt32(Transactions.TillId) } equals new { TillId = Tills.TillId }
join CompanyAddresses in db.CompanyAddresses on new { CompanyId = Convert.ToInt32(Transactions.StoreID) } equals new { CompanyId = CompanyAddresses.CompanyId }
where
CompanyAddresses.CompanyId == StoreId <===== OPTIONAL
&& Tills.Active == ShowEnabledTills <===== OPTIONAL
select new
{
Tills.TillId,
Tills.ComputerName,
Tills.Description,
CompanyAddresses.CompDescription
}).Distinct();
I took a look at PredicateBuilder but I couldn't quite get my head around this but if I can create some re-usable code this would be great!
// get these from params to decide optional or not
var validateStoredId = false;
var validateActiveStatus = false;
And in your where clause do something like:
where
(!validateStoreId || CompanyAddresses.CompanyId == StoreId)
&& (!validateActiveStatus || Tills.Active == ShowEnabledTills)
I have a little to contribute here I think. I came across the same problem, and implemented a similar solution. HOWEVER! This wont short-circuit as you would hope (or at least it didn't in mine!)
This led to me looping through a list of 10k items checking if true == false effectively, which whilst not an expensive check is a check you don't want to do thousands of times nevertheless.
For anyone else coming to this, I'd recommend splitting your queries down into subqueries wrapped in if checks, that is far more performant when dealing with large datasets :)
Edit : I built this little helper for doing exactly this, hope someone else finds it useful, I enjoyed not having to break my method chains :)
public static IEnumerable<T> OptionalWhere<T>(this IEnumerable<T> source, Func<T, bool> predicate, bool excecuteFilter)
{
if (!excecuteFilter)
{
return source;
}
else
{
return source.Where(predicate);
}
}
Try this:
var query = db.Transactions.Join(
db.Tills, //Join to this table
transaction => transaction.TillId //Outer key
, till => till.TillId, //Inner key
(transaction, till) => new {transaction, till}) //Result of join
.Join(db.CompanyAddresses, //Join to this table
r => r.till.StoreId, //Outer key
a => a.CompanyId, //Inner key
(r, address) => new {r.transaction, r.till, address}); //Result of join
if(StoreId != null)
query = query.Where(d => d.address.StoreId == StoreId);
if(ShowEnabledTills)
query = query.Where(d => d.till.Active == true);
var items = query.Select(d => new {
d.Till.TillId,
d.Till.ComputerName,
d.Till.Description,
d.address.CompDescription
}).Distinct();
I did it without knowing much about your schema, but this should give you a good idea.
I disliked the "SQL style" syntax for this very reason (confusing at times). I started using method calls like this and never had a problem since.
I have code:
if (request.OrderBy != "PricesCount")
query = query.ApplyOrder(request);
else
{
if (request.OrderDirection == "ASC")
{
query = query.ToList().OrderBy(p => p.Prices.Count).AsQueryable(); //must be optimize!
}
else
query = query.ToList().OrderByDescending(p => p.Prices.Count).AsQueryable(); //must be optimize!
}
query = query.Page(pageNumber, pageSize);
var result = query.ToList();
query has type NHibernate.Linq.NhQueryable<Book>
I must remove the ToList() which causes loading all Books from DB.
If I try to use some code:
query = query.OrderBy(p => p.Prices.Count);
...
var result = query.ToList();//I have Antlr.Runtime.NoViableAltException
Exception of type 'Antlr.Runtime.NoViableAltException' was thrown. [.Take[Book](.Skip[Book](.OrderBy[Book,System.Int32](NHibernate.Linq.NhQueryable`1[Book], Quote((p, ) => (p.Prices.Count)), ), p1, ), p2, )]
result = query.Where(p=>p.Price > 5).ToList(); //put whatever filter you want
You don't need to do .ToList().AsQueryable().ToList() like you are in your first segment.
If you can't filter results, then you should implement some sort of paging:
result = query.Skip(x).Take(y).ToList(); //You will need to send in X and Y based on what page you are on and how many items per page you use
result = query.Where(p=>p.Price>5).OrderBy(p=>p.Prices.Count).ToList()
Like I said in comments, if you don't provide a where clause, or if you don't do .Take(y) then this will return all items in the database. You will pass in X and Y yourself if you do .Skip(x).Take(y), you need to determine what is appropriate paging for your application.
each Route contains Locations in specific order.
For example:
NY -> LA is different from LA -> NY.
I would like to write a method that gets locations array and return true or false whether route with the same locations and order exists.
I need to do it using linq to entities and entity framework (Route and Location are entities).
Here is what I wrote:
public bool IsRouteExists(IList<LocationInRoute> locationsInRoute)
{
Route route = null;
if (locationsInRoute.Count > 0)
{
var query = GetRoutesQuery().
Where(x => x.Locations.Count() == locationsInRoute.Count);
for (int i = 0; i < locationsInRoute.Count; i++)
{
long locationId = locationsInRoute[i].LocationId;
query = query.Where(x =>
x.Locations.ElementAt(i).LocationId == locationId); //THROWS EXCEPTION
}
route = query.SingleOrDefault();
}
return route!=null;
}
I get the following exception in the marked line:
Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
What is the reason for this exception?
EDIT
The exception accurs when executing route = query.SingleOrDefault(); and the exception complains about Where(x => x.Locations.ElementAt(i).LocationId == locationId);.
I believe this query is completely wrong. First of all it is not linq-to-entities query and it will never be because linq to entities is not able to work with indexes. I think comparing ordered sequences will have to be executed in memory = linq-to-objects.
Another problem is this:
for (int i = 0; i < locationsInRoute.Count; i++)
{
long locationId = locationsInRoute[i].LocationId;
query = query.Where(x => x.Locations.ElementAt(i).LocationId == locationId);
}
route = query.SingleOrDefault();
I think this is known gotcha when using Linq, query built in loop and deferred execution - I believe this always compares locationId with the last element.
In my opinion the most efficient way to do this is stored procedure with table valued parameter passing your expected sequence and using SQL cursor to compare sequences in the stored procedure.
Looks like perhaps your x.Locations.Count() might be less than your locationsInRoute.Count. are you sure it's not? I'm saying that b/c you're calling x.Locations.ElementAt(i), which will throw if i > Count().
As a sidenote, a better solution to what you're doing is to override equality or implement an IComparer on your class that you want unique and then you can use things like Any() and Contains() for your test.
If you getting an index out of range exception it must mean that number of elements in the locationsRoute collection exceeds the number of elements in IQueryable. If you attempting to test that each location in the provided list is contained in route you should be able to do something like:
var locationIds = locationsInRoute.Select(l => l.LocationId);
query = query.Where(r => r.Locations.All(l => locationIds.Contains(l.LocationId)))
I'm guessing it has to do with your use of ElementAt, which can't be translated to SQL (see the Operators with No Translation section), to operate on your IQueryable. This materializes the IQueryable's result set on the first iteration and so subsequent iterations Route items will be unable to access their related Locations sets. This should probably only happen on the second iteration, but the myriad implications of the deferred execution nature of LINQ is not entirely clear to me in ever case ;-) HTH
You could put a breakpoint at SingleOrDefault and inspect the SQL statement it is executing there, to see why there is no record returned for the SingleOrDefault to find. Though the SQL may be pretty ugly depending on how many routes you have.
Thanks to #Ladislav Mrnka's advice, here is the solution:
public class LocationSequenceEqual : IEqualityComparer<Location>
{
public bool Equals(Location x, Location y)
{
return x.Id == y.Id;
}
public int GetHashCode(Location obj)
{
return obj.Id.GetHashCode();
}
}
public bool IsRouteExists(IList<LocationInRoute> locationsInRoute)
{
Route route = null;
if (locationsInRoute.Count > 0)
{
var query = GetRoutesQuery().
Where(x => x.Locations.Count() == locationsInRoute.Count);
query = query.Where(x => x.Locations.OrderBy(l => l.Order).
Select(l => l.Location).SequenceEqual(locations, new LocationSequenceEqual()));
route = query.FirstOrDefault();
}
return route!=null;
}
If Location has Order as you indicate above, this can be done entirely in (Linq to) SQL:
public bool IsRouteExists(IList<LocationInRoute> locationsInRoute)
{
Route route = null;
if (locationsInRoute.Count == 0)
return;
var possibleRoutes = GetRoutesQuery().
Where(x => x.Locations.Count() == locationsInRoute.Count);
var db = GetDataContext(); //get a ref to the DataContext or pass it in to this function
for (var i = 0; i < locationsInRoute.Length; i++)
{
var lcoationInRoute = locationsInRoute[i];
possibleRoutes = possibleRoutes.Where(x => x.Locations.Any(l => l.Id == locationInRoute.Id && l.Order == locationInRoute.Order));
}
route = possibleRoutes.FirstOrDefault();
return route!=null;
}
The following LINQ statement:
public override List<Item> SearchListWithSearchPhrase(string searchPhrase)
{
List<string> searchTerms = StringHelpers.GetSearchTerms(searchPhrase);
using (var db = Datasource.GetContext())
{
return (from t in db.Tasks
where searchTerms.All(term =>
t.Title.ToUpper().Contains(term.ToUpper()) &&
t.Description.ToUpper().Contains(term.ToUpper()))
select t).Cast<Item>().ToList();
}
}
gives me this error:
System.NotSupportedException: Local
sequence cannot be used in LINQ to SQL
implementation of query operators
except the Contains() operator.
Looking around it seems my only option is to get all my items first into a generic List, then do a LINQ query on that.
Or is there a clever way to rephrase the above LINQ-to-SQL statement to avoid the error?
ANSWER:
Thanks Randy, your idea helped me to build the following solution. It is not elegant but it solves the problem and since this will be code generated, I can handle up to e.g. 20 search terms without any extra work:
public override List<Item> SearchListWithSearchPhrase(string searchPhrase)
{
List<string> searchTerms = StringHelpers.GetSearchTerms(searchPhrase);
using (var db = Datasource.GetContext())
{
switch (searchTerms.Count())
{
case 1:
return (db.Tasks
.Where(t =>
t.Title.Contains(searchTerms[0])
|| t.Description.Contains(searchTerms[0])
)
.Select(t => t)).Cast<Item>().ToList();
case 2:
return (db.Tasks
.Where(t =>
(t.Title.Contains(searchTerms[0])
|| t.Description.Contains(searchTerms[0]))
&&
(t.Title.Contains(searchTerms[1])
|| t.Description.Contains(searchTerms[1]))
)
.Select(t => t)).Cast<Item>().ToList();
case 3:
return (db.Tasks
.Where(t =>
(t.Title.Contains(searchTerms[0])
|| t.Description.Contains(searchTerms[0]))
&&
(t.Title.Contains(searchTerms[1])
|| t.Description.Contains(searchTerms[1]))
&&
(t.Title.Contains(searchTerms[2])
|| t.Description.Contains(searchTerms[2]))
)
.Select(t => t)).Cast<Item>().ToList();
default:
return null;
}
}
}
Ed, I've run into a similiar situation. The code is below. The important line of code is where I set the memberList variable. See if this fits your situation. Sorry if the formatting didn't come out to well.
Randy
// Get all the members that have an ActiveDirectorySecurityId matching one in the list.
IEnumerable<Member> members = database.Members
.Where(member => activeDirectoryIds.Contains(member.ActiveDirectorySecurityId))
.Select(member => member);
// This is necessary to avoid getting a "Queries with local collections are not supported"
//error in the next query.
memberList = members.ToList<Member>();
// Now get all the roles associated with the members retrieved in the first step.
IEnumerable<Role> roles = from i in database.MemberRoles
where memberList.Contains(i.Member)
select i.Role;
Since you cannot join local sequence with linq table, the only way to translate the above query into SQL woluld be to create WHERE clause with as many LIKE conditions as there are elements in searchTerms list (concatenated with AND operators). Apparently linq doesn't do that automatically and throws an expception instead.
But it can be done manually by iterating through the sequence:
public override List<Item> SearchListWithSearchPhrase(string searchPhrase)
{
List<string> searchTerms = StringHelpers.GetSearchTerms(searchPhrase);
using (var db = Datasource.GetContext())
{
IQueryable<Task> taskQuery = db.Tasks.AsQueryable();
foreach(var term in searchTerms)
{
taskQuery = taskQuery.Where(t=>t.Title.ToUpper().Contains(term.ToUpper()) && t.Description.ToUpper().Contains(term.ToUpper()))
}
return taskQuery.ToList();
}
}
Mind that the query is still executed by DBMS as a SQL statement. The only drawback is that searchTerms list shouldn't be to long - otherwise the produced SQL statement won'tbe efficient.