If i have a simple named query defined, the preforms a count function, on one column:
<query name="Activity.GetAllMiles">
<![CDATA[
select sum(Distance) from Activity
]]>
</query>
How do I get the result of a sum or any query that dont return of one the mapped entities, with NHibernate using Either IQuery or ICriteria?
Here is my attempt (im unable to test it right now), would this work?
public decimal Find(String namedQuery)
{
using (ISession session = NHibernateHelper.OpenSession())
{
IQuery query = session.GetNamedQuery(namedQuery);
return query.UniqueResult<decimal>();
}
}
As an indirect answer to your question, here is how I do it without a named query.
var session = GetSession();
var criteria = session.CreateCriteria(typeof(Order))
.Add(Restrictions.Eq("Product", product))
.SetProjection(Projections.CountDistinct("Price"));
return (int) criteria.UniqueResult();
Sorry! I actually wanted a sum, not a count, which explains alot. Iv edited the post accordingly
This works fine:
var criteria = session.CreateCriteria(typeof(Activity))
.SetProjection(Projections.Sum("Distance"));
return (double)criteria.UniqueResult();
The named query approach still dies, "Errors in named queries: {Activity.GetAllMiles}":
using (ISession session = NHibernateHelper.OpenSession())
{
IQuery query = session.GetNamedQuery("Activity.GetAllMiles");
return query.UniqueResult<double>();
}
I think in your original example, you just need to to query.UniqueResult(); the count will return an integer.
Related
I'm trying to do a select count query in Sql Server using Dapper. The expected response should be 0 when a profile does not exist. When I do the query in SSMS it returns correctly, but in the API using Dapper it returns 1. Any idea why this is happening?
public IActionResult GetProfileCount(string profileId)
{
int profileCount = 0;
using (IDbConnection db = new SqlConnection(connectionString))
{
try
{
profileCount = db.Query($"select count(*) from Profile where Id='{profileId}'").Count();
}
catch(Exception ex)
{
Console.WriteLine($"Error retrieving count for ProfileId: {profileId}", ex.Message);
}
}
return Ok(profileCount);
}
I see you added your own answer but could I recommend not doing it that way. When you do
profileCount = db.Query($"select * from Profile where Id='{profileId}'").Count();
What you are actually doing is selecting every field from the database, pulling it into your C# application, and then counting how many results you got back. Then you are binning all that data you got back, very inefficient!
Change it to this :
profileCount = db.QueryFirst<int>($"select count(*) from Profile where Id = #profileId", new { profileId })");
Instead you are selecting an "int" from the result set, which just so happens to be your count(*). Perfect!
More on querying in Dapper here : https://dotnetcoretutorials.com/2019/08/05/dapper-in-net-core-part-2-dapper-query-basics/
Also notice that (similar to the other answer), I am using parameterized queries. I also heavily recommend this as it protects you from SQL Injection. Your initial example is very vulnerable!
You can read a little more about SQL Injection in C#/MSSQL here https://dotnetcoretutorials.com/2017/10/11/owasp-top-10-asp-net-core-sql-injection/ But just know that Dapper protects you from it as long as you use the inbuilt helpers to add parameters to your queries.
Another option is use the method ExecuteScalar for "select count" queries:
profileCount = db.ExecuteScalar<int>("select count(*) from Profile where Id=#profileId", new { profileId });
Ref.: https://www.learndapper.com/selecting-scalar-values
Try and change your query to the following:
db.Query($"select count(*) from Profile where Id = #ProfileId", new { ProfileId = profileId }).Count()
I figured it out. The .Count() is counting the rows of the result, which is going to be 1 because the result is one row displaying the number 0. I switched my code to this and it works now.
public IActionResult GetProfileCount(string profileId)
{
int profileCount = 0;
using (IDbConnection db = new SqlConnection(connectionString))
{
try
{
profileCount = db.Query($"select * from Profile where Id='{profileId}'").Count();
}
catch(Exception ex)
{
Console.WriteLine($"Error retrieving count for ProfileId: {profileId}", ex.Message);
}
}
return Ok(profileCount);
}
Domain class:
class Transaction {
String roundId
BigDecimal amount
:
}
The SQL we wish to execute the following:
"select sum(t.amount) from transaction t where t.roundId = xxx"
We have been unable to find an example which does not return Transaction rows.
We assume there are two approaches:
Use projections and/or criteria etc? All the examples we have found only return lists of transaction rows, not the sum.
Use raw SQL. How do we call SQL, and get a handle on the BigDecimal it returns?
I tried this:
class bla{
def sessionFactory
def someMethod() {
def SQLsession = sessionFactory.getCurrentSession()
def results = SQLsession.createSQLQuery("select sum(t.credit) from transaction t where t.round_id = :roundId", [roundId: roundId])
But this fails with
groovy.lang.MissingMethodException: No signature of method: org.hibernate.internal.SessionImpl.createSQLQuery() is applicable for argument types: (java.lang.String, java.util.LinkedHashMap)
Also, I have no idea what the return type would be (cant find any documentation). I am guessing it will be a list of something: Arrays? Maps?
==== UPDATE ====
Found one way which works (not very elegant or grails like)
def SQLsession = sessionFactory.getCurrentSession()
final query = "select sum(t.credit) from transaction t where t.round_id = :roundId"
final sqlQuery = SQLsession.createSQLQuery(query)
final results = sqlQuery.with {
setString('roundId', roundId)
list() // what is this for? Is there a better return value?
}
This seems to return an array, not a list as expected, so I can do this:
if (results?.size == 1) {
println results[0] // outputs a big decimal
}
Strangely, results.length fails, but results.size works.
Using Criteria, you can do
Transaction.withCriteria {
eq 'roundId', yourRoundIdValueHere
projections {
sum 'amount'
}
}
https://docs.jboss.org/hibernate/core/3.3/api/org/hibernate/classic/Session.html
Query createSQLQuery(String sql, String[] returnAliases, Class[] returnClasses)
Query createSQLQuery(String sql, String returnAlias, Class returnClass)
The second argument of createSQLQuery is one or more returnAliases and not meant for binding the statement to a value.
Instead of passing your values in the 2nd argument, use the setters of your Query object i.e. setString, setInteger, etc.
results.setInteger('roundId',roundId);
Am developing a ViewModel/PresentationModel which is getting complex.
I want the Linq query to return an IQueryable<UserPresentationModel>
Using EntityFramework against MSSQL
Is it possible to do any sort of iteration over the set before returning it to the presentation layer ie
List<UserPresentationModel> list = new List<UserPresentationModel>();
foreach (var person in listOfPeople)
{
UserPresentationModel u = new UserPresentationModel();
int userUIStatus = GetColourStateOfPerson(person);
u.FirstName = person.FirstName;
u.UserUIStatus = userUIStatus;
list.Add(u);
}
return list
This feels like it would always be N+1, and I'd never get the advantages of deferred execution, composing of queries..
Or (and I think am answering my own question) do I need to think in a SQL set based manner.
First, we can convert your code to LINQ.
IEnumerable<UserPresentationModel> models =
from person in listOfPeople
select new UserPresentationModel
{
FirstName = person.FirstName,
UserUIStatus = GetColourStateOfPerson(person)
}
return models.ToList();
Now, if GetColourStateOfPerson is making a DB round-trip, you definitely want to pull that out.
IDictionary<int, int> colourStatesByPersonId = GetColourStatesOfPeople(listOfPeople);
IEnumerable<UserPresentationModel> models =
from person in listOfPeople
select new UserPresentationModel
{
FirstName = person.FirstName,
UserUIStatus = colourStatesByPersonId[person.PersonId]
}
return models.ToList();
You could probably manage to create a single LINQ query that grabs just the first names and colour states of the people you want in a single query, but you haven't provided enough information about your data context for me to help you with that.
I would personally avoid passing around an IQueryable, which could continue making database trips any time somebody touches it. Let your data layer get out all the data you're likely to need, compose it into a list, and return that.
use IEnumerable<T>.Aggregate() instead of looping.
return listOfPeople.Aggregate(new List<UserPresentationModel>(), person => {
return new UserPresentationModel {
FirstName = person.FirstName,
UserUIStatus = GetColourStateOfPerson(person)
};
}).AsQueryable();
return listOfPeople.AsEnumerable().Select(p =>
new UserPresentationModel
{
FirstName = p.FirstName,
UserUIStatus = GetColourStateOfPerson(p)
}).AsQueryable();
I'm assuming that listOfPeople is an IQueryable that will eventually execute against your database. If that is the case then AsEnumerable() is important because SQL Server won't know what to do with GetColourStateOfPerson(). AsEnumerable() will force the IQueryable's expression tree to execute, pull the resulting rows out of your database and then apply Select() transformation in code as oppose to in SQL Server.
If you can implement GetColourStateOfPerson() as a stored proc or database function then you can omit AsEnumerable() and AsQueryable() and allow execution to delay even longer.
I need to find the number of documents that are in the raven database , so that I can properly page the documents out. I had the following implementation -
public int Getcount<T>()
{
IQueryable<T> queryable = from p in _session.Query<T>().Customize(x =>x.WaitForNonStaleResultsAsOfLastWrite())
select p;
return queryable.Count();
}
But if the count is too large then it times out.
I tried the method suggested in FAQs -
public int GetCount<T>()
{
//IQueryable<T> queryable = from p in _session.Query<T>().Customize(x => x.WaitForNonStaleResultsAsOfLastWrite())
// select p;
//return queryable.Count();
RavenQueryStatistics stats;
var results = _session.Query<T>()
.Statistics(out stats);
return stats.TotalResults;
}
This always returns 0.
What am I doing wrong?
stats.TotalResults is 0 because the query was never executed. Try this instead:
var results = _session
.Query<T>()
.Statistics(out stats)
.Take(0)
.ToArray();
The strange syntax to get the statistics tripped me up as well. I can see why the query needs to be run in order to populate the statistic object but the syntax is a bit verbose imo.
I have written the following extension method for use in my unit tests. It helps keep the code terse.
Extension Method
public static int QuickCount<T>(this IRavenQueryable<T> results)
{
RavenQueryStatistics stats;
results.Statistics(out stats).Take(0).ToArray();
return stats.TotalResults;
}
Unit Test
...
db.Query<T>().QuickCount().ShouldBeGreaterThan(128);
...
I'm trying to figure out if there's a way I can do the following strictly using Criteria and DetachedCriteria via a subquery or some other way that is more optimal. NameGuidDto is nothing more than a lightweight object that has string and Guid properties.
public IList<NameGuidDto> GetByManager(Employee manager)
{
// First, grab all of the Customers where the employee is a backup manager.
// Access customers that are primarily managed via manager.ManagedCustomers.
// I need this list to pass to Restrictions.In(..) below, but can I do it better?
Guid[] customerIds = new Guid[manager.BackedCustomers.Count];
int count = 0;
foreach (Customer customer in manager.BackedCustomers)
{
customerIds[count++] = customer.Id;
}
ICriteria criteria = Session.CreateCriteria(typeof(Customer))
.Add(Restrictions.Disjunction()
.Add(Restrictions.Eq("Manager", manager))
.Add(Restrictions.In("Id", customerIds)))
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Name"), "Name")
.Add(Projections.Property("Id"), "Guid"))
// Transform results to NameGuidDto
criteria.SetResultTransformer(Transformers.AliasToBean(typeof(NameGuidDto)));
return criteria.List<NameGuidDto>();
}
return Session.CreateCriteria<Customer>()
.CreateAlias("BackupManagers", "bm", LeftOuterJoin)
.Add(Restrictions.Disjunction()
.Add(Restrictions.Eq("Manager", manager))
.Add(Restrictions.Eq("bm.Id", manager.Id)))
.SetProjection(Projections.Distinct(Projections.ProjectionList()
.Add(Projections.Property("Name"), "Name")
.Add(Projections.Property("Id"), "Guid")))
.SetResultTransformer(Transformers.AliasToBean(typeof(NameGuidDto)))
.List<NameGuidDto>();
I threw a distinct in there because I'm not sure if it is possible to have backup and primary be the same person.