When does NHibernate execute my query? - c#

I am trying to write a generic repository for my NHibernate data access. The Get<T>() method should be able to take an optional predicate, that should be included in the query - that is, NHibernate should generate the WHERE clause in the SQL.
public virtual IList<T> Get(Func<T, bool> predicate = null)
{
// Open NHibernate Session
using (var session = NHibernateHelper.OpenSession())
return (predicate != null
? session.Query<T>().Where(predicate)
: session.Query<T>()).ToList();
}
When I pass in a predicate, and observe the SQL statement NH generates, I see no where clause.
When does NHibernate execute the query? Right when calling .Query<T>()? If so, how can I achieve this?

The query should be executed by the call ToList().
The case why the WHERE clause is not included in your sql statement is that you need to pass an Expression<Func<T,bool>> to your method.
public virtual IList<T> Get(Expression<Func<T, bool>> predicate = null)
{
// Open NHibernate Session
using (var session = NHibernateHelper.OpenSession())
return (predicate != null
? session.Query<T>().Where(predicate)
: session.Query<T>()).ToList();
}
The extension method Where(Func<T,bool>>) is defined on Enumerable, so that the the query loads all data and then applies the WHERE-filter in memory.
The extension method Where(Expression<Func<T,bool>>) is defined on Queryable, so that the query provider (NHibernate) can build a sql statement including your WHERE condition that gets executed on the data source.

Since #Jehof gave you correct explanation I just want to add separate note - you should not return IList<T> from you repository method as then any other linq operation will be executed in memory and not in the database. Assume following calls
var data = repository.Get<Company>(c=>c.Name.StartsWith("MyCompany"));
... some other operations / method calls etc.
var companySubset = data.Where(...);
so now if you have IList<T> Get<T>() you decrease performance but with IQueryable<T> Get<T> you would still have the second Where() appended to the database query.
Of course not all linq operations are supported by IQueryable like (join , last ) and this is the only place to call ToList() extension to evaluate expression.

Related

Using an IQueryable in another IQueryable

I've an extension method, which returns an IQueryable, to get company products, I just want to use it in a IQueryable as a subquery,
public static class DBEntitiesCompanyExtensions {
public static IQueryable<Product> GetCompanyProducts(this DBEntities db, int companyId)
{
return db.Products.Where(m => m.CompanyId == companyId);
}
}
And this is how I call it,
using(var db = new DBEntities()) {
var query = db.Companies.Select(m => new {
CompanyName = m.Name,
NumberOfProducts = db.GetCompanyProducts(m.CompanyId).Count()
});
}
I expected it to works beacuse my extension methods returns an IQueryable, so it could be used in a IQueryable, am I wrong?
This is what I get, Is that possible to make it work?
System.NotSupportedException: LINQ to Entities does not recognize the
method 'System.Linq.IQueryable`1[WebProject.Models.Company]
GetCompanyProducts(WebProject.Models.DBEntities, Int32)'
method, and this method cannot be translated into a store expression.
Problem is not IQueryable inside IQueryable, because you can include subqueries just not the way you did.
In your example whole Select is represented as expression tree. In that expression tree there is something like :
CALL method DBEntitiesCompanyExtensions.GetCompanyProducts
Now EF should somehow traslate this into SQL SELECT statement. It cannot do that, because it cannot "look inside" GetCompanyProducts method and see what is going on there. Nor can it execute this method and do anything with it's result. The fact it returns IQueryable does not help and is not related.
Instead of using IQueryable you should create an expression predicate and use inside the IQueryable object that is connected to the data source
the object looks like that:
Expression<Func<Person, bool>> predicate = x => x.Name == "Adi";
var data = await queryable.Where(predicate).ToListAsync();

Entity Framework: Update inside LINQ query

