Optimize query using HQL / Antlr.Runtime.NoViableAltException - c#

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.

Related

Appending to the Select part of a query

We have a duplicate part of our LINQ METHOD syntax query. Here is a contrived example.
IQueryable<orders> query = _context.Set<orders>();
var result = query.Select(p => new{
REMAINING = p.qtyOrdered + p.alreadysent,
AWATING = p.qtyOrdered + p.alreadysent
}).ToList();
So we are trying to resolve the duplicate part by putting something in a method and then calling that and getting some sort of result. So something like this....
private IQueryable WhatsLeft()
{
IQueryable<orders> query = _context.Set<orders>();
return query.Select(p => new{p.qtyOrdered + p.alreadysent});
}
IQueryable<orders> query = _context.Set<orders>();
var result = query.Select(p => new{
REMAINING = WhatsLeft(),
AWATING = WhatsLeft()
}).ToList();
Is this at all possible and if so can anyone give me some brief advise on how I would achieve this.
Wouldn't you just simply pass the Order object to the new function directly?
private int Total(Order order)
{
return order.qtyOrdered + order.alreadySent;
}
IQueryable<orders> query = _context.Set<orders>();
var result = query.Select(p => new{
REMAINING = Total(p),
AWATING = Total(p)
}).ToList();
If I understand what you're after correctly. I can't remember off the top of my head how well Linq to sql etc can handle functions, interpreting them into SQL functions. Maybe you could give it a try.
Alternatively, to reduce the complexity of the function (to facilitate L2S conversion) you can make the parameters granular on the function such as:
private int Total(int left, int right)
{
return left + right;
}
Then make the call more like:
var result = query.Select(p => new{
REMAINING = Total(p.qtyOrdered, p.alreadysent),
AWATING = Total(p.qtyOrdered, p.alreadysent)
}).ToList();
UPDATE:
Have you thought about querying the calculation up front?
var result = query.Select(c => c.qtyOrdered + c.alreadysent).Select(p => new {
REMAINING = p,
AWAITING = p
}).ToList();

How to inner join(filtering for nested properties) with expression tree?

My users should be able to configure a filter to get a result from the database.
I decided to use an expression tree, to be flexible with the query. But I have problems with the expression tree for filtering by ReferenceCode.
Entities:
PumpStartUp
Duration (TimeSpan)
DutDbId (Guid/FK)
TimeStamp (DateTime)
DUT (DeviceUnderTest)
DeviceUnderTest
AdminId (string/unique)
DbId (Guid/PK)
ReferenceCode (string)
StartUps (List)
Here is a part of the filter in equivalent linq. But linq I can't use, because I don't know how many ReferenceCodes will be defined by the user.:
//-------------------reference code filter---------------------------
var query = context.PumpStartUps.Where((p => p.DUT.ReferenceCode == "HKR566" ||
p.DUT.ReferenceCode == "HSH967" ||
.
.
.));
startUps = query.ToList();
For filtering by DeviceUnderTest part of the filter, my solution is:
// --------------------duts filter-----------------------------------
Expression dutsExpression = null;
Expression psuExpression = Expression.Parameter(typeof(PumpStartUp), "psu");
Expression psuDutIdExpression = Expression.Property(psuExpression, "DutDbId");
foreach (var dut in filter.Duts)
{
DeviceUnderTest deviceUnderTest = context.DevicesUnderTest.Where(d => d.AdminId == dut.Id).Single();
Expression dutIdExpression = Expression.Constant(deviceUnderTest.DbId);
Expression dutExpression = Expression.Equal(pumpStartUpDutIdExpression, dutIdExpression);
if (dutsExpression == null)
{
dutsExpression = dutExpression;
}
else
{
dutsExpression = Expression.Or(dutsExpression, dutExpression);
}
}
How can I filter by ReferenceCode in that manner:
Use this:
var dutExpression = Expression.Property(psuExpression, "DUT");
var referenceCodeExp = = Expression.Property(dutExpression, "ReferenceCode ");
var constExpr = Expression.Constant("HKR566");
var eqExp = Expression.Equal(referenceCodeExp , constExpr);
dutsExpression = Expression.Or(dutsExpression, eqExp);
If you have a limited amount of codes, you can always say
var query = context.PumpStartUps.Where(p => codes.Contains(p.DUT.ReferenceCode))
This works up until about 2000 parameters. If you need more, then you should probably send the codes somehow to a temp table (or rather a function returning a table, since ef does not support temp tables), and join on that since constructing an expression with more than 2000 ors is not gonna perform well.

MVC SqlQuery and a 2nd sort

