This must be a simple question. Given a criteria, how one deletes the entities satisfying the criteria?
The rationale:
HQL and NH criteria are NHibernate specific constructs and as such they are server side DAL implementation details. I do not want them to "leak" to the client side. So, our client side provides LINQ expressions for the server to process. Up until now the requests where select requests and LINQ to NHibernate dealed with them just fine.
However, there is a need to implement batch delete operation now. As usual, the client side provides a LINQ expression and the server is to delete entities satisfying the expression.
Unfortunately, LINQ to NHibernate is of no help here. The most it can do is translate the given LINQ expression to NHibernate criteria.
Anyway, this is the story. I wish to stress that the client side is unaware of NHibernate at all and I like it to stay this way.
P.S.
I am using NH 2.1
You may use the criteria to select the IDs of your elements, join them in a string and use HQL to delete them?
Something like:
public void Delete(ICriteria criteria, string keyName, string tableName)
{
criteria.setProjection(Projections.Attribute(keyName));
IList<int> itemIds = criteria.List<int>();
string collection = string.Join(",", Array.ConvertAll<int, string>(itemIds, Convert.ToString));
Session.HQL(string.Format("delete from {0} where {1} in ({2})", tableName, keyName, collection);
}
This code was not tested or compiled (in particular I'm not sure of the HQL section), but I think that you got the idea: we don't fetch the whole objects thanks to the projection, but only the indices.
Simply put, up until 2.1.2 you cannot.
However, if you can translate the LINQ expression to HQL (or the ICriteria to HQL) then you can use the overloaded ISession.Delete() method which uses a passed HQL string.
In your repository/dao/persistencemanager/whatever class:
public IEnumerable<T> FindAll(DetachedCriteria criteria)
{
return criteria.GetExecutableCriteria(Session).List<T>();
}
and then
public void Delete(DetachedCriteria criteria)
{
foreach (T entity in FindAll(criteria))
{
Delete(entity);
}
}
See Davy Brion's post Data Access with NHibernate.
Edit:
As far as I know, if you want to use Criteria you need to load the objects and iterate over them to delete them. Alternatively use HQL or pass in the SQL to the session.
I know this is an old question but for argument sake; if one uses repository pattern you can declare a delete method which does the following:
public void Delete(System.Linq.Expressions.Expression<System.Func<TEntity, bool>> predicate)
{
var entities = _session.Query<TEntity>().Where(predicate);
foreach (var entity in entities)
_session.Delete(entity);
}
Note the code is using expressions in order for repository interface to be generic enough so you can also implement a for example Entity Framework repository.
Related
I'd like to be able to implement a search method that can take any arbitrary properties of my POCO class as arguments. This works well:
public static IEnumerable<iUser> Search(DataContext context, Func<iUser, bool> predicate)
{
return from i in context.GetTable<iUser>().Where(predicate) select i;
}
but in this case the filtering appears to take place after collecting all the rows in the table.
Is it possible to use Linq to generate an arbitrary query like this without filtering after the sql call? What approaches would you recommend?
Thanks!
LINQ to DB is an Object-Relational Mapper (ORM) that is capable of translating LINQ expressions into SQL. The word "expression" is important here. A Func is not an expression but a delegate, you have to use Expression<Func<>> in LINQ methods for LINQ to DB to be able to translate them. Otherwise the data will be pulled from the database first after which the Func filters them in memory.
So your function should look like:
public static IEnumerable<iUser> Search(DataContext context,
Expression<Func<iUser, bool>> predicate)
{
return context.GetTable<iUser>().Where(predicate);
}
The return type depends on the what you want the caller of this function to be capable of. If you return IQueryable<iUser> the caller will be able to extend the expression by their own expressions. That is, Search(context, somePredicate).Where(...) will be translated into SQL as a whole. Returning IEnumerable will apply any subsequent predicates (either as Func or as Expression) in memory.
Side note, in order to line up with common naming conventions, if iUser is an interface (I have no idea if LINQ to DB supports interfaces) then you should rename it into IUser, otherwise name it User.
Looking to achieve the below but it is failing as the locations.Any() is being treated as an IEnumerable instead of an IQueryable and scalar functions invoked via EF require IQueryable. I need this filter to happen at the database level (not materialize the list first).
How can I get the locations.Any() to be treated as an IQueryable here? I understand the list doesn't exist in the database but is there a way for Entity Framework to understand this any and build and AND statement with nested OR in SQL?
public Address GetAddresses(List<Loctions> locations)
{
_context.Addresses.
Where(a => locations.Any(l => MyContext.CustomFunction(l.PropA,l.PropB, a.PropA, a.ProbB) > 1 ))
}
[DbFunction("fn_DistanceBetweenCoordinates", "dbo")]
public static decimal CustomFunction(decimal SourceLatitude, decimal SourceLongitude, decimal TargetLatitude, decimal TargetLongitude) {
throw new NotImplementedException();
}
You could achieve this by moving CustomFunction into the database and use that server side function when querying from EF.
Please read User defined function mapping and try to adapt the sample according to your use case.
We haven't seen the body of the CustomFunction, so it's impossible to tell if it's viable to do the transfer from client based UDF to server based.
We also don't know how the Locations list is populated. Depending on how that is done, the adaptation of the example code might become more cumbersome.
I have this
public List<TableA> Get(Guid Id, List<int> listId)
{
const string query = "FROM TableA WHERE MyListColumn in (:listId) AND Id in (:Id)";
return session.CreateQuery(query).SetParameterList("listId", listId).SetParameter("Id",Id).List<TableA>().ToList();
}
I am wondering what to do if keys count equals zero? When using a linq way it would jsut return an empty List of TableAs. The only way I can think of is a "if" statement but I am wondering if there is any other way before I do that since I don't want really logic in my method here as it is a repo method.
if(listId.Count == 0){..}
As I get this error
Server Error in '/' Application. An
empty parameter-list generate wrong
SQL; parameter name 'listId'
I consider an if block proper as it's not "program logic": it's query generation.
That said, i personally change the query signature to a MyListColumn =:id for listId.Count==1 and for listId.Count==0 i don't run the query at all: Why have a round-trip when you know that no results will return?
Based on your other questions regarding this topic, I suggest using QueryOver:
The following query will work with listId == null and listId.Count == 0.
public List<TableA> Get(List<int> listId)
{
return session.QueryOver<TableA>()
.Where(Restrictions.In(
Projections.Property<TableA>(e => e.Id),
listId ?? (new List<int>() { }))).List().ToList();
}
I dropped the Guid Id parameter, since that doesn't make much sense here.
Edit:
Yes, there are several ways in NHibernate to query the database (plain SQL, HQL, ICriteria, QueryOver and NHibernate.Linq). All of these have their strengths and weaknesses and I usually try to use the method best suited for the job.
In this I suggestes QueryOver instead of HQL, because you get full Intellisense and compiler support. Also, QueryOver does not throw an error if the list is empty (it will translate to WHERE 1=0). As Jaguar wrote, in that case you could also return an empty list without going to the DB.
I usually only use HQL when QueryOver or ICriteria can't get me the result I want. The Linq provider still lacks some features, so that will currently only work for rather simple queries.
You can also do the above query with Linq. Here is the NHibernate.Linq version of the code above: (this works in NHibernate 3.1, not tested in previous versions)
List<int> idList = new List<int>(){ 1, 2 };
// if idList is null this will still throw an error
var result = session.Query<TableA>()
.Where(e => idList.Contains(e.Id)).ToList();
Is it possible to convert:
public IList Get()
{
return Session.CreateCriteria(typeof(T)).List();
}
to return IQueryable?
What is the difference between IList and IQueryable?
One of the most important aspects when thinking of SQL and Linq is that returning IList means that the query has been executed. Returning IQueryable keeps open the option of deferring the sql execution later (so you could potentially build on the query outside of this method).
However, this would only be the case with NHibernate when using the Linq to NHibernate provider. The Criteria API is not Linqified for the SQL statements. So realistically in this instance returning IList or IQueryable has no significant difference.
What is possible is to return an IEnumerable like this:
public IEnumerable<T> Get()
{
return Session.CreateCriteria(typeof(T)).Future<T>();
}
This way you can do deferred execution as you do with Linq to SQL.
For more info about the Future method see:
http://ayende.com/Blog/archive/2009/04/27/nhibernate-futures.aspx
You can also simulate the IQueryable by returning the ICriteria interface instead of an IList:
public ICriteria<T> Get()
{
return Session.CreateCriteria(typeof(T));
}
This way you can start building the query outside of your method and finally execute it whenever you want.
I am curious about what methods do you use for complex searching with NHibernate ?
I am using Ayende's
What is yours ?
Thanks for your advices and answers.
If we have a complex dynamic search, we will usually construct a SearchParameter object and then pass that into a method that will build our criteria for us.
For example, if we were searching for a person we might have a search object that looks like this:
public class PersonSearchParameters
{
public string FirstName {get; set;}
public string LastName {get; set;}
public ICriteria GetSearchCriteria()
{
DetachedCriteria query = DetachedCriteria.For(typeof (Person));
//Add query parameters
Return query;
}
}
Then for each type of search, we'll be able to create the single criteria from the class, or we could have multple search parameter classes and chain them together
We use HQL, but we're still trying to wrap our heads around the Criteria API for complex queries. We have to manage a lot of duplication when using HQL.
I use pretty much Ayende's too jsut a bit more complex, what do you want to do that you cant do with that?
Basically what we added is that we have an interface where we define all the fields where we want to search and we call this when we are about to make the search which means that we can easily change what we are searching for.
Also we are using Active Record in the project ( on top of Hibernate) and tis pretty cool, loads of tasks gets simplified , thou the lack of docs does hurt sometimes
Cheer