I've came across this idea of updating a table inside of a LINQ query instead of making the query first, and updating each object returned by that query.
For instance, it is possible to change the value of any property associated with x inside of this query:
var Query = from x in EFContext.SomeTable
where x.id == 1
// SET X = Model or x.Name = "NewName"
select SaveChanges();
Could something like this be done at all?
From MSDN:
In a query that returns a sequence of values, the query variable itself
never holds the query results and only stores the query commands.
Execution of the query is deferred until the query variable is
iterated over in a foreach or for loop. This is known as deferred
execution; that is, query execution occurs some time after the query
is constructed. This means that you can execute a query as frequently
as you want to. This is useful when, for example, you have a database
that is being updated by other applications. In your application, you
can create a query to retrieve the latest information and repeatedly
execute the query, returning the updated information every time.
So, your query will be executed when you do the foreach to update your entities. As #recursive said, LINQ is useful when you need to do a query over a collection, not to update data specifically.
As an aditional info, you can also force immediate execution. This is useful when you want to cache the results of a query,for example, when you want to use some functionalities that Linq to Entities doesn't support. To force immediate execution of a query that does not produce a singleton value, you can call the ToList method, the ToDictionary method, or the ToArray method on a query or query variable.
I believe the best possible way to do so would be to write an extension method which can be done by creating a static class:
public static class Extensions
{
public static IEnumerable<T> Remove<T>(this DbSet<T> Input, Func<T, Boolean> Objects) where T : class
{
var I = Input.Where(Objects).ToList();
for (int i = 0; i < I.Count; i++)
{
Input.Remove(I[i]);
}
return Input;
}
public static IEnumerable<T> Update<T>(this DbSet<T> Input, Func<T, Boolean> Objects, Action<T> UpdateAction) where T : class
{
var I = Input.Where(Objects).ToList();
I.ForEach(UpdateAction);
return I;
}
}
Then you can do:
var Context = new EFContext();
Context.YourTable.Remove(x=> x.Id == 1);
Context.SaveChanges();
// OR
Context.Update((x=> x.Id == 1), (y)=> {y.Title = "something"});
Context.SaveChanges();
You could use method calls, and write a ForEach or ForEachWithContinue method that lets you modify each element, but EF wouldn't know what to do with it anyway, and you'd have to use ToList to pull the items out of EF before you could do anything to them.
Example of ForEach (functional purists won't like this of course):
public static void ForEach<T>(this IEnumerable<T> pEnumerable, Action<T> pAction) {
foreach (var item in pEnumerable)
pAction(item);
}
public static IEnumerable<T> ForEachWithContinue<T>(
this IEnumerable<T> pEnumerable,
Action<T> pAction
) {
foreach (var item in pEnumerable)
pAction(item);
return pEnumerable;
}
Then:
EFContext
.SomeTable
.Where(x => x .id == 1)
.ToList() // come out of EF
.ForEach(x => x.Name = "NewName");
EFContext.SaveChanges();
(Actually, List<T> even already has a ForEach method, too, so writing the IEnumerable extensions is not strictly necessary in this case.)
Basically, EF needs to pull the data into memory to know that you have changed anything, to know what your changes are, and to know what to save to back to the DB. I would also consider what it is you're trying to do, where you are overwriting data that neither the user nor the program has even looked at. How did you determine that this was data you wanted to overwrite in the first place?
Also, you can write direct SQL queries straight to the DB as well, using the ExecuteStoreCommand method, which would be the "normal" way of accomplishing this. Something like:
EFContext.ExecuteStoreCommand(
"UPDATE SomeTable SET Name = {0} WHERE ID = {1};",
"NewName",
1
);

Passing Parameters into an Expression Specification using LinqToSQL

I want to reduce duplicate logic in a LinqToSQL query by using Expression<Func<T,bool>>. We've successfully done the before using static properties like so:
public static Expression<Func<Document, bool>> IsActive
{
get
{
return document => !document.Deleted;
}
}
...
_workspace.GetDataSource<Document>().Where(DocumentSpecifications.IsActive)
However I am struggling to get this working when additional parameters need to be passed into the Expression like so:
public static Expression<Func<Comment, bool>> IsUnread(int userId, Viewed viewed)
{
return
c =>
!c.Deleted && c.CreatedByActorID != actorId
&& (viewed == null || c.ItemCreatedDate > viewed.LastViewedDate);
}
...
// Throwing "Argument type 'System.Linq.Expression<X,bool>' is not assignable
// to parameter type 'System.Func<X,bool>'"
return (from a in alerts
select
new UnreadComments
{
TotalNumberOfUnreadComments =
a.Comments.Count(CommentSpecifications.IsUnread(actorId, a.LastView))
})
How do I convert the specification so it can be accepted in this way and would it still convert to SQL correctly?
EDIT: Following Anders advice I added .Compile() to the query. It now works correctly when unit testing in memory collections; however when LinqToSQL trys to convert it into SQL I get the following exception:
System.NotSupportedException: Unsupported overload used for query operator 'Count'
I've tried:
a.Comments.Count(CommentSpecifications.IsUnread(actorId, a.LastView).Compile())
a.Comments.AsQueryable().Count(CommentSpecifications.IsUnread(actorId, a.LastView))
It looks like the second query is executed as linq-to-objects and not as linq-to-sql. It expects a Func<X, bool> which is what linq-to-objects use, while linq-to-sql (or any other IQueryable provider) expects an uncompiled expression tree that can be translated to something else)
A quick fix is to call Compile() on the expression to convert it to an executable function.
a.Comments.Count(CommentSpecifications.IsUnread(actorId, a.LastView).Compile())
To be more detailed you really should figure out why that query is executed as linq-to-objects and not linq-to-sql. Especially if you expected it to be translated to efficient sql it could become a performance nightmare.
Update
After your edit it's more obvious what's happening:
You're running the query as linq-to-objects during unit testing and as linq-to-sql later. In that case converting the expression to a Func<> through Compile() won't work as linq-to-sql won't recognize it.
Update 2
Composing reusable part into query expression that are to be translated is hard - it confuses the translation engine. Linq-to-sql is somewhat more tolerant than linq-to-entities is, but it is nevertheless hard to get it work. A better way is often to make chaining functions that operate on IQueryable<T>.
public static IQueryable<Comment> WhereIsUnread(this IQueryable<Comment> src, int userId)
{
return src.Where(
c =>
!c.Deleted && c.CreatedByActorID != actorId
&& (viewed == null || c.ItemCreatedDate > c.Alert.LastView.LastViewedDate));
}
...
return (from a in alerts
select
new UnreadComments
{
TotalNumberOfUnreadComments =
a.Comments.WhereIsUnRead(actorId).Count()
})
Something like that should work. Notice I've rewritten how the last viewed date is accessed, as it would otherwise fail translation to SQL when passed in as a parameter.

