I am in big need of help.
I am making dynamic query using Criteria:
ICriteria query = session.CreateCriteria(typeof(Employee));
if (searchOptions.FirstName != null)
{
query.Add(Expression.Eq("FirstName", searchOptions.FirstName));
}
if (!searchOptions.LastName != null)
{
query.Add(Expression.Eq("LastName", searchOptions.LastName));
}
if (searchOptions.PhoneNumber != null)
{
query.CreateCriteria("PhoneNumbers")
.Add(Expression.Like("Number", searchOptions.PhoneNumber + "%"));
}
After this I need to have both Total Row Count and Pagination.
For pagination:
query.SetFirstResult(0).SetMaxResults(8);
for rowcount:
query.SetProjection(Projections.RowCountInt64());
How can I execute both in a single query either by using a MultiCriteria or something else.
Please help!
You can see my answer in nhibernate 2.0 Efficient Data Paging DataList Control and ObjectDataSource .
Code again:
protected IList<T> GetByCriteria(
ICriteria criteria,
int pageIndex,
int pageSize,
out long totalCount)
{
ICriteria recordsCriteria = CriteriaTransformer.Clone(criteria);
// Paging.
recordsCriteria.SetFirstResult(pageIndex * pageSize);
recordsCriteria.SetMaxResults(pageSize);
// Count criteria.
ICriteria countCriteria = CriteriaTransformer.TransformToRowCount(criteria);
// Perform multi criteria to get both results and count in one trip to the database.
IMultiCriteria multiCriteria = Session.CreateMultiCriteria();
multiCriteria.Add(recordsCriteria);
multiCriteria.Add(countCriteria);
IList multiResult = multiCriteria.List();
IList untypedRecords = multiResult[0] as IList;
IList<T> records = new List<T>();
if (untypedRecords != null)
{
foreach (T obj in untypedRecords)
{
records.Add(obj);
}
}
else
{
records = new List<T>();
}
totalCount = Convert.ToInt64(((IList)multiResult[1])[0]);
return records;
}
It clones your original criteria twice: one criteria that return the records for the page and one criteria for total record count. It also uses IMultiCriteria to perform both database calls in one roundtrip.
Related
I have a DB used for a production line. It has an Orders table, and Ordertracker table, an Item table, and an Itemtracker table.
Both Orders and Items have many-to-many relationships with status. The tracker tables resolves these relationships in such a way that an item can have multiple entries in the tracker - each with a particular status.
I tried to upload a picture of the tables to make things clearer but alas, I don't have enough points yet :C
I need to find items whose last status in the Itemtracker table meets a condition, either '3' or '0'.
I then need to get the first one of these items.
The steps I am using to accomplish this are as follows:
Get all the Orders which have a certain status.
Get all the Items in that Order.
Get all the Items whose last status was = 0 or 3.
Get the first of these items.
My code is as follows:
public ITEM GetFirstItemFailedOrNotInProductionFromCurrentOrder()
{
var firstOrder = GetFirstOrderInProductionAndNotCompleted();
var items = ERPContext.ITEM.Where(i => i.OrderID == firstOrder.OrderID) as IQueryable<ITEM>;
if (CheckStatusOfItems(items) != null)
{
var nextItem = CheckStatusOfItems(items);
return nextItem ;
}
else
{
return null;
}
}
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
List<ITEM> listOfItemsToProduce = new List<ITEM>();
foreach (ITEM item in items.ToList())
{
var lastStatusOfItem = ERPContext.ITEMTRACKER.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID).FirstOrDefault();
if (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
{
listOfItemsToProduce.Add(item);
}
}
return listOfItemsToProduce.FirstOrDefault();
}
Now, this all works fine and returns what I need but I'm aware that this might not be the best approach. As it is now my IQueryable collection of items will never contain more than 6 items - but if it could grow larger, then calling ToList() on the IQueryable and iterating over the results in-memory would probably not be a good idea.
Is there a better way to iterate through the IQueryable items to fetch out the items that have a certain status as their latest status without calling ToList() and foreaching through the results?
Any advice would be much appreciated.
Using LINQ query syntax, you can build declaratively a single query pretty much the same way you wrote the imperative iteration. foreach translates to from, var to let and if to where:
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
let lastStatusOfItem = ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.FirstOrDefault()
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}
or alternatively using from instead of let and Take(1) instead of FirstOrDefault():
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
from lastStatusOfItem in ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.Take(1)
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}
We are using ICriteria and now we would like to switch to more readable QueryOver in Nhibernate
Can someone give me a hint how to convert this generic pagination logic for Icriteria to QueryOver?
public static PagedList<T> PagedList<T>(this ICriteria criteria,
ISession session, int pageIndex, int pageSize) where T : class
{
if (pageIndex < 0)
pageIndex = 0;
var countCrit = (ICriteria)criteria.Clone();
countCrit.ClearOrders(); // so we don’t have missing group by exceptions
var results = session.CreateMultiCriteria()
.Add<long>(countCrit.SetProjection(Projections.RowCountInt64()))
.Add<T>(criteria.SetFirstResult(pageIndex * pageSize).SetMaxResults(pageSize))
.List();
var totalCount = ((IList<long>)results[0])[0];
return new PagedList<T>((IList<T>)results[1], totalCount, pageIndex, pageSize);
}
The way I am using it:
var session = ... // get a ISession
// the QueryOver
var query = session.QueryOver<MyEntity>();
// apply all filtering, sorting...
query...
// GET A ROW COUNT query (ICriteria)
var rowCount = CriteriaTransformer.TransformToRowCount(query.UnderlyingCriteria);
// ask for a list, but with a Future, to combine both in one SQL statement
var list = query
.Future<MyEntity>()
.ToList();
// execute the main and count query at once
var count = rowCount
.FutureValue<int>()
.Value;
// list is now in memory, ready to be used
var list = futureList
.ToList();
So, we are using QueryOver, and profiting from underlying criteria and transformer. With a Future, we also execute that all at one command.
I have a datatable with the following information:
365.00
370.00
369.59
365.00
365.00 -> match with previous item
365.00 -> match with previous item
I only need to remove the next matched items, like this:
365.00
370.00
369.59
365.00
I tried:
(from articlespricehistory in dt.AsEnumerable()
select new
{
articlepricehistory_cost = articlespricehistory.Field<Double>("articlepricehistory_cost")
})
.DistinctBy(i => i.articlepricehistory_cost)
.ToList();
Result:
365.00
370.00
369.59
Any ideas?
Another approach:
public static IEnumerable<T> MyDistinct<T>(this IEnumerable<T> items)
{
T previous = default(T);
bool first = true;
foreach(T item in items)
{
if (first || !Equals(previous, item))
{
first = false;
previous = item;
yield return item;
}
}
}
Or, as requested, with a selector
public static IEnumerable<T> MyDistinct<T, U>(this IEnumerable<T> items, Func<T, U> selector)
{
U previous = default(U);
bool first = true;
foreach(T item in items)
{
U current = selector(item);
if (first || !Equals(previous, current))
{
first = false;
previous = current;
yield return item;
}
}
}
Here's a neat LINQ solution for u
var list = (dt as Enumerable);
var numbers = list.TakeWhile((currentItem, index) => currentItem != list.ElementAtOrDefault(index - 1));
Keep in mind if u have 0 as the first element it will be ommitted from the new list since ElementAtOrDefault will return 0 in the first iteration of the while loop (index of -1), thus evaluating the expression to false. A simple if statement can help you avoid this.
Here's an idea I have not actually tried
Do a Skip(1) on the query to produce a second query.
Now append to the second query any element not equal to the last element in the first query, to produce a third query.
Now zip join the first and third queries together to form a set of pairs; this is the fourth query.
Now construct a fifth query that filters out pairs that have identical elements from the fourth query.
Finally, construct a sixth query that selects the first element of each pair from the fifth query.
The sixth query is the data set you want.
The problem in your query is that you are using .DistinctBy() which will return distinct results only. So if 365.00 appeared anywhere, it won't show up in the returned list again.
var differentPreviousList = new List<double>();
var itemPriceList = dt.ToList();
differentPreviousList.Add(itemPriceList[0]);
for (var index = 1; index < itemPriceList.Count; index++)
{
if (itemPriceList[index - 1] == itemPriceList[index]) continue;
differentPriceList.Add(itemPriceList[index]);
}
It may not be an elegant solution but I would just parse a bare query..here is how.
Run a lambda query to get all the original results without trying to filter out DistinctBy.
Create an object of a single query result of the type you initially queried for.
Initialize a new list for foreach parse results.
Do a for each loop for each result.
The first if section should be if(object above loop is null).
IF is true add item to list
ELSE if check to see if value of item is same as the last iteration.
Store foreach iteration object to the object declared before the loop.
Rinse and repeat, and the result is no duplicate objects found in a row in the loop will be stored in the list resulting in what you wanted.
I think you have to use a temporary value to check if the next value matches the current value or not.
double temporary = -1; // temp value for checking
List<double> results = new List<double>(); // list to store results
(from articlespricehistory in dt.AsEnumerable()
select new
{
articlepricehistory_cost = articlespricehistory.Field<Double>("articlepricehistory_cost")
})
.Select(a => a.articlepricehistory_cost)
.ToList()
.ForEach(cost =>
{
if (temporary != cost) { results.Add(cost); }
temporary = cost;
});
foreach (var result in results)
{
Console.WriteLine(result);
}
After ForEach method is equivalent to the following.
foreach (var cost in costs)
{
if (temporary != cost)
{
results.Add(cost);
Console.WriteLine(cost);
}
temporary = cost;
}
What I have is a list of entities coming back from a database that I want to pivot so that I end up with a new list of entities with the duplicates removed and the pivoted items attached to the new entity.
Currently I have a simple solution like this:
IQueryable<Entity> results // being passed in from calling method.
List<Entity> pivotedEntities = new List<Entity>();
foreach (Entity entity in results)
{
if (pivotedEntities.Contains(entity))
{
Entity matchedEntity = pivotedEntities.Find(e => e.Id == entity.Id);
matchedEntity.RelatedEntities.Add(entity.RelatedEntity);
}
else
{
pivotedEntities.Add(new Entity());
}
}
return pivotedEntities.AsQueryable();
This works fine however I want to be able to achieve the same thing with a LINQ query on the IQueryable results variable so that it maintains its deffered execution rather than executing as soon as I enter the foreach.
I have other methods that get called around this code that also alter the IQueryable and I then want to execute the call to the database once all filters have been applied.
Hope that all makes sense.
Maybe this will reduce few loops.
List<Entity> pivotedEntities = new List<Entity>();
int index = 0;
foreach (Entity entity in results)
{
index = pivotedEntities.IndexOf(e => e.Id == entity.Id);
if (index>-1)
{
pivotedEntities[index].RelatedEntities.Add(entity.RelatedEntity);
}
else
{
pivotedEntities.Add(new Entity());
}
}
return pivotedEntities.AsQueryable();
I have a question about IQueriable. Maybe my understanding is wrong but I thought calling ToList() would force the query to execute. I have a PaginatedList class that I want to populate using an IQuerriable, but when I do it I get a series of null objects. Can anyone tell me what I'm doing wrong.
public class PaginatedList<T> : List<T>
{
public PaginatedList(IQueryable<T> source, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
PageSize = pageSize;
TotalCount = source.Count();
TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize);
List<T> temp = source.Skip(PageIndex * PageSize).Take(PageSize).ToList();
this.AddRange(temp);
}
}
My temp List is being filled with default values. I don't understand. ANy help would be appreciated.
The list is populated as follows:
public IQueryable<Transaction> GetTransactions()
{
return from trx in pingDataContext.Table0
join pdo in pingDataContext.Table1
on trx.AssociatedID equals pdo.ID
where trx.Type == 1 &&
pdo.PortalID == 330
select new Transaction(trx.ID, pdo.CreateDate, pdo.Msisdn, pdo.Remarks, 0.1,
(double)pdo.Price, pdo.DnldLink, pdo.UserIP);
}
I am creating my paginated list here:
var transactions = transactionRepository.GetTransactions();
var paginatedTransactions = new PaginatedList<Transaction>(transactions,
(int)page,
pageSize);
Thanks
You say your list is filled with default values. If you mean default(T), than this is the problem:
default(T) returns null it T is a reference type.
Your usage of ToList() has nothing to do with this. It isn't even necessary in your code. The following would be equivalent:
var temp = source.Skip(PageIndex * PageSize).Take(PageSize);
this.AddRange(temp);
The reason is that AddRange will enumerate the passed enumerable anyway.