Reputation: 127603
This question was a inspired by the question: "Including a Method class inside an Expression". This question is not just "how can I find a solution to this" as that question already has that answer. My question is "How do I write a Expression
that uses a captured variable more than once but does not repeatedly query the variable within a single evaluation".
Lets say I have a property value that can vary over time or is very expensive to pull, what would be the correct way to use it multiple times in a Expression query.
Let me show by example
namespace Sandbox_Console
{
public class Program
{
static void Main(string[] args)
{
using (var ctx = new Context())
{
var selectExpression = GetSelect();
var query = ctx.Sources.Select(selectExpression);
var queryText = query.ToString();
var result1 = query.First();
var result2 = query.First();
var goodResult = (result1.Id != result2.Id && result1.Id == (result1.Prop - 1));
if(!goodResult)
throw new InvalidDataException();
}
}
static public Expression<Func<Source, Result>> GetSelect()
{
var foo = new Foo();
return source => new Result {Id = source.Id + foo.PropertyThatVaries, Prop = foo.PropertyThatVaries};
}
}
//...
}
In the above code the entity framework source is queried twice by the same query, but it should have two different values for some of the passed in parameters. Here is the sql that is generated from the query
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Id] + @p__linq__0 AS [C1],
@p__linq__1 AS [C2]
FROM [dbo].[Sources] AS [Extent1]
The problem is @p__linq__0
and @p__linq__1
are two different values from two subsequent calls to the PropertyThatVaries
property.
I can get a similar result by not directly putting in the varying property to the query, but if I do that I don't get different values on subsequent queries.
static public Expression<Func<Source, Result>> GetSelect()
{
var foo = new Foo();
var tmp = foo.PropertyThatVaries;
return source => new Result { Id = source.Id + tmp, Prop = tmp };
//Now fails the "result1.Id != result2.Id" test.
}
How would you go about getting a linq statement that looked like this in sql:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Id] + @p__linq__0 AS [C1],
@p__linq__0 AS [C2]
FROM [dbo].[Sources] AS [Extent1]
But still got the current value from foo.PropertyThatVaries
?
Here is a full complileable version of the test program, it was made in .NET 4.5
using System;
using System.Data.Entity;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
namespace Sandbox_Console
{
public class Program
{
static void Main(string[] args)
{
using (var ctx = new Context())
{
var selectExpression = GetSelect();
var query = ctx.Sources.Select(selectExpression);
var queryText = query.ToString();
var result1 = query.First();
var result2 = query.First();
var goodResult = (result1.Id != result2.Id && result1.Id == (result1.Prop + 1));
if(!goodResult)
throw new InvalidDataException();
}
}
static public Expression<Func<Source, Result>> GetSelect()
{
var foo = new Foo();
var tmp = foo.PropertyThatVaries;
return source => new Result { Id = source.Id + tmp, Prop = tmp };
//return source => new Result {Id = source.Id + foo.PropertyThatVaries, Prop = foo.PropertyThatVaries};
}
}
public class Context : DbContext
{
public Context()
{
Database.SetInitializer<Context>(new Init());
}
public DbSet<Source> Sources { get; set; }
}
public class Init : DropCreateDatabaseAlways<Context>
{
protected override void Seed(Context context)
{
base.Seed(context);
context.Sources.Add(new Source() { Id = 1 });
}
}
public class Source
{
public int Id { get; set; }
}
public class Result
{
public int Id { get; set; }
public int Prop { get; set; }
}
public class Foo
{
public Foo()
{
rnd = new Random();
}
public int PropertyThatVaries
{
get
{
//This could also be a "Expensive" get. Un-comment the next line to simulate.
//Thread.Sleep(1000);
return rnd.Next(1, 100000);
}
}
private Random rnd;
}
}
Upvotes: 1
Views: 282
Reputation: 244998
If you don't need to do this using a single Expression
, you could take advantage of let
:
IQueryable<Result> PerformSelect(IQueryable<Source> sources)
{
var foo = new Foo();
return from source in sources
let tmp = foo.PropertyThatVaries
select new Result { Id = source.Id + tmp, Prop = tmp };
}
I think this will do what you want.
Upvotes: 1