Can't Translate Extension Method Into Store Expression

I have an extension method as follows:
public static bool SatisfiesSomeCondition(this Post post, SomeObj someObj)
{
return post.SomeObjId == someObj.SomeObjId;
}
And i'm trying to use it like this:
var query = ctx.Posts.Where(p => p.SatisfiesSomeCondition(someObj)).ToList();
But i get the error:
LINQ to Entities does not recognize the method 'Boolean SatisfiesSomeCondition(xx.xx.xx.Post, xx.xx.xx.SomeObj)' method, and this method cannot be translated into a store expression.
If i change the query to:
var query = ctx.Posts.Where(p => p.SomeObjId == someObj.SomeObjId).ToList();
Which is identical to the method.
It works fine, and executes the expected T-SQL.
Why doesn't my first query work? It's a static method, can't it figure out how to create the expression tree? (e.g a WHERE filter). Surely i don't have to materialize the query first? (which means the records i don't want come back over the wire, and i'm doing paging/ordering here, so that's not an option).
Of course, i can just go with what works (e.g the above), but the method SatisfiesSomeCondition is an existing method used across the domain and i want to re-use that functionality, not duplicate it.
Any ideas?
Change it to:
public static IQueryable<Post> SatisfiesSomeCondition(this IQueryable<Post> query, SomeObj someObj)
{
int id = someObj.SomeObjId;
return query.Where(post => post.SomeObjId == id);
}
and use it like:
var query = ctx.Posts.SatisfiesSomeCondition(someObj)).ToList();
This way it should work. You can combine multiple Where conditions in single query so it should offer you at least basic reusablity.
The LINQ to Entities engine has no way of knowing what your static method does.
LINQ queries can only be translated from expression trees.

LINQ Dynamic Where - Not adding clause

I have the following code:
public OTestTable GetTestCode(Func<TestTable, bool> whereClause)
{
return CoreContext.TestTables.Where(whereClause).Select(TestTableMap.DataToObject).FirstOrDefault();
}
CoreContext is my data context (which is initialized in a base class)
My TestTableMap is as follows:
public class TestTableMap
{
public static readonly Func<TestTable, OTestTable> DataToObject = mapper =>
new OTestTable
{
Code = mapper.mycode
};
}
Then in my business method i have the following:
public OTestTable GetTestCode(string code)
{
return QueryEngine.GetTestCode(id => id.mycode == code);
}
From my main program, i am calling GetTestCode with a string value.
When I watch SQL profiler, I get the following:
SELECT [t0].[mycode]
FROM [dbo].[TestTable] AS [t0]
It does not have the where clause appended to the SQL query. If i add the where clause to the LINQ as var query = from c in DataContext.TestTable where c.mycode == '' select c;
It will add the where clause.
However, when I run my code, it will return the correct record, but it seems like I am pulling back all records from the database and filtering in my code (which should not happen).
Any thoughts with what I am doing wrong?
Thanks
In order to construct SQL statements, LINQ to SQL requires an expression tree. Func<TestTable, bool> does not represent an expression tree, it is a "black box" function pointer. LINQ cannot do anything intelligent with this apart from blindly execute it on an in-memory collection.
You need to do this instead:
public OTestTable GetTestCode(Expression<Func<TestTable, bool>> whereClause) {
return CoreContext.TestTables.Where(whereClause).Select(TestTableMap.DataToObject).FirstOrDefault();
}
This code compiles using the Queryable.Where extension method, which does accept an expression tree, rather than the Enumerable.Where extension method, which only accepts a raw delegate.
Try creating your where clause as:
Expression<Func<T, bool>> whereClause
Where the T parameter is your source type Table<T> source
Also see the PredicateBuilder here: http://www.albahari.com/nutshell/predicatebuilder.aspx
It provides you convenient extension methods to predicate IQueryable<T>. like this:
var predicate = PredicateBuilder.True<Family>();
predicate = predicate.And(o => o.Birthday < new DateTime(1980, 1, 1));
.Or(o => o.Name.Contains("ke"));
var result = Source.Where(predicate).ToList();

Categories

Resources