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.
Related
I am updating an old EF solution to EF Core and I would like to remove much of the code that is not using generics (T) but I have to do it in stages. The original code is suffering from organic growth of learning entity framework over 10 years and is a mix of "philosophies" and patterns from the code like in the tutorials from Microsoft to Repository pattern.
The ideal solution would be something that would allow me to build up the query for a particular entity then open the connection and run the "ToList()". I can then move parts of the code in stages to something cleaner.
//logic to figure out what is needed with no connection or dbconnection yet
public void GetOrder(){
var query = new List<Order>().AsQueryable();
query = query.Where(x => x.Id > 100);
var orders = repo.GetItems<Order>(query);
}
public IEnumerable<T> GetItems<T>(IQueryable<T> query, int page = 0, int maxcount = 0)
{
using (MyEntities context = new MyEntities(getConnectionString()))
{
context.Attach(query); //This does not seem to work
if (page > 1)
query = query.Skip(page * maxcount);
if (maxcount > 0)
query = query.Take(maxcount);
return query.ToList();
}
}
In the original older code the EF repository was initializing the entities in the code/request constructor then it was calling the above method GetItems. It looked like two different connections were being made to the database and two different Entity initializations were happening to make 1 request to the database.
I am using EF Core 6 and .NET 6 (Core 6).
You use context.Set<T>() to get the DbSet<T> (which is already an IQueryable<T>).
context.Attach attaches entities to the change tracker of the context, which is not what you want.
So you want something like:
public void GetOrders(){
List<Order> orders;
using(MyEntities context = new MyEntities(getConnectionString()))
{
var query = new List<Order>().AsQueryable();
orders = context.GetItems<Order>().Where(x => x.Id > 100).ToList();
}
// Do whatever with "orders", which is already materialized and doesn't need the dbcontext instance anymore
}
public static class DbContextExtensions
{
// On some extension static class
public static IQueryable<T> GetItems<T>(this MyEntities context, int page = 0, int maxcount = 0)
where T: class
{
var query = context.Set<T>().AsQueryable();
if (page > 1)
query = query.Skip(page * maxcount);
if (maxcount > 0)
query = query.Take(maxcount);
return query;
}
}
Note that this is way out of how you should use dbcontext's these days (there's dependency injection, async, etc.), but I'm basing on the code you posted on the question
You can build Func<IQueryable<T>, IQueryable<T>> and use context.Set<T>() method. Something along this lines:
public void GetOrder(){
Func<IQueryable<Order>, IQueryable<Order>> queryProvider =
query => query.Where(x => x.Id > 100);
var orders = repo.GetItems<Order>(query);
}
public IEnumerable<T> GetItems<T>(Func<IQueryable<T>, IQueryable<T>> queryProvider, int page = 0, int maxcount = 0)
{
using (MyEntities context = new MyEntities(getConnectionString()))
{
var query = queryProvider(context.Set<T>());
if (page > 1)
query = query.Skip(page * maxcount);
if (maxcount > 0)
query = query.Take(maxcount);
return query.ToList();
}
}
}
Note that manually creating DbContext's is not an idiomatic approach anymore, usually it is handled by DI.
Also I would recommend just to create an Paginate extension method. Something like this:
public static class QueryableExt
{
public static IQueryable<T> Paginate<T>(this IQueryable<T> query, int page = 0, int maxcount = 0)
{
if (page > 1)
query = query.Skip(page * maxcount);
if (maxcount > 0)
query = query.Take(maxcount);
return query;
}
}
The code's problem isn't generics - by definition, EF Core is generic. DbSet<T> and IQueryable<T> are generic. All operators are generic.
The problem is mixing up two things. Paging over an IQueryable and creating a specific IQueryable implementation. If you split these two into separate parts, the problem becomes very easy.
The paging method can be :
public static IQueryable<T> GetPage<T>(this IQueryable<T> query,int page,int maxcount)
{
if (page > 1)
query = query.Skip(page * maxcount);
if (maxcount > 0)
query = query.Take(maxcount);
return query;
}
This can be used with any IQueryable<T> implementation, whether it's a container implementing IEnumerable<T> like a List<T> or an EF Core query:
var myList=new List<Order>();
...
var query=myList.AsQueryable()
.Where(...)
.GetPage(2,10);
var pageOrders=query.ToList();
Or
var query=context.Orders
.Where(...)
.GetPage(2,10);
var pageOrders=query.ToList();
There's no reason to create a GetItems method, as all it does is create a specific DbContext and use a specific DbSet. There's no need to hide context.Orders or the code that creates context. In fact, in environments with dependency injection like ASP.NET Core, that DbContext will be injected by the DI container
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 am making rest api in ASP.NET CORE using MySQL db. In one of my repositories i am getting, filtering, paging and sorting data. Although i get a WARN message: __
queryObj__Categorie.. uses a row limitating operation (Skip/Take) which may lead to unpredictable results. So my question is, what kind of unpredictable results may i get with the following code???
public async Task<QueryResult<User>> GetAll(UserQuery queryObj)
var query = context.users
.Include(users=> users.Category)
.Include(users=> users.Tags)
.Include(users=> users.Localization)
.AsQueryable();
if (queryObj.Categories.Length > 0)
query = query.Where(v => queryObj.Categories.Contains(v.Category.Name));
if (queryObj.Localizations.Length > 0)
query = query.Where(v => queryObj.Localizations.Contains(v.Localization.Id));
int usersCount = query.Count();
//only orders when there is specific value set in queryObj
query = query.ApplyOrdering(queryObj, COLUMNS_MAP);
query = query.ApplyPaging(queryObj);
var users = await query.ToListAsync();
var queryResult = new QueryResult<Users>();
queryResult.items = users;
queryResult.itemsCount = usersCount ;
return queryResult;
}
My paging extension method:
public static IQueryable<T> ApplyPaging<T>(this IQueryable<T> query, IQueryObject queryObj)
{
return query.Skip((queryObj.Page - 1) * queryObj.PageSize).Take(queryObj.PageSize);
}
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.
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.