Linq repository and GetTable<T>() - sql

I'm following the fairly standard L2S repository pattern, using the following as one of the methods
public IEnumerable<T> GetAllByFilter(Func<T, bool> expression)
{
return _dataContext.GetTable<T>().Where(expression);
}
I'm a bit miffed to see that the call to GetTable appears to literally get the table, with the Where expression presumably evaluated in-memory afterwards.
So a simple call like
var order = GetAllByFilter(o => o.OrderNumber == 1);
which should only ever return one record, is fetching the entire 50000 record database.
Is Linq normally this bad? Or am I missing something?

Change:
public IEnumerable<T> GetAllByFilter(Func<T, bool> expression)
{
return _dataContext.GetTable<T>().Where(expression);
}
To:
public IQueryable<T> GetAllByFilter(Expression<Func<T, bool>> expression)
{
return _dataContext.GetTable<T>().Where(expression);
}
This will use Queryable (i.e. SQL) instead of Enumerable (i.e. local) and therefore will perform much better.

Related

How to loop an IList of objects and set state to modified for SQL

I am trying to loop a list of objects and set them as modified before posting to SQL. but I am getting an error Object Reference not set to an instance of an object.
setting a single instance of object works fine using
[BindProperty]
public Models.Ord Order { get; set; }
Order = await _context.Ord.SingleOrDefaultAsync(m => m.Id == id);
_context.Attach(Order).State = EntityState.Modified;
But this return an error.
[BindProperty]
public IList<OrdLn> OrderLineList { get; private set; }
OrderLineList = await _context.OrdLn.Where(o => o.OrdId == id).ToListAsync();
foreach (OrdLn p in OrderLineList)
{
_context.Attach(p).State = EntityState.Modified;
}
await _context.SaveChangesAsync();
The 'Q' in LINQ stands for "Query". LINQ is not meant to update objects.
You can use LINQ to find the object you want to update and then update it "traditionally".
When you are using SingleOrDefault function, the object in remote or database until you make changing the entity type(changing to new class), so there you can make change on the entity and db operation able to carryout.
When you are using List function. the object is in memory of the application so there is relation between db and will not able to carryout db operate operation.
All of these return types have interfaces inherit from IEnumerable, which you should make sure you understand. That interface basically lets you use the class in a foreach statement (in C#).
IList : is everything that ICollection is, but it also supports adding and removing items, retrieving items by index, etc. It's the most commonly-used interface for "lists of objects", which is vague I know.
IQueryable : is an enumerable interface that supports LINQ. You can always create an IQueryable from an IList and use LINQ to Objects, but you also find IQueryable used for deferred execution of SQL statements in LINQ to SQL and LINQ to Entities.

Querying with linq a collection mapped as a map (IDictionary)

Using NHibernate, I have a collection of entities mapped as a dictionary.
By example, class A has a collection of B named Children, mapped as a IDictionary<int, B>. B has a property Name.
Querying class A base on some condition on B children unrelated to their dictionary indexation is quite straightforward with HQL:
from A where A.Children.Name = 'aName'
Runs flawlessly.
But for achieving the same with LINQ, this is quite less straightforward:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
Fails with message could not resolve property: Values of: B
So yes, we can trick it through
IQueryable<A> query = ...;
query.Where(a => ((ICollection<B>)a.Children).Any(b => b.Name == "aName"));
That does works and yields expected results.
But this looks to me a bit ugly, I would rather not have to do that 'invalid' cast (at least 'invalid' outside of linq2NH context).
Is there any better way for querying a children collection mapped as a IDictionary with Linq and NHibernate?
As an exercise, I have decided to extend linq-to-nhibernate for supporting Values. This give a solution to the question.
There is many ways for extending linq2NH, see this list. Here, I need to add a new 'generator', as in my answer to another question.
First you need a bunch of using:
using System.Reflection;
using System.Linq.Expressions;
using System.Collections;
using System.Collections.Generic;
using NHibernate.Hql.Ast;
using NHibernate.Linq.Visitors;
using NHibernate.Linq.Functions;
Then, implement HQL translation for Values.
public class DictionaryValuesGenerator : BaseHqlGeneratorForProperty
{
public override HqlTreeNode BuildHql(
MemberInfo member, Expression expression,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
// Just have to skip Values, HQL does not need it.
return visitor.Visit(expression).AsExpression();
}
}
Extend the default linq2NH registry with your generator:
public class ExtendedLinqToHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public override bool TryGetGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
if (base.TryGetGenerator(property, out generator))
return true;
return TryGetDictionaryValuesGenerator(property, out generator);
}
private DictionaryValuesGenerator _dictionaryValuesGenerator =
new DictionaryValuesGenerator();
protected bool TryGetDictionaryValuesGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
generator = null;
if (property == null || property.Name != "Values")
return false;
var declaringType = property.DeclaringType;
if (declaringType.IsGenericType)
{
var genericType = declaringType.GetGenericTypeDefinition();
if (genericType != typeof(IDictionary<,>))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
if (declaringType != typeof(IDictionary))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
}
I had quite a hard time figuring out how to register a generic class property generator. There is built-in support for many cases including generic dictionaries methods through derived class of GenericDictionaryRuntimeMethodHqlGeneratorBase, but apparently no support for generic dictionaries properties. So I have ended up 'hard coding' it in the TryGetGenerator method for properties.
Now configure NH to use your new registry. With hibernate.cfg.xml, add following property node under session-factory node:
<property name="linqtohql.generatorsregistry">YourNameSpace.ExtendedLinqToHqlGeneratorsRegistry, YourAssemblyName</property>
Now this does work:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
Disclaimer: done only as an exercise, I have not even committed that in my actual code. I am currently no more using any map in my mappings. I have added some just for testing, then I have undone all.