I am re-writing an existing legacy system that uses stored procedures to retrieve the data needed.
The new design will have the normal column sorting and text filtering, but I came across something that has me stumped.
I am able to perform a LINQ query on the retrieved data and get my desired result as follows:
var customerIDParam = new SqlParameter("#customerID", 452);
var result =
db.Database.SqlQuery<InventoryDetail>("map_efs_InventoryDetail #customerID", customerIDParam).ToList();
// this works!
var finalResult1 = from s in result
.Where(s => s.cProductDescription.Contains("E"))
.OrderBy(s => s.cProductDescription)
select s;
return View(finalResult1.ToList());
I would really like to build the LINQ statement dynamically as follows BUT THIS FAILS, always returning the full query
var customerIDParam = new SqlParameter("#customerID", 452);
var result =
db.Database.SqlQuery<InventoryDetail>("map_efs_InventoryDetail #customerID", customerIDParam).ToList();
// This does not work ???
var finalResult2 = from s in result select s;
finalResult2.OrderBy(s => s.cProductDescription);
finalResult2.Where(s => s.cProductDescription.Contains("E"));
return View(finalResult2.ToList());
If anyone can assist I would appreciate it.
Regards
Mark
OrderBy/Where/Etc are "pure" methods, they will return an other IEnumerable, so your result never gets ordered or filtered, you need to assign the new operations (I say operations beacuse IEnumerables have deferred execution), eg:
Assigning variables:
List<Customer> customers = context.Customers.ToList();
IEnumerable<Company> companies = customers.Select(e => e.Company);
IEnumerable<Company> companiesFiltered = companies.Where(e => e.Active);
IOrderedEnumerable<Company> companiesOrdered = companiesFiltered.OrderBy(e => e.Id);
companiesFiltered = companiesOrdered.ThenBy(e => e.Code); // because the variable and result are the same type we can do this
Using returning values:
var finalResult2 = result.Select(r => r.s)
.Where(s => s.cProductDescription.Contains("E"))
.OrderBy(s => s.cProductDescription);
Because every operation returns another IEnumrable we can "chain calls" fluently like that. Remember that actual execution takes place when you call ToList().
I discovered my own error.
var finalResult2 = from s in result select s;
finalResult2 = finalResult2.OrderBy(s => s.cProductDescription);
finalResult2 = finalResult2.Where(s => s.cProductDescription.Contains("E"));
return View(finalResult2.ToList());

Construct LINQ query using variables in asp.net WebAPI

I am trying to build a method in my asp.net WebAPI to grab data based on the arguments passed on the method. The method is used to perform a search on restaurant data. I have a variable called 'type' that determines the type of data search performed. The second variable 'keyword' is the keyword searched by the user. The WHERE condition in my LINQ query depends on the type and needs to be dynamic, so I have used a separate variable outside the LINQ query to define the condition. I have tried assigning this variable to my WHERE statement on the LINQ query but it doesn't seem to work. Can someone help with it please? I have been stuck on this for a few days now
public IQueryable<RestaurantView> GetRestaurantsForSearch(string keyword, int type, string location)
{
//
var condition = "";
if(type == 1)
{
condition = "x.RestaurantName.Contains(keyword)";
} else if(type == 2){
condition = "x.Cuisine.Equals(keyword)";
}
else {
condition = "x.Rating.Equals(keyword)";
}
var query = from x in db.Restaurants
join y in db.Cuisine on x.RestaurantCuisine equals y.CuisineID
where condition
select new RestaurantView
{
RestaurantID = x.RestaurantID,
RestaurantName = x.RestaurantName,
RestaurantCuisine = y.CuisineName,
RestaurantDecription = x.RestaurantDecription
};
return query;
}
Try this:
Predicate<Restaurant> pred;
if (type == 1) pred = x => x.RestaurantName.Contains(keyword);
else if (type == 2) pred = x => x.Cuisine.Equals(keyword);
else pred = x => x.Rating.Equals(keyword);
var query = from x in db.Restaurants
join y in db.Cuisine on x.RestaurantCuisine equals y.CuisineID
where pred(x)
select new RestaurantView
{
RestaurantID = x.RestaurantID,
RestaurantName = x.RestaurantName,
RestaurantCuisine = y.CuisineName,
RestaurantDecription = x.RestaurantDecription
};
return query;
You need to look a dynamic linq library i think then you can execute string statements inside your linq
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
or you can execute direct query
http://msdn.microsoft.com/en-us/library/system.data.linq.datacontext.executequery.aspx
If you are ok with dropping your comprehensive LINQ query in favour of the extension method syntax, it's pretty simple (I'm on a netbook without VS, so I apologize that this is untested but should give you the idea):
var query = db.Restaurants
.Include("Cuisine")
if(type == 1)
{
query= query.Where(x => x.RestaurantName.Contains(keyword));
}
else if(type == 2)
{
query = query.Where(x => x.Cuisine == keyword);
}
else {
query = query.Where(x => x.Rating == keyword);
}
This builds out your expression tree differently based on your logic checks, which will result in a different SQL query being generated based on the value of type.
I notice that in your join, Cuisine appears to be an Entity, but in your logic checks, you attempt to filter by comparing Cuisine to a string so I think there is some disconnect.
var query = from x in db.Restaurants
join y in db.Cuisine on x.RestaurantCuisine equals y.CuisineID
where condition
select new RestaurantView
{
RestaurantID = x.RestaurantID,
RestaurantName = x.RestaurantName,
RestaurantCuisine = y.CuisineName,
RestaurantDecription = x.RestaurantDecription
};
return query;
}
how to get the return query value in client side to assign for grid view binding

Index was out of range exception in query

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;
}

Categories

Resources