1

この質問は、「式内にメソッド クラスを含める」という質問に触発されたものです。この質問は、「これに対する解決策を見つけるにはどうすればよいか」というだけではありません。その質問にはすでにその答えがあります。私の質問は、「キャプチャされた変数を複数回使用するが、単一の評価内で変数を繰り返しクエリしないa を作成するにはどうすればよいですかExpression」です。

時間の経過とともに変化する可能性がある、またはプルするのに非常にコストがかかるプロパティ値があるとしましょう。式クエリでそれを複数回使用する正しい方法は何でしょうか。

例で示しましょう

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};
        }
    }

   //...
}

上記のコードでは、エンティティ フレームワーク ソースが同じクエリによって 2 回クエリされますが、渡されたパラメーターの一部に対して 2 つの異なる値が必要です。クエリから生成されたSQLは次のとおりです

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Id] + @p__linq__0 AS [C1], 
@p__linq__1 AS [C2]
FROM [dbo].[Sources] AS [Extent1]

問題は@p__linq__0、プロパティ@p__linq__1への 2 つの後続の呼び出しからの 2 つの異なる値です。PropertyThatVaries

さまざまなプロパティをクエリに直接入れなくても同様の結果を得ることができますが、そうすると、後続のクエリで異なる値が得られません。

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.
}

SQL で次のような linq ステートメントを取得するにはどうすればよいでしょうか。

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Id] + @p__linq__0 AS [C1], 
@p__linq__0 AS [C2]
FROM [dbo].[Sources] AS [Extent1]

しかし、まだ から現在の値を取得していfoo.PropertyThatVariesますか?


これは、.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;
    }
}
4

1 に答える 1