How would I alter the SQL that Linq-to-Nhibernate generates for specific columns?

To take advantage of Full text indexing on MariaDB 10, I need to use this new "MATCH AGAINST" syntax in the sql string.
http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html#function_match
I think it would be really cool if, for certain columns only, I could override linq-to-nhibernate to change the sql it generates when I use
.Where(x => FullTextIndexedStringProperty.Contains("Some word")).ToList().
Who can give me some general directions on how to get started?
This will get you a very simple MATCH ... AGAINST clause. If you want to get more complex (more arguments, specifying the search modifier), you'll have to make some bigger changes. Hopefully this will get you started though:
Create a new dialect and register a simple MATCH (...) AGAINST (...) function:
public class CustomMySQLDialect : MySQLDialect
{
public CustomMySQLDialect()
{
this.RegisterFunction(
"matchagainst",
new SQLFunctionTemplate(
NHibernateUtil.Boolean,
"match (?1) against (?2)"));
}
}
Create a static extension method on string that you'll use in LINQ statements:
public static class LinqExtensions
{
public static bool MatchAgainst(this string source, string against)
{
throw new NotImplementedException();
}
}
Create a new LINQ to HQL generator class that associates the method with the SQL function we registered in the custom dialect:
public class MatchAgainstGenerator : BaseHqlGeneratorForMethod
{
public MatchAgainstGenerator()
{
this.SupportedMethods = new[]
{
ReflectionHelper.GetMethod(() => LinqExtensions.MatchAgainst(null, null))
};
}
public override HqlTreeNode BuildHql(
MethodInfo method,
System.Linq.Expressions.Expression targetObject,
ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder,
IHqlExpressionVisitor visitor)
{
return treeBuilder.BooleanMethodCall(
"matchagainst",
arguments.Select(visitor.Visit).Cast<HqlExpression>());
}
}
Create a custom LinqToHqlGeneratorsRegistry:
public class MyLinqToHqlRegistry : DefaultLinqToHqlGeneratorsRegistry
{
public MyLinqToHqlRegistry()
{
var generator = new MatchAgainstGenerator();
RegisterGenerator(typeof(LinqExtensions).GetMethod("MatchAgainst"), generator);
}
}
Use your custom dialect, and Linq to HQL registry either in your cfg.xml file or in code:
var cfg = new Configuration()
.DataBaseIntegration(db =>
{
db.Dialect<CustomMySQLDialect>();
})
.LinqToHqlGeneratorsRegistry<MyLinqToHqlRegistry>();
Finally, use your extension method in a LINQ-to-NHibernate query:
session.Query<Article>()
.Where(a => a.Body.MatchAgainst("configured"))
.ToList()
.Dump();
This will generate SQL that looks like this:
select
userquery_0_.Id as Id51_,
userquery_0_.Title as Title51_,
userquery_0_.Body as Body51_
from
articles userquery_0_
where
match (userquery_0_.Body) against ('configured');
Again, this won't help if you have more complicated requirements. But hopefully this is at least a good starting point.
In case anyone is curious about how to make this support more complex scenarios, here are the problems I think you'd run into:
Separating the arguments to MATCH from those to AGAINST.
Registering a custom SQL function with NHibernate that can take an arbitrary number of arguments in different places
Creating the correct HQL even after solving the two issues above.

