Use a variable in a Linq expression - linq

I'm composing a Linq query where my object constructs a grid (table) down in my view.
filteredProducts =
filteredProducts.Take(take)
.Skip(pagesToSkip)
.OrderBy(w => w.ProductName)
.ToList();
As per the very common pattern; the grid can 'OrderBy' different columns depending on which column header is clicked. I need to replace the hardcoded 'ProductName' so that any of the possible column names can be included. The solution is probably based on the approach presented at this question but but my use-case is different enough that I haven't yet been able to implement anything. SO's auto-suggest isn't offering any other obvious which seems strange because surely this is a common problem.
I've tried the obvious:
var mySortColumn = "w.ProductId";
then:
.OrderBy(w => mySortColumn)
and get no compile or runtime complaints but no respect for the designated value either. Other variations I've tried will toss compiler errors right away.
This article suggests I should use a SWITCH/CASE construct to build a completely different query for each of my possible 'OrderBy' clauses. While I'm not typically one to turn up my nose at a hack but; Ick.
It's almost looks like we haven't evolved all that far from the 'build a string' days of dynamic SQL days.

The OrderBy method takes a Func<Type,int> delegate. This means you can do something like this:
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
var foos = new List<Foo>()
{
new Foo { A = 1, B = 2 },
new Foo { A = 2, B = 1 }
};
Func<Foo, int> a = f => f.A;
Func<Foo, int> b = f => f.B;
bool orderByA = false;
Display(foos.OrderBy(orderByA ? a : b));
}
private static void Display(IEnumerable<Foo> foos)
{
foos.ToList().ForEach(f => Console.WriteLine(f));
}
}
class Foo
{
public int A { get; set; }
public int B { get; set; }
public override string ToString()
{
return A + " " + B;
}
}
If you really want to be able to sort on a property name, you need to create some code that creates an Expression from your string.

Related

How to AND predicates in the WHERE clause in a LINQ to RavenDB query

I have the following model:
public class Clip {
public Guid ClipId { get; set; }
public IList<StateChange> StateChanges { get; set; }
}
public class StateChange {
public string OldState { get; set; }
public string NewState { get; set; }
public DateTime ChangedAt { get; set; }
}
And this is how a query raven:
var now = DateTime.UtcNow;
var since = now.AddSeconds(60);
string state = "some state of interest";
using (var session = docStore.OpenSession()) {
RavenQueryStatistics stats;
session.Query<Clip>()
.Statistics(out stats)
.Where(
p => p.StateChanges.Any(a => (since > a.ChangedAt && a.NewState == state))
&& !p.StateChanges.Any(a => (a.OldState == state && now > a.ChangedAt)))
.ToArray();
return stats.TotalResults;
}
I want to get the count for all Clip records that have a (StateChange.CreatedAt before since and NewState is "some state of interest") AND NOT have a (StateChange.CreatedAt before now and OldState is "some state of interest").
While the predicate used above works in linq to object, it doesn't seem to work in linq to raven (i.e. does not return the expected result). I suspect that this is because the expression && !.p.StateChanges.Any.... is never evaluated if the expression on the left-hand side evaluates to true. Is there a way to work around this?
It's not related to the evaluation of conditions. && works just fine.
The problem is that RavenDB doesn't properly handle queries that use .All(...) or !.Any(...). This is due to the way raven's dynamic index engine evaluates your linq statement. It wants to build a separate index entry for each of your StateChange entries, which won't work for operations that need to consider multiple related items, such as the different state changes.
There is an issue already logged for this here. It was closed in build 2151 to throw back a meaningful exception when you try to query in this way. Maybe at some future date they can reassess if there's some way to actually evaluate these types of queries properly instead.
Update
I've been thinking about your challenge, and another related one, and was able to come up with a new technique that will allow you to do this. It will require a static index and lucene query:
public class Clips_ByStateChange : AbstractIndexCreationTask<Clip>
{
public Clips_ByStateChange()
{
Map = clips =>
from clip in clips
select new {
OldState = clip.StateChanges
.Select(x => x.OldState + "|" + x.ChangedAt.ToString("o")),
NewState = clip.StateChanges
.Select(x => x.NewState + "|" + x.ChangedAt.ToString("o"))
};
}
}
var results = session.Advanced.LuceneQuery<Clip, Clips_ByStateChange>()
.Where(string.Format(
"NewState: {{{0}|* TO {0}|{1}}} AND -OldState: {{{0}|* TO {0}|{2}}}",
state, since.ToString("o"), now.ToString("o")));
Of course, you can still just take statistics on it if that's what you want.

How to rewrite LINQ Any() to make it suitable for .NET 3.5

