Appending to the Select part of a query - c#

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();

Related

Concatenate string in Linq query

I have a db call that returns me an object. I use linq to cast that object how I want it
var result = queryResult.OrderBy(c => c.TariffName)
.Take(count)
.Select(c => new
{
Text = c.TariffName,
Key = c.TariffId,
Price = c.LineRental
});
var list = result.ToList();
I now want to add the line rental to the tariff name show that it shows like:
myTariff - 12.99
when I try and do this though I can make this change ok:
Text = c.TariffName + " - ",
but when I try and add the line rental I get problems that linq won't recognise the ToString(). I need it to look like:
Text = c.TariffName + " - " + c.LineRental.ToString(),
I understand that linq won't recognise the ToString() method from reading LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression but how do I change this given I can't set it as a string prior to the linq query?
Convert the query result to a list first then use select to make the toString work.
var result = queryResult.OrderBy(c => c.TariffName)
.Take(count);
var list = result.ToList().Select(c => new
{
Text = c.TariffName + " - " + c.LineRental.ToString(),
Key = c.TariffId,
Price = c.LineRental
});
What is happening linq trying to execute your select statement on the database query level, and it does not know how to transform your .Select lambda to select part of sql statement.
Easiest thing you can do is first query required fields, call .ToList() on query to execute it and perform projection after that.
var result = queryResult.OrderBy(c => c.TariffName)
.Take(count)
.Select(c => new
{
Text = c.TariffName,
Key = c.TariffId,
Price = c.LineRental
});
var list = result.ToList().Select(c=>new {
Text = string.Format("{0} - {1}", c.Text, c.Price),
Key=Key,
Price=Price
});

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());

Optimize query using HQL / Antlr.Runtime.NoViableAltException

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.

Creating Extension method to include subquery

