Problem with using Join operator in Lambda Expression and Expression Tree - linq

I write this method:
public List<TResult2> SelectAndJoin<TOuter, TInner, TKey, TResult, TResult2>(IEnumerable<TInner> inner,
System.Linq.Expressions.Expression<Func<Regions, TKey>> outerKeySelector,
System.Linq.Expressions.Expression<Func<TInner, TKey>> innerKeySelector,
System.Linq.Expressions.Expression<Func<Regions, TInner, TResult>> resultSelector,
Func<Regions, TResult2> selector)
{
using (RepositoryDataContext = new DataClasses1DataContext())
{
return RepositoryDataContext.Regions.Join(inner, outerKeySelector, innerKeySelector, resultSelector).AsEnumerable<TResult2>().Select<Regions, TResult2>(selector).ToList<TResult2>();
}
}
but the expression follow return has this Error:
'System.Linq.IQueryable' does not contain a definition for 'AsEnumerable' and the best extension method overload 'System.Linq.Enumerable.AsEnumerable(System.Collections.Generic.IEnumerable)' has some invalid arguments
How I can get rid of this error?
is this code standard?
thanks

You're calling AsEnumerable<TResult2> on enumerable which will be of type IQueryable<TResult>. You should call AsEnumerable<TResult> or you can even omit generic parameter and call AsEnumerable()
Also your next select will not work for the same reason - you are providing wrong types for generics.

I might be missing the point... but:
IQueryable inherits from IEnumerable so I think that AsEnumerable() is unnecessary in this code.
If you really do need/want to execute the query before doing the Select, then you could use ToList() instead of AsEnumerable() - this would be clearer
I'm also wondering if you are including using System.Linq in your .cs file - as AsEnumerable() is an extension method within System.Linq

Related

nHibernate HqlTreeBuilder to implement Linq methods for HierarchyId

I am looking at implementing a Hierarchy data structure in SQL Server using HierarchyId, and I need to add extension methods that can be used, via Linq, to use the HierarchyId methods exposed in TSQL. Now I have all the code for connecting a Linq method to NHibernate via a HqlGenerator I just can't find the right code to build the SQL I need.
So for example, for the blow Linq...
session.Query<Person>().Where(x=>x.Hierarchy.IsDescendantOf('/1/3/'))
I want to end up with SQL that looks something like this...
SELECT people0_.ObjectId, people0_.Name, people0_.Hierarchy
FROM People people0_
WHERE people0_.Hierarchy.IsDescendantOf('/1/3/') = 1
My problem is I can't figure out the HqlTreeBuilder code to implement in my BaseHqlGeneratorForMethod class to accomplish it because I need to get the IsDescendantOf method to be a child method of the column, meaning I need to combine the expression representing the Hierarchy Column to appear immediately before my method call with the dot in between.
I THOUGHT this would work, but it doesn't. Any suggestions?
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
return treeBuilder.Equality(
treeBuilder.Dot(visitor.Visit(arguments[0]).AsExpression(), treeBuilder.MethodCall("IsDescendantOf", new[] {visitor.Visit(arguments[1]).AsExpression()})),
treeBuilder.Constant(1)
);
}
I ended up doing it this way...
My code implementation
public static bool IsDescendantOf(this string childHierarchy, string parentHierarchy)
{
//In most cases this will be translated to the SQL implementation by LINQ, but on the off chance it's used on an in memory collection, the simplest implementation
//Is to verify that the child hierarchy starts with the hierarchy of the parent.
//for example....
// "/11/534/2134/".StartsWith("/11/534/") //would be TRUE
return childHierarchy.StartsWith(parentHierarchy);
}
The HqlGenerator
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
return treeBuilder.BooleanMethodCall("_IsDescendantOf", new[] { visitor.Visit(arguments[0]).AsExpression(), visitor.Visit(arguments[1]).AsExpression() });
}
And then where you configure nHibernate I have this line
cfg.AddSqlFunction("_IsDescendantOf", new NHibernate.Dialect.Function.SQLFunctionTemplate(NHibernate.NHibernateUtil.Boolean, "?1.IsDescendantOf(?2) = 1"));
Where cfg is an instance of your NHibernate.Cfg.Configuration class

How can I make LINQ Lambda expressions fail gracefully like XPath?

More a general question, but how can I write LINQ Lambda expressions such that they will return a default string or simply an empty string if the LINQ expression fails or returns nothing. In XSLT XPath if a match fails then one just got nothing, and the application did not crash whereas in LINQ one seems to get exceptions.
I use First() and have tried FirstOrDefault().
So example queries may be:
Customers.First(c=>c.id==CustId).Tasks.ToList();
or
Customers.Where(c=>c.id==CustId).ToList();
or
Model.myCustomers.Where(c=>c.id==CustId);
etc.
Whatever the query, if it returns no records or null, then is there a general approach to ensure the query fails gracefully?
Thanks.
There isn't anything elegant built into C# for propagating nulls when you access properties. You could create your own extension methods:
public static class Extensions
{
public static TValue SafeGet<TObject, TValue>(
this TObject obj,
Func<TObject, TValue> propertyAccessor)
{
return obj == null ? default(TValue) : propertyAccessor(obj);
}
public static IEnumerable<T> OrEmpty<T>(this IEnumerable<T> collection)
{
return collection ?? Enumerable.Empty<T>();
}
}
Used like this:
Customers.FirstOrDefault(c => c.id==CustId).SafeGet(c => c.Tasks).OrEmpty().ToList();
Customers.First(c=>c.id==CustId) will crash if there is no matching record.
There are few ways you can try to find it, if you use FirstOrDefault that'll return NULL if no match is found and you can check for NULL.
Or, you can use the .Any syntax which checks if you have any record and returns boolean.
The only query I would expect to throw an exception would be the first one (assuming that Customers is a valid collection and not null itself):
Customers.First(c=>c.id==CustId).Tasks.ToList();
This will throw an exception if there is no customer with an id of CustId (you have some casing issues with your property and variable names).
If you don't wish to throw an exception on no match, then use FirstOrDefault as you mention, and do a null check, e.g:
var customer = Customers.FirstOrDefault(c => c.id == CustId);
if (customer == null)
{
// deal with no match
return;
}
var taskList = customer.Tasks.ToList();