I have the following EF class:
class Product
{
public int ProdId { get; set; }
public int ProdDesc { get; set; }
public int ProdKeywords { get; set; }
}
Now I have to implement a search function that looks at ProdDesc and ProdKeywords. The keywords are registered in a array and the collection of products in a IQueryable
string[] keywordsArray = new string[] {"kw1", "kw2", ..., "kwN"};
IQueryable<Product> products = repository.GetProducts();
To see if there are products matching the keywords I use the following LINQ:
var matchingProducts = products.Where(p => keywordsArray.Any(k => p.ProdDesc.Contains(k) ||
p.ProdKeywords.Contains(k));
which works like a charm in .NET 4.
The BIG problem is that I am forced to use this code in .NET 3.5 and I just discovered that Any and Contains (the LINQ method, not the one applied to strings) don't work in that framework. That's a real pain. The code is too big to rewrite everything and the deadline is too close.
I found this article really interesting but I can't make it work in my case. Anybody might help?
What's about:
static class Extension
{
public static bool Contains(this IEnumerable<object> source, object value)
{
foreach (object o in source)
if (o.Equals(value)) return true;
return false;
}
}
var mylist = keywordsArray.ToList();
matchingProducts = products.Where(p => mylist.Exists(k => p.ProdDesc.Contains(k) ||
p.ProdKeywords.Contains(k));
you could query first the any and store that in a enumerable and the check if the count is bigger then 0

How to assign a property value of an IQueryable<T>?

I'm using Entity Framework 4.1 Code First. In my entity, I have three date/time properties:
public class MyEntity
{
[Key]
public Id { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
[NotMapped]
public DateTime? QueryDate { get; set; }
// and some other fields, of course
}
In the database, I always have the From/To dates populated. I query against them using a simple where clause. But in the result set, I want to include the date I queried for. I need to persist this for some other business logic to work.
I'm working on an extension method to do this, but I'm running into problems:
public static IQueryable<T> WhereDateInRange<T>(this IQueryable<T> queryable, DateTime queryDate) where T : MyEntity
{
// this part works fine
var newQueryable = queryable.Where(e => e.FromDate <= queryDate &&
e.ToDate >= queryDate);
// in theory, this is what I want to do
newQueryable = newQueryable.Select(e =>
{
e.QueryDate = queryDate;
return e;
});
return newQueryable;
}
This doesn't work. It works if I use an IEnumerable, but I want to keep it as IQueryable so everything runs on the database side, and this extention method can still be used in any part of another query. When it's IQueryable, I get a compile error of the following:
A lambda expression with a statement body cannot be converted to an expression tree
If this was SQL, I would just do something like this:
SELECT *, #QueryDate as QueryDate
FROM MyEntities
WHERE #QueryDate BETWEEN FromDate AND ToDate
So the question is, how can I transform the expression tree I already have to include this extra property assignment? I have looked into IQueryable.Expression and IQueryable.Provider.CreateQuery - there's a solution in there somewhere. Maybe an assignment expression can be appended to the existing expression tree? I'm not familiar enough with the expression tree methods to figure this out. Any ideas?
Example Usage
To clarify, the goal is to be able to perform something like this:
var entity = dataContext.Set<MyEntity>()
.WhereDateInRange(DateTime.Now)
.FirstOrDefault();
And have the DateTime.Now persisited into the QueryDate of the resulting row, WITHOUT having more than one row returned from the database query. (With the IEnumerable solution, multiple rows are returned before FirstOrDefault picks the row we want.)
Another Idea
I could go ahead and map QueryDate like a real field, and set its DatabaseGeneratedOption to Computed. But then I would need some way to inject the "#QueryDate as QueryDate" into the SQL created by EF's select statements. Since it's computed, EF won't try to provide values during update or insert. So how could I go about injecting custom SQL into the select statements?
Ladislav is absolutely right. But since you obviously want the second part of your question to be answered, here is how you can use Assign. This won't work with EF, though.
using System;
using System.Linq;
using System.Linq.Expressions;
namespace SO5639951
{
static class Program
{
static void Main()
{
AdventureWorks2008Entities c = new AdventureWorks2008Entities();
var data = c.Addresses.Select(p => p);
ParameterExpression value = Expression.Parameter(typeof(Address), "value");
ParameterExpression result = Expression.Parameter(typeof(Address), "result");
BlockExpression block = Expression.Block(
new[] { result },
Expression.Assign(Expression.Property(value, "AddressLine1"), Expression.Constant("X")),
Expression.Assign(result, value)
);
LambdaExpression lambdaExpression = Expression.Lambda<Func<Address, Address>>(block, value);
MethodCallExpression methodCallExpression =
Expression.Call(
typeof(Queryable),
"Select",
new[]{ typeof(Address),typeof(Address) } ,
new[] { data.Expression, Expression.Quote(lambdaExpression) });
var data2 = data.Provider.CreateQuery<Address>(methodCallExpression);
string result1 = data.ToList()[0].AddressLine1;
string result2 = data2.ToList()[0].AddressLine1;
}
}
}
Update 1
Here is the same code after some tweaking. I got rid of the "Block" expression, that EF choked on in the code above, to demonstrate with absolute clarity that it's "Assign" expression that EF does not support. Note that Assign works in principle with generic Expression trees, it is EF provider that does not support Assign.
using System;
using System.Linq;
using System.Linq.Expressions;
namespace SO5639951
{
static class Program
{
static void Main()
{
AdventureWorks2008Entities c = new AdventureWorks2008Entities();
IQueryable<Address> originalData = c.Addresses.AsQueryable();
Type anonType = new { a = new Address(), b = "" }.GetType();
ParameterExpression assignParameter = Expression.Parameter(typeof(Address), "value");
var assignExpression = Expression.New(
anonType.GetConstructor(new[] { typeof(Address), typeof(string) }),
assignParameter,
Expression.Assign(Expression.Property(assignParameter, "AddressLine1"), Expression.Constant("X")));
LambdaExpression lambdaAssignExpression = Expression.Lambda(assignExpression, assignParameter);
var assignData = originalData.Provider.CreateQuery(CreateSelectMethodCall(originalData, lambdaAssignExpression));
ParameterExpression selectParameter = Expression.Parameter(anonType, "value");
var selectExpression = Expression.Property(selectParameter, "a");
LambdaExpression lambdaSelectExpression = Expression.Lambda(selectExpression, selectParameter);
IQueryable<Address> finalData = assignData.Provider.CreateQuery<Address>(CreateSelectMethodCall(assignData, lambdaSelectExpression));
string result = finalData.ToList()[0].AddressLine1;
}
static MethodCallExpression CreateSelectMethodCall(IQueryable query, LambdaExpression expression)
{
Type[] typeArgs = new[] { query.ElementType, expression.Body.Type };
return Expression.Call(
typeof(Queryable),
"Select",
typeArgs,
new[] { query.Expression, Expression.Quote(expression) });
}
}
}
No, I don't think there is a solution. It is true that you can modify expression tree but you will get exactly the same exception as you got with your linq query because that query actually is what you will build in expression tree. The problem is not in expression tree but in the mapping. EF can't map QueryData to the result. Moreover you are trying to do projection. Projection can't be done to mapped entity and anonymous type can't be returned from the method.
You can off course do the select you mentioned but simply you can't map it to your entity. You must create a new type for that:
var query = from x in context.MyData
where x.FromDate <= queryDate && x.ToDate >= queryDate
select new MyDateWrapper
{
MyData = x,
QueryDate = queryDate
};
Automapper has Queryable Extensions, i think it can resolve your needs.
You can use ProjectTo to calculate property on runtime.
Ef Core 2 set value to ignored property on runtime
http://docs.automapper.org/en/stable/Queryable-Extensions.html
Example configuration:
configuration.CreateMap(typeof(MyEntity), typeof(MyEntity))
.ForMember(nameof(Entity.QueryDate), opt.MapFrom(src => DateTime.Now));
Usage:
queryable.ProjectTo<MyEntity>();
Thank you for all of the valuable feedback. It sounds like the answer is "no - you can't do it that way".
So - I figured out a workaround. This is very specific to my implementation, but it does the trick.
public class MyEntity
{
private DateTime? _queryDate;
[ThreadStatic]
internal static DateTime TempQueryDate;
[NotMapped]
public DateTime? QueryDate
{
get
{
if (_queryDate == null)
_queryDate = TempQueryDate;
return _queryDate;
}
}
...
}
public static IQueryable<T> WhereDateInRange<T>(this IQueryable<T> queryable, DateTime queryDate) where T : MyEntity
{
MyEntity.TempQueryDate = queryDate;
return queryable.Where(e => e.FromDate <= queryDate && e.ToDate >= queryDate);
}
The magic is that I'm using a thread static field to cache the query date so it's available later in the same thread. The fact that I get it back in the QueryDate's getter is specific to my needs.
Obviously this isn't an EF or LINQ solution to the original question, but it does accomplish the same effect by removing it from that world.

Passing an expression to a method in NHibernate results in Object of type 'ConstantExpression' cannot be converted to type 'LambdaExpression'

This problem occurs in both NHibernate 2 and 3. I have a Class A that has a member set of class B. Querying the classes directly executes nicely. But when I pass one of the expressions involving class B into a method I get the following error:
System.ArgumentException: Object of type 'System.Linq.Expressions.ConstantExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
As far as I can see I am passing the exact same expression into the Any() method. But for some reason they are treated differently. I have done some debugging and it looks like in the first method, the expression is treated as an expression with NodeType 'Quote', while the same expression in the 2nd method seems to be treated as an expression with NodeType 'Constant'. The parent expression of the expression in the 2nd method has a NodeType 'MemberAccess'. So it looks like the expression tree is different in the different test methods. I just don't understand why and what to do to fix this.
Classes involvend:
public class A
{
public virtual int Id { get; set; }
public virtual ISet<B> DataFields { get; set; }
}
public class B
{
public virtual int Id { get; set; }
}
Sample test code:
[TestMethod]
public void TestMethod1()
{
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where<A>(a => a.DataFields
.Any(b => b.Id == 1));
Console.Write("Number of records is {0}", records.Count());
}
}
[TestMethod]
public void TestMethod2()
{
GetAsWhereB(b => b.Id == 1);
}
private void GetAsWhereB(Func<B, bool> where)
{
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where(a => a.DataFields
.Any(where));
Console.Write("Number of records is {0}", records.Count());
}
}
This is one problem:
private void GetAsWhereB(Func<B, bool> where)
That's taking a delegate - you want an expression tree otherwise NHibernate can't get involved. Try this:
private void GetAsWhereB(Expression<Func<B, bool>> where)
As an aside, your query is hard to read because of your use of whitespace. I would suggest that instead of:
var records = session.Query<A>().Where<A>(a => a.DataFields.
Any(b => b.Id == 1));
you make it clear that the "Any" call is on DataFields:
var records = session.Query<A>().Where<A>(a => a.DataFields
.Any(b => b.Id == 1));
I'd also suggest that you change the parameter name from "where" to something like "whereExpression" or "predicate". Some sort of noun, anyway :)
Not quite sure if this is the proper solution or not. The problem feels like a bug and my solution like a workaround. Nonetheless the following works for me, which boils down to creating a 'copy' of the given expression by using its body and parameter to construct a new expression.
private void GetAsWhereB(Func<B, bool> where)
{
Expression<Func<T, bool>> w = Expression.Lambda<Func<T, bool>>(where.Body, where.Parameters);
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where(a => a.DataFields
.Any(w));
Console.Write("Number of records is {0}", records.Count());
}
}

