6

式(System.Linq.Expressions)のシリアル化と逆シリアル化が可能なJSON.NET用のJsonConverterの作成に取り組んでいます。作業の最後の5%程度まで下がっていますが、逆シリアル化された式から生成されたLINQ-to-SQLクエリを実行できないという問題があります。

式は次のとおりです。

Expression<Func<TestQuerySource, Bundle>> expression = db => (
    from b in db.Bundles
    join bi in db.BundleItems on b.ID equals bi.BundleID
    join p in db.Products on bi.ProductID equals p.ID
    group p by b).First().Key;

これは、LINQ-to-SQLでの非常に単純なグループ化クエリです。TestQuerySourceの実装ですSystem.Data.Linq.DataContextBundle、、、はすべて、およびその他のマッピング属性で装飾されたLINQ-to-SQLエンティティBundleItemです。対応するdatacontextプロパティは、すべて通常どおりのプロパティです。言い換えれば、ここで見事に注目すべきことは何もありません。ProductTableAttributeTable<T>

ただし、式が逆シリアル化された後にクエリを実行しようとすると、次のエラーが発生します。

System.Reflection.TargetInvocationException:
Exception has been thrown by the target of an invocation. --->
    System.NotSupportedException: The member '<>f__AnonymousType0`2[Bundle,BundleItem].bi' has no supported translation to SQL.

これは、式が実行していることをLINQ-to-SQLクエリプロバイダーがSQLに変換できないことを意味することを理解しています。これは、joinステートメントの一部のように、クエリの一部として匿名型を作成することと関係があるようです。この仮定は、元の式と逆シリアル化された式の文字列表現を比較することでサポートされます。

オリジナル(動作中):

{db => db.Bundles
.Join(db.BundleItems,
    b => b.ID,
    bi => bi.BundleID,
    (b, bi) => new <>f__AnonymousType0`2(b = b, bi = bi))
.Join(db.Products,
    <>h__TransparentIdentifier0 => <>h__TransparentIdentifier0.bi.ProductID,
    p => p.ID,
    (<>h__TransparentIdentifier0, p) =>
        new <>f__AnonymousType1`2(<>h__TransparentIdentifier0 = <>h__TransparentIdentifier0, p = p))
.GroupBy(<>h__TransparentIdentifier1 =>
    <>h__TransparentIdentifier1.<>h__TransparentIdentifier0.b,
    <>h__TransparentIdentifier1 => <>h__TransparentIdentifier1.p)
.First().Key}

デシリアライズ(壊れた):

{db => db.Bundles
.Join(db.BundleItems,
    b => b.ID,
    bi => bi.BundleID,
    (b, bi) => new <>f__AnonymousType0`2(b, bi))
.Join(db.Products,
    <>h__TransparentIdentifier0 => <>h__TransparentIdentifier0.bi.ProductID,
    p => p.ID,
    (<>h__TransparentIdentifier0, p) => new <>f__AnonymousType1`2(<>h__TransparentIdentifier0, p))
.GroupBy(<>h__TransparentIdentifier1 =>
    <>h__TransparentIdentifier1.<>h__TransparentIdentifier0.b,
    <>h__TransparentIdentifier1 => <>h__TransparentIdentifier1.p)
.First().Key}

この問題は、匿名型の非プリミティブ型のプロパティにアクセスする必要がある場合に発生するようです。この場合、biプロパティにアクセスするためにプロパティにアクセスしてい BundleItemますProductID

私が理解できないのは、違いが何であるかということです-元の式でプロパティにアクセスするのはうまくいくのに、逆シリアル化された式ではうまくいかないのはなぜですか。

この問題は、シリアル化中に匿名タイプが失われるという何らかの情報に関係していると思いますが、どこでそれを見つけるか、さらには何を探すべきかさえわかりません。


その他の例:

このような単純な式がうまく機能することは注目に値します。

Expression<Func<TestQuerySource, Category>> expression = db => db.Categories.First();

(参加せずに)グループ化を行うことも同様に機能します。

Expression<Func<TestQuerySource, Int32>> expression = db => db.Categories.GroupBy(c => c.ID).First().Key;

単純な結合は機能します:

Expression<Func<TestQuerySource, Product>> expression = db => (
    from bi in db.BundleItems
    join p in db.Products on bi.ProductID equals p.ID
    select p).First();

匿名タイプの選択は機能します:

Expression<Func<TestQuerySource, dynamic>> expression = db => (
    from bi in db.BundleItems
    join p in db.Products on bi.ProductID equals p.ID
    select new { a = bi, b = p }).First();

最後の例の文字列表現は次のとおりです。

オリジナル:

{db => db.BundleItems
.Join(db.Products,
    bi => bi.ProductID,
    p => p.ID,
    (bi, p) => new <>f__AnonymousType0`2(a = bi, b = p))
.First()}

デシリアライズ:

{db => db.BundleItems
.Join(db.Products,
    bi => bi.ProductID,
    p => p.ID,
   (bi, p) => new <>f__AnonymousType0`2(bi, p))
.First()}
4

1 に答える 1

2

違いは、実際の例では匿名型がプロパティを使用して構築され、壊れた場合はコンストラクターを使用してインスタンス化されることだと思います。

L2Sは、クエリの変換中に、プロパティに特定の値を割り当てると、プロパティはその値だけを返すと想定します。

L2Sは、abcという名前のctorパラメーターがAbcというプロパティを初期化するとは想定していません。ここでの考え方は、プロパティが値を格納するだけで、ctorは何でもできるということです。

匿名型はカスタムDTOクラスと同じであることに注意してください(文字通り!L2Sはそれらを区別できません)。

あなたの例では、a)匿名型(動作)を使用していないb)最終射影でのみctorを使用している(動作-すべてが最終射影として機能し、任意のメソッド呼び出しでも機能します。L2Sは素晴らしいです。)またはc)使用していますクエリのSQL部分のコンストラクター(壊れています)。これは私の理論を裏付けています。

これを試して:

var query1 = someTable.Select(x => new CustomDTO(x.SomeString)).Where(x => x.SomeString != null).ToList();
var query2 = someTable.Select(x => new CustomDTO() { SomeString = x.SomeString }).Where(x => x.SomeString != null).ToList();

2つ目は機能しますが、1つ目は機能しません。


(ダニエルからの更新)

Expression.Newデシリアライズされた式を再構築するときは、コンストラクターを介してプロパティを設定する必要がある場合の正しいオーバーロードを使用してください。使用する正しいオーバーロードはまたはExpression.New(ConstructorInfo, IEnumerable<Expression>, IEnumerable<MemberInfo>)ですExpression.New(ConstructorInfo, IEnumerable<Expression>, MemberInfo[])。他のオーバーロードの1つが使用されている場合、引数はプロパティに割り当てられるのではなく、コンストラクターにのみ渡されます。

于 2012-04-05T22:43:50.040 に答える