Calling instance methods inside a where conditon in Nhibernate

I have this piece of code in a TaskRepository :
public List<Task> GetActiveTasks()
{
return SessionContainer.Session
.Query<Task>()
.Where(t => t.IsActive())
.ToList();
}
And this is the IsActive() method in the Task Class
public virtual bool IsActive()
{
return States.ToList().Max().Name == "Active";
}
My problem is that GetActiveTasks() returns NotSupportedException.
Does anyone knows whats the problem ? I think it is not possible to call instance methods in the Where predicate. If that is not possible, is there any workaround for this? I need to get all the active tasks but I dont know how to do it if it is not like this.
The lambdas given in the Query have to be translated to sql. How should NHibernate interprete a method implemented by you? it can't. However you could map IsActive as a Formula property and query that. Something like:
Map(x => x.IsActive).Formula("(SELECT ... FROM (Select s.Name FROM States s WHERE s.task_id = Id ORDER BY Id desc LIMIT 1) maxState WHERE maxState.Name == Active)")
One option is to refactor the Task class like this:
public static Expression<Func<Task, bool>> IsActiveExpr =
(task) => task.States.Max().Name == "Active";
private static Func<Task, bool> _IsActiveCompiled = IsActiveExpr.Compile();
public virtual bool IsActive()
{
return IsActiveCompiled(this);
}
Now you can use IsActiveExpr in LINQ queries.
Another options is to extend the NHibernate query provider so that it recognizes the IsActive() method directly. Do a search, or see for instance my reply to this question: Lambda string as VARCHAR. But care might need to be taken to avoid duplicating logic.

NHibernate Linq uses implicit transaction?

I'm using Ayende's NHibernate Linq version 2.1.2, available here, and when I use NHProf to inspect queries that use this method:
public IQueryable<T> GetAll()
{
return Session.Linq<T>();
}
It gives me the warning that I'm using an implicit transaction. Problem is, I'm using this in a repository to abstract out the database session, but I still want the flexibility of returning an IQueryable so I can run any Linq query I want. Is there a way to explicitly wrap the Session.Linq<T>() in a transaction without exposing it, or should I just ignore the warning in this case?
A little more background. I'm using the method like so:
var repo = new Repository();
var animals = repo.GetAll<Animal>().Where(x => x.Size > 100);
NoahsArk.LargeAnimals.AddRange(animals);
NHProf's message is actually not related to your repository implementation. It's just pointing out that you are running your query outside a transaction, which can be a source of problems.
Ayende explains this in a blog post: NH Prof Alerts: Use of implicit transactions is discouraged
You should manage your transactions from a higher level in your application. There are several ways to do this while using repositories, have a look at unhaddins
I had a very similar problem which I solved quite simply.
I created a LinqClass within my repository returned by my Linq method
public virtual LinqClass Linq()
{
return new LinqClass(Session, LinqSource());
}
public class LinqClass : IDisposable
{
public LinqClass(ISession session, IQueryable<T> linqSource)
{
_linq = linqSource;
_transaction = session.BeginTransaction();
}
private readonly IQueryable<T> _linq;
private readonly ITransaction _transaction;
public IQueryable<T> Linq
{
get { return _linq; }
}
public void Dispose()
{
_transaction.Commit();
}
}
I could then wrap my linq statements up in a using block
using (var linq = Linq())
{
var versions = from t in linq.Linq
where t.BaseName == BaseName
orderby t.Version descending
select t.Version;
return versions.Take(1).SingleOrDefault();
}
and even if returning data from the middle of it, the transaction commit is still called. No more implicit transactions. Obviously this example is for NHibernate, but it should work similarly for other things.
I'm pretty sure you can ingnore this warning.
Can you see the transaction in NHProf?
http://groups.google.com/group/nhprof/browse_thread/thread/fbc97d3286ad783b