I am having trouble creating an extension method for an IQueryable that will include the translation for a specified column in a Linq Query.
Suppose i have the query below.
I would like to call a method IncludeTranslation on the CFG_Article IQueryable specifying the column i want to get the translation for.
Could someone help me in the right direction.
var translations =
from t in UoW.CFG_TRANSLATION.GetAll()
select t;
var result = (
from a in UoW.CFG_ARTICLE.GetAll()
select new
{
a,
translation = translations
.Where(t=> t.TR_TEXT == a.AR_NAME).FirstOrDefault()
});
All i have come up so far is the code below but this does not compile.
public static IQueryable IncludeTranslation<T>(
this IQueryable<T> query,
Expression<Func<t, bool>> fieldToTranslate)
{
// this will get an IQueryable of CFG_TRANSLATION
var translations = GetTranslations();
var result = (
from q in query
select new
{
q,
translation = translations
.Where(t=> t.TR_TEXT == fieldToTranslate)
.FirstOrDefault()
});
// even better is to return all fields from query
// + the TR_TRANSLATION field from the translations table
return result;
}
Try this (I'm having to guess class names are CFG_ARTICLE & CFG_TRANSLATION - replace as required)
public static IQueryable IncludeTranslation(
this IQueryable<CGF_ARTICLE> query,
Func<CFG_ARTICLE, CFG_TRANSLATION, bool> fieldToTranslate)
{
var translations = GetTranslations();
var result =
from a in query
select new
{
a,
translation = translations
.Where(t => fieldToTranslate(a, t))
.FirstOrDefault()
};
return result;
}
calling like this
var result = query.IncludeTranslation(
(article, translation) => article.TR_TEXT == translation.AR_NAME);
I found an other way of returning the same result using a Generic Way.
public static IQueryable IncludeTranslation<S>(this IQueryable<S> source, Expression<Func<S, string>> keyField)
where S : class
{
IQueryable<CFG_TRANSLATION> translations = GetTranslations();
var trans = source.GroupJoin(translations, keyField, t => t.TR_TEXT, (s, t) => new { Source = s, Translations = t });
var result = trans.Select(t => new {
Source = t.Source,
Translation = t.Translations
.FirstOrDefault()
});
return result;
}
Maybe someone can use this as a sollution
this can be called as follow
var Result = QueryableTable.IncludeTranslation(t => t.FieldToTranslate);

NHibernate get first x distinct results does not get me x results?

I have an ICriteria query like so:
var contentCriteria = DetachedCriteria.For<InvoiceItem>();
var countCriteria = DetachedCriteria.For<InvoiceItem>();
if (model.CurrentPage <= 0) model.CurrentPage = 1;
if (model.OnlyShowErrors)
{
contentCriteria.Add(Restrictions.Not(Restrictions.Eq("TroubleClass", TroubleClasses.Success)));
countCriteria.Add(Restrictions.Not(Restrictions.Eq("TroubleClass", TroubleClasses.Success)));
}
if (!string.IsNullOrEmpty(model.BatchId))
{
contentCriteria.Add(Restrictions.Eq("BatchId", model.BatchId));
countCriteria.Add(Restrictions.Eq("BatchId", model.BatchId));
}
if (model.DocumentStartDate != null)
{
contentCriteria.Add(Restrictions.Ge("DocumentDate", model.DocumentStartDate));
countCriteria.Add(Restrictions.Ge("DocumentDate", model.DocumentStartDate));
}
if (model.DocumentEndDate != null)
{
contentCriteria.Add(Restrictions.Le("DocumentDate", model.DocumentEndDate));
countCriteria.Add(Restrictions.Le("DocumentDate", model.DocumentEndDate));
}
if (!string.IsNullOrEmpty(model.VendorId))
{
contentCriteria.Add(Restrictions.Eq("VendorId", model.VendorId));
countCriteria.Add(Restrictions.Eq("VendorId", model.VendorId));
}
using (var session = GetSession())
{
var countC = countCriteria.GetExecutableCriteria(session)
.SetProjection(Projections.CountDistinct("RecordId"));
var contentC = contentCriteria
.AddOrder(Order.Desc("PersistedTimeStamp"))
.GetExecutableCriteria(session)
.SetResultTransformer(Transformers.DistinctRootEntity)
.SetFirstResult((model.CurrentPage * model.ItemsPerPage) - model.ItemsPerPage)
.SetMaxResults(model.ItemsPerPage);
var mq = session.CreateMultiCriteria()
.Add("total", countC)
.Add<InvoiceItem>("paged", contentC);
model.Invoices = ((IEnumerable<InvoiceItem>)mq.GetResult("paged"));
model.Invoices = model.Invoices
.OrderBy(x => x.PersistedTimeStamp);
model.TotalItems = (int)(mq.GetResult("total") as System.Collections.ArrayList)[0];
}
return model;
This returns results, but where I would expect the results to be in groups of model.ItemsPerPage, it rarely is. I think that the .SetResultTransformer(Transformers.DistinctRootEntity) transform is being run after the .SetMaxResults(model.ItemsPerPage) limit, and I don't know why or how to fix it. Can someone please enlighten me?
You need to see the SQL generated by NHibernate as this is not essentially NHibernate bug but behavior of SQL queries when ROWNUM and DISTINCT are applied together. This has been an issue in our known issues list from long.
Following URLs might enlighten you...
ROW NUMBER vs DISTINCT
How ROWNUM works
So this is directly related to what was written in this blog post. Additionally, I had the platform-specific complication of PostgreSQL not allowing a DISTINCT ordered set ordered by something not in the SELECT list. Ultimately, I had to make two calls to the database, like so:
using (var session = GetSession())
{
//I honestly hope I never have to reverse engineer this mess. Pagination in NHibernate
//when ordering by an additional column is a nightmare.
var countC = countCriteria.GetExecutableCriteria(session)
.SetProjection(Projections.CountDistinct("RecordId"));
var contentOrdered = contentCriteria
.SetProjection(Projections.Distinct(
Projections.ProjectionList()
.Add(Projections.Id())
.Add(Projections.Property("PersistedTimeStamp"))
))
.AddOrder(Order.Desc("PersistedTimeStamp"))
.SetFirstResult((model.CurrentPage * model.ItemsPerPage) - model.ItemsPerPage)
.SetMaxResults(model.ItemsPerPage);
var contentIds = contentOrdered.GetExecutableCriteria(session)
.List().OfType<IEnumerable<object>>()
.Select(s => (Guid)s.First())
.ToList();
var contentC = DetachedCriteria.For<InvoiceItem>()
.Add(Restrictions.In("RecordId", contentIds))
.SetResultTransformer(Transformers.DistinctRootEntity);
var mq = session.CreateMultiCriteria()
.Add("total", countC)
.Add("paged", contentC);
model.Invoices = (mq.GetResult("paged") as System.Collections.ArrayList)
.OfType<InvoiceItem>()
.OrderBy(x => x.PersistedTimeStamp);
model.TotalItems = (int)(mq.GetResult("total") as System.Collections.ArrayList)[0];
}
return model;
This is not pretty, but it worked; I think the folks over at NHibernate need to work on this and make it a tad bit easier.

Categories

Resources