Linq and SubSonic - returning nested complex types

I'm new to SubSonic and reasonably new to LINQ as well, so I'm just trying to put a little app together.
I've got the templates all sorted and running okay, but I've run into a bit of trouble with this LINQ statement (simplified slightly, the real statement has some other joins but they don't affect this particular problem so I've removed them for brevity):
var addresses = from address in Database.Addresses.All()
select new Address()
{
MyNestedType = new NestedType()
{
Field1 = address.ADDR1
}
};
If I execute this statement I get the error Invalid cast from 'System.String' to 'NestedType'. when I try to enumerate the results.
I'm probably overlooking the obvious but I can't see anywhere that I request such a conversion.
Both Field1 and address.ADDR1 are strings.
Any ideas what I'm doing wrong?
Edit:
I've had another look at this and in an effort to provide more information, I've created a small, complete example using SimpleRepository and an SQLite database that demonstrates the issue. Using SimpleRepository the error I get is different (Sequence contains no elements) but the result is the same. Here's the complete code:
public class DatabaseAddress
{
public int Id { get; set; }
public string Address1 { get; set; }
}
public class Address
{
public NestedType MyNestedType;
}
public class NestedType
{
public string Field1 { get; set; }
}
static class Program
{
[STAThread]
static void Main()
{
var repo = new SimpleRepository("Db", SimpleRepositoryOptions.RunMigrations);
DatabaseAddress address1 = new DatabaseAddress();
address1.Address1 = "Test";
repo.Add(address1);
var all = repo.All<DatabaseAddress>();
var addresses = from address in repo.All<DatabaseAddress>()
select new Address { MyNestedType = new NestedType { Field1 = address.Address1 } };
}
}
In this example, all contains the object added to the database, but addresses returns "Sequence contains no elements".
If I use anonymous types instead of concrete types in the select statement it works.
There's obviously a gap in my knowledge here; any help appreciated.
Please see my question and answer here.
Here's how you can test if it is the same issue:
In that sample code you posted, change Field1 in your NestedType to be named Address1. Re-run your sample. If it works, same issue and the fix I answered with in the linked question should solve it for you.
You have to call ToList(), otherwise the SubSonic provider tries to do something with MyNestedType and it doesn't exist in the database.
var addresses = from address in repo.All<DatabaseAddress>().ToList()
select new Address { MyNestedType = new NestedType { Field1 = address.Address1 } };
Update: It also works if you call ToList afterwards, i.e.:
addresses.ToList().ForEach(address => Console.WriteLine("Address.MyNestedType.Field1 = {0}", address.MyNestedType.Field1));
I guess there is a bug in the SubSonic query provider, because it does work for anonymous types, as you mentioned.
Try this
var nestedTypes= from address in Database.Addresses.All()
select new NestedType()
{
Field1 = address.ADDR1
};

Resources