I'm converting an MSSQL query to NHibernate. In essence, this is my SQL:
SELECT * FROM MyTable as T
WHERE (T.Value1 - T.Value2 > 0.01)
And this is my C# code:
var query = QueryOver.Of<MyType> ()
.Where(r => (r.Value1 - r.Value2 > 0.01));
and it's giving me an exception:
Could not determine member from (r.Value1 - r.Value2)
I'm sure there's a way to let the database do the calculation. Does anyone know?
Predicates within QueryOver.Where() can generally only be very simple comparison expressions. QueryOver dos not support arithmetic operations within delegates (r.Value1 - r.Value2). As #Rippo has pointed out You need to fallback to ICriterion.
.QueryOver<MyType>()
.Where(
Expression.Gt(
Projections.SqlProjection("{{alias}}.rValue1 - {{alias}}.rValue2", null, null),
0.01
)
)
In sql query you use "MyTable" but in QueryOver: "MyType"
reading back on this, I wanted to share the solution that ended up in production: mapping a readonly formula in FluentNHibernate.
the model needs to have a Property like this:
public virtual decimal Difference { get { return Value2 - Value1; } protected set; }
and this property should be mapped as follows:
Map(x => x.Difference).Formula("(Value2 - Value1)").Readonly();
the string parameter of the Formula method ends up in the SQL Select query, similar to this :
Select Id, Value1, Value2, (Value2 - Value1) from MyTable...
Note that the calculated property needs to have a protected setter or NHibernate will throw an exception saying that no setter is available.
Related
I've tried several methods such as using double hyphens, i.e. --THIS IS A COMMENT but when the executed sql is read in a profiler the comment is stripped out leaving only raw SQL that is being performed.
I want to do this to enable rapid identification of queries and their origins when looking at a SQL Profilers output that has over 8000 entries per minute,
so something like
--Method signature and an application name
e.g.
--MyMethod(string username) in MyFunkyAppName.
I'm using EntityFramework 4.3 which complicates things even further with linq to entities and a smattering of linq to sql thrown in for good measure.
EDIT: I'm aware of solutions to add a dodgy where clause or use anonymous properties to identify things such as Clever tricks to find specific LINQ queries in SQL Profiler but I'm hoping for a far less hacky approach or perhaps a generic one.
Here is an extension method you can use to tag your Entity Framework queries. It uses the WHERE clause, but shouldn't impair performance.
public static class ExtensionMethods
{
public static IQueryable<T> SetQueryName<T>(this IQueryable<T> source,
[CallerMemberName] String name = null,
[CallerFilePath] String sourceFilePath = "",
[CallerLineNumber] Int32 sourceLineNumber = 0)
{
var expr = Expression.NotEqual(Expression.Constant("Query name: " + name), Expression.Constant(null));
var param = Expression.Parameter(typeof(T), "param");
var criteria1 = Expression.Lambda<Func<T, Boolean>>(expr, param);
expr = Expression.NotEqual(Expression.Constant($"Source: {sourceFilePath} ({sourceLineNumber})"), Expression.Constant(null));
var criteria2 = Expression.Lambda<Func<T, Boolean>>(expr, param);
return source.Where(criteria1).Where(criteria2);
}
}
Here is how to use it:
context.Table1.SetQueryName().Where(x => x.C1 > 4)
It will use the calling method name as the query name.
You can specify another name like this:
context.Table1.SetQueryName("Search for numbers > 4").Where(x => x.Number > 4)
Here is how the SQL will look like:
SELECT
[Extent1].[Number] AS [Number]
FROM (SELECT
[Table1].[Number] AS [Number]
FROM [dbo].[Table1] AS [Table1]) AS [Extent1]
WHERE
(N'Query name: Search for numbers > 4' IS NOT NULL)
AND
(N'Source: C:\Code\Projects\MyApp\Program.cs (49)' IS NOT NULL)
AND ([Extent1].[Number] > 4)
I am trying the following code but nhibernate is throwing the following exception:
Expression type 'NhSumExpression' is not supported by this SelectClauseVisitor.
var data =
(
from a in session.Query<Activity>()
where a.Date.Date >= dateFrom.Date && a.Date.Date <= dateTo.Date
group a by new { Date = a.Date.Date, UserId = a.RegisteredUser.ExternalId } into grp
select new ActivityData()
{
UserID = grp.Key.UserId,
Date = grp.Key.Date,
Bet = grp.Sum(a => a.Amount < 0 ? (a.Amount * -1) : 0),
Won = grp.Sum(a => a.Amount > 0 ? (a.Amount) : 0)
}
).ToArray();
I've been looking around and found this answer
But I am not sure what I should use in place of the Projections.Constant being used in that example, and how I should create a group by clause consisting of multiple fields.
It looks like your grouping over multiple columns is correct.
This issue reported in the NHibernate bug tracker is similar: NH-2865 - "Expression type 'NhSumExpression' is not supported by this SelectClauseVisitor."
Problem is that apart from the less-than-helpful error message, it's not really a bug as such. What happens in NH-2865 is that the Sum expression contains something which NHibernate doesn't know how to convert into SQL, which result in this exception being thrown by a later part of the query processing.
So the question is, what does you sum expression contains that NHibernate cannot convert? The thing that jumps to mind is the use of the ternary operator. I believe the NHibernate LINQ provider has support for the ternary operator, but maybe there is something in this particular combination that is problematic.
However, I think your expressions can be written like this instead:
Bet = grp.Sum(a => Math.Min(a.Amount, 0) * -1), // Or Math.Abs() instead of multiplication.
Won = grp.Sum(a => Math.Max(a.Amount, 0))
If that doesn't work, try to use a real simple expression instead, like the following. If that works, we at least know the grouping itself work as expected.
Won = grp.Sum(a => a.Amount)
I refer to this example: Return selected specified columns
Quote:
If BlobDetails isn't the LINQ entity, then you can do it directly:
var qry = from b in dc.Blobs
orderby b.RowVersion descending
select new BlobDetails {
Id = b.Id, Size = b.Size,
Signature = b.Signature, RowVersion = b.RowVersion};
return qry.ToList();
I see that they are selecting specific column in a query through the ORM-tool LINQ TO SQL.
Critics of ORM-tools say that, if I remember correctly, that ORM-tools select and return entire objects from the table, and limits the options of selecting only specific columns as one can do through classic SQL-programming. Of course, I have my doubts about that when I see this example, but nevertheless, I still keep asking myself the question: Does the database return only the selected columns, or does it return the entire objects, leaving the column-filtering to the ORM-tool?
From this example, they also have a class called Blobdetails:
public class BlobDetails
{
public int Id { get; set; }
public string Signature { get; set; }
public int Size { get; set; }
public System.Data.Linq.Binary RowVersion { get; set; }
}
Do I need to create my own classes everytime I only wish to select a few columns from a table through LINQ?
You don't need to create new classes to select few columns from a table. You can use anonymous types for that.
var qry = from b in dc.Blobs
orderby b.RowVersion descending
select new { b.Id, b.Size, b.Signature, b.RowVersion};
return qry.ToList();
Only selected columns are transferred. There is no difference between using plain SQL and using LINQ to SQL. When you are executing LINQ query, it is converted to plain SQL and executed. Then result is mapped to your objects.
You can use SQL Server Profiler to see what query was generated and executed on server. Also you can use LINQPad to see what SQL will be generated from your query. In your case query will be same either you use BlobDetails or anonymous object:
SELECT [t0].[Id], [t0].[Size], [t0].[Signature], [t0].[RowVersion]
FROM [Blobs] AS [t0]
ORDER BY [t0].[RowVersion] DESC
when you do projections LINQ does indeed only select those columns and there is nothing preventing you from materializing it however you want. So in your example code
select new BlobDetails
{
Id = b.Id,
Size = b.Size,
Signature = b.Signature,
RowVersion = b.RowVersion
};
Only b.id, b.size, b.signature, & b.rowversion are selected. You can verify this with sql profiler or your debugger, I seem to recall there is also a function you can call on the datacontext to get the last query that was ran.
I think that the answer to your first question is already in the POST you mentioned. However...
If your BlobDetails is not LINQ entity you can simply use it in your select statement to define (shrink) your projection attributes. For example:
var qry = from b in dc.Blobs
select new BlobDetails { Id = b.Id, Size = b.Size }
would compile to SQL query like SELECT Id, Size FROM Blob ....
But if BlobDetails is LINQ entity you will need to use that AsEnumerable() hack otherwise you will get NotSupportedException: Explicit construction of entity type in query is not allowed.
var qry = from b in dc.Blobs.AsEnumerable()
select new BlobDetails { Id = b.Id, Size = b.Size }
Edit
As #Chris Pitman stated in his comment this AsEnumerable() approach could create serious bottleneck, beacause the whole table would be loaded in memory before applying the projection. So it is not recommended!
To your second question:
You will need to create custom class for objects that you want use easily outside the scope of the method. Properties of an anonymous object are visible only in the scope, where they have been declared and anonymous objects can be cast only to type object.
So if you want to return anonymous objects from method the return type would has to be an enumerable of object or dynamic as #xeondev stated in his comment.
There's no need to create your own classes, you can return an anonymous type. You can write something like this
var qry = from b in dc.Blobs
orderby b.RowVersion descending
select new {
Id = b.Id, Size = b.Size,
Signature = b.Signature, RowVersion = b.RowVersion};
return qry.ToList();
Although the signature of the method should look to something like this
public IEnumerable<object> GetItems()
or
public dynamic GetItems()
So if you are going to use the result of linq query in outer scope like you example suggest, it is highly recommended you create your own classes.
I have a fairly run-of-the-mill QueryOver query containing the following,
.SelectList(list => list
.SelectGroup(() => txn.GroupField)
.SelectGroup(() => txn.Date))
.List<object[]>()
This query works as expected however I now have a requirement to group by the truncated Date as some of the Date's for these objects may contain a time component. This seems like it should be a trivial change but I can't find a way that is supported by NHibernate.
The obvious solution would be the change below but is not supported.
.SelectGroup(() => txn.Date.Date))
Any ideas?
Thanks
Your QueryOver could look like this:
Status alias = null;
var query = QueryOver.Of(() => alias)
.Select(Projections.GroupProperty(
Projections.SqlFunction("date", NHibernateUtil.Date, Projections.Property(() => alias.Date))));
Output (pseudo sql):
SELECT
...
FROM [Status] this_
GROUP BY dateadd(dd, 0, datediff(dd, 0, this_.Date))
Hope this helps, cheers!
You might want to add a helper property to your map, using the Formula command, to be able to use the date (instead of datetime) in queries.
here's an example from my code; it uses a decimal value, but this works fine with any subquery:
model class has this property, to be mapped to a formula:
public virtual decimal Profit
{
get { return this.SellPrice - this.Cost; }
set { return; }
}
fluentNHibernate map:
//SellPrice and Cost are columns in the object's table
Map(v => v.Profit).Formula("(SellPrice - Cost)"); // this field is calculated, not read
be sure to put the formula between () brackets though.
If you'd make your formula a select query that trunks the datetime into a date, you could then group by that property in your query.
I'm trying to write a query to select using the DateTime. Year as a where parameter, but I'm receiving this error from nunit:
NHibernate.QueryException : could not resolve property: Register.Year of: Estudantino.Domain.Events
In class Events I've a property named Register as a DateTime type.
public virtual DateTime Registada { get; set; }
This is the method that is returning the error:
using (ISession session = NHibernateHelper.OpenSession())
{
return session.QueryOver<Evento>()
.Where(x => x.Register.Year == year)
.List();
}
The variable year is of type int, that is been passed to the method.
Does anyone have an idea of what i'm doing wrong? My database server is SQL Server 2005 Express.
QueryOver does not resolve things like DateTime.Year.
Use LINQ instead:
return session.Query<Evento>()
.Where(x => x.Register.Year == year)
.ToList();
In QueryOver, you can just say:
dateTimeColumn.YearPart() == 1999
There is also other extension methods: MonthPart(), DayPart(), etc.
You may need to use HQL here instead. Basically NHibernate does not know really what to do with year. I suspect you need to use the RegisterFunction to register a YEAR function.
Please read this article in its entirety to fully understand what it is you are trying to do.
Create a custom dialect in code
Create a function in SQL server called MyYearFunction that returns a year for a date
Then use HQL (not sure if QueryOver can do this) to get the date where dbo.MyYearFunction(:date) = 12" ...
.Where(x => x.Register >= new DateTime(year, 1, 1) && x.Register <
new DateTime(year + 1, 1, 1))