NHibernate: Using a LINQ expression with IQueryOver

I'm trying to use the IQueryOver interface of a NHibernate session object together with a LINQ expression as a criteria for selecting records in a static class. The LINQ expressions are defined in a mapping class as Expression<Func<T, object>> to get a value for an object T:
public void SearchParameter(Expression<Func<T, object>>)
These parameters get added by extending the mapping class:
public MyMapping : FindMap<MyNHibernateMappedObject>
{
public MyMapping()
{
this.SearchParameter(x => x.SomeColumn);
}
}
My Find class defines static methods for getting the previous and next record of the same type on the time axis. Each search parameter has to be identical in both records.
The Find class gets the search parameters from the mapping configuration and compiles the expressions with .Compile(). So I have the GetQueryWithSearchParameters method:
private static Func<T, object> searchParameter;
...
public static IQueryOver<T, T> GetQueryWithSearchParameters(ISession session, T current)
{
var query = session.QueryOver<T>()
.Where(x => searchParameter(x) == searchParameter(current));
return query;
}
However when building the query, I get the following exception:
System.InvalidOperationException: variable 'x' of type MyNHibernateMappedObject' referenced from scope '', but it is not defined
I don't know exactly what is going on here, but I suspect that x is not available in the delegate somehow. What am I doing wrong here?
session.QueryOver().Where(...) takes an expression, so it's going to try evaluate your expression and translate it to a query - ie. it will try to convert searchParameter(x) == searchParameter(current) into a SQL query, which it won't know how to do.
To get this to work you will need to construct the expression in code (not using a lambda expression). However I think that this is going to be a painful exercise and I think you will find it much easier to build a Criterion and add that to the QueryOver.

How to convert GetFuncType using LINQBridge

I have a client using .net and I am using LINQBride with some existing code that uses LINQ. I don't really know much about linq but am wondering if there is a quick conversion using LINQBridge for this expression
DynamicMethod.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type));
Thanks
LinqBridge only implements Linq to Objects, it doesn't support expressions. But you can achieve something similar using reflection:
static Type GetFuncType(params Type[] typeArgs)
{
string typeName = "System.Func`" + typeArgs.Length;
Type genericTypeDef = typeof(Func<>).Assembly.GetType(typeName); // Func<,...,>
return genericTypeDef.MakeGenericType(typeArgs); // Func<TArg1, ..., TResult>
}

Lambda Func delegate using interface

I have a class implementing an interface, and I need to return a Queryable<> list of that interface, given a certain Where predicate :
public interface ISomeInterface
{
int ID{get;}
IQueryable<ISomeInterface> GetWhere(Func<ISomeInterface,bool> predicate);
}
public class SomeClass : ISomeInterface
{
public static IQueryable<SomeClass> AVeryBigList;
public int ID {get;set;}
public IQueryable<ISomeInterface> GetWhere(Func<ISomeInterface,bool> predicate)
{
return (from m in AVeryBigList select m).Where(predicate);
}
}
problem is , this won't even compile, as the predicate won't match.
I've attempted so far:
return (from m in AVeryBigList select m as ISomeInterface)
.Where(predicate);
This will compile, but will fail at runtime, saying that it can't instantiate an interface
Second attempt:
return (from m in AVeryBigList select m)
.Cast<ISomeInterface>
.Where(predicate);
This will fail with a more enigmatic error: Unable to cast object of type 'System.Linq.Expressions.MethodCallExpression' to type 'SubSonic.Linq.Structure.ProjectionExpression'.
Edit:
The answer from wcoenen works as it should. Problem now appears when my AVeryBigList is provided by SubSonic 3.0. I get an exception thrown from within SubSonic when executing a query with Cast<>:
Unable to cast object of type 'System.Linq.Expressions.MethodCallExpression' to type 'SubSonic.Linq.Structure.ProjectionExpression'.
SubSonic.Linq.Structure.DbQueryProvider.Translate(Expression expression) at SubSonic.Core\Linq\Structure\DbQueryProvider.cs:line 203
Should I understand that SubSonic's Linq does not support Cast<> or is this a bug in SubSonic?
The Where extension methods for IEnumerable indeed take a System.Func, which is how you are trying to pass the predicate here.
But you're working with IQueryable, not IEnumerable. The Where extension methods for IQueryable take a System.Linq.Expressions.Expression, not a System.Func. Change the type of the predicate argument like this:
IQueryable<ISomeInterface> GetWhere(Expression<Func<SomeClass, bool>> predicate)
{
return AVeryBigList.Where(predicate).Cast<ISomeInterface>();
}
Alternatively, you could keep the original function declaration and pass the predicate to the Where method as x => predicate(x), but that would sabotage the ability of the IQueryable implementation to analyze the expression and optimize the query. (That's exactly what Subsonic does by the way; it analyzes the expression tree to generate a SQL statement.)
Also, you'll be glad to hear that the .Cast<ISomeInterface>() is no longer necessary in .NET 4.0 because of the new support for covariance.

Resources