2016-06-11 18 views
4

クエリからエンベロープのバックを取得しようとしています。エンベロープは次のように定義されます。f#:LINQ to Entitiesでパラメータのないコンストラクタと初期化子のみがサポートされています

[<CLIMutable>] 
type Envelope<'T> = { 
    Id : Guid 
    StreamId: Guid 
    Created : DateTimeOffset 
    Item : 'T } 

MyLibAAS.DataStore.MyLibAASDbContextは、C#で書かれたEF DbContextです。次のように私はF#でそれを拡張すると、私はエラーを取得する:私はイベントを返し、事実の後封筒にそれをマップする場合Only parameterless constructors and initializers are supported in LINQ to Entities.

type MyLibAAS.DataStore.MyLibAASDbContext with 
    member this.GetEvents streamId = 
     query { 
      for event in this.Events do 
      where (event.StreamId = streamId) 
      select { 
       Id = event.Id; 
       StreamId = streamId; 
       Created = event.Timestamp; 
       Item = (JsonConvert.DeserializeObject<QuestionnaireEvent> event.Payload) 
      } 
     } 

、それが正常に動作します。

type MyLibAAS.DataStore.MyLibAASDbContext with 
    member this.GetEvents streamId = 
     query { 
      for event in this.Events do 
      where (event.StreamId = streamId) 
      select event 
     } |> Seq.map (fun event -> 
       { 
        Id = event.Id 
        StreamId = streamId 
        Created = event.Timestamp 
        Item = (JsonConvert.DeserializeObject<QuestionnaireEvent> event.Payload) 
       } 
      ) 

なぜこれが違いますか?エンベロープタイプはEFタイプではありません。

答えて

5

F#のレコードが
のF#レコードは読み取り専用のプロパティとパラメータ(プラスいくつかのインターフェイス)など、すべてのフィールドの値を取るコンストラクタと.NETのクラスにコンパイルされますどのように動作しますか。次のように
は、例えば、あなたの記録はおおよそC#で表現することになります。

public class Envelope<T> : IComparable<Envelope<T>>, IEquatable<Envelope<T>>, ... 
{ 
    public Guid Id { get; private set; } 
    public Guid StreamId { get; private set; } 
    public DateTimeOffset Created { get; private set; } 
    public T Item { get; private set; } 

    public Envelope(Guid id, Guid streamId, DateTimeOffset created, T item) { 
     this.Id = id; 
     this.StreamId = streamId; 
     this.Created = created; 
     this.Item = item; 
    } 

    // Plus implementations of IComparable, IEquatable, etc. 
} 

あなたはF#のレコードを作成したい場合には、F#コンパイラは、すべてのフィールドの値を供給、このコンストラクタの呼び出しを発します。
たとえば、クエリのselect一部は次のようにC#でになります。

select new Envelope<QuestionnaireEvent>( 
    event.Id, streamId, event.Timestamp, 
    JsonConvert.DeserializeObject<QuestionnaireEvent>(event.Payload)) 

Entity Frameworkの制限
とてもEntity Frameworkのクエリにデフォルト以外のコンストラクタを呼び出すことはできませんということが起こります。正当な理由があります:それはそれを許可しなかった場合、あなたは、原則的に、このようなクエリを作成できます。

from e in ... 
let env = new Envelope<E>(e.Id, ...) 
where env.Id > 0 
select env 

Entity Frameworkは、それがあることを知っていないため、このクエリをコンパイルする方法を知っているだろうコンストラクタに渡されたe.Idの値は、プロパティenv.Idの値になります。これはF#レコードには常に当てはまりますが、他の.NETクラスには当てはまりません。
Entity Frameworkは、原則として、EnvelopeがF#レコードであることを認識し、コンストラクタ引数とレコードプロパティの間の接続の知識を適用できます。しかし、それはしません。残念ながら、Entity Frameworkの設計者はF#を有効なユースケースとは考えていませんでした。
(楽しみの事実:C#の匿名型が同じように動作し、EFは、彼らのために例外を作るん)

、この作業を行うために、この
を修正する方法を、あなたはとしてEnvelopeを宣言する必要がありますデフォルトのコンストラクタを持つ型。これを行うための唯一の方法はそれをクラスを作ることではなく、記録:

type Envelope<'T>() = 
    member val Id : Guid = Guid.Empty with get, set 
    member val StreamId : Guid = Guid.Empty with get, set 
    member val Created : DateTimeOffset = DateTimeOffset.MinValue with get, set 
    member val Item : 'T = Unchecked.defaultof<'T> with get, set 

し、プロパティの初期化構文を使用して作成します。

select Envelope<_>(Id = event.Id, StreamId = streamId, ...) 

なぜselectからSeq.map仕事を動かすん
Seq.map呼び出しはクエリ式の一部ではありません。それはIQueryableの一部として終わるわけではないので、Entity FrameworkによってSQLにコンパイルされることはありません。代わりに、EFはqueryの中にあるものだけをコンパイルし、SQL Serverからフェッチした結果のシーケンスを返します。その後、そのシーケンスにSeq.mapを適用します。
Seq.mapのコードは、CLRで実行され、SQLにコンパイルされないため、デフォルト以外のコンストラクタを含め、必要なものを呼び出すことができます。
この「修正」にはコストがかかります。必要なフィールドの代わりに、Eventエンティティ全体がDBからフェッチされ、マテリアライズされます。このエンティティが重い場合、これはパフォーマンスに影響を与える可能性があります。することはできませんJsonConvert.DeserializeObject方法:あなたはデフォルトコンストラクタ(上記で示唆したように)でEnvelopeタイプを作ることによって、問題を解決したとしても

もう一つ
に注意して、あなたはまだ次の問題をヒットしますSQLにコンパイルされるので、Entity Frameworkもそれに不満を持ちます。あなたがそれを行うべきである方法は、すべてのフィールドをCLR側にフェッチし、必要な非SQLコンパイル可能な変換を適用します。

2

LINQ to Entitiesを使用すると、query計算式で発生するすべてのものは、実際にはデータベースエンジン内で実行され、リモートサーバー上に存在する可能性があります。その外部のすべては、クライアント上の実行中のアプリケーションで実行されます。

最初のスニペットでは、エンティティフレームワークはEnvelope<'T>のコンストラクタの実行を拒否します。そのためには、そのためにサーバーのSQLコマンドに変換する必要があります。これは、コンストラクタに潜在的にあらゆる種類の副作用や.NETフレームワークコードが含まれている可能性があるため、保証できるものではありません。ユーザーの入力を要求したり、クライアントのハードディスクからファイルを読み込んだりすることができます。

、2番目のスニペットで、それはだから、それはが行うことができますその後、あなたの空想Envelope秒にSeq.mapピングそれらを課されているクライアント、に戻って独自のPOCO eventオブジェクトを送信してくださいすることができますEF完全な.NETフレームワークにアクセスできるようにクライアントマシン上で実行します。

補足:なぜパラメーターなしコンストラクターはいいですか?パラメータなしのコンストラクタでMsgBox()を呼び出すとどうなりますか? I クライアントは、クエリー結果を知らずにオブジェクトを構成し、シリアライズされた形式でサーバーに送信し、サーバーがクエリー結果でオブジェクトのプロパティを埋め込むだけで、パラメーターなしコンストラクターが機能すると考えています。

私は実際にそれをテストしていません。しかし、F#のレコードタイプにはパラメータがないコンストラクタはありません。そのため、あなたの場合は問題になります。

+0

なぜパラメーターなしコンストラクターが動作するかについてのあなたの理論は間違っています。 –

+0

私はあなたの(非常に徹底的な)答えを読んだが、それは私のためにその点をかなり解決しなかった。 'Foo()= do MsgBox(" foo ")'型を宣言し、そのパラメータのないコンストラクタをL2Eクエリ式の中で呼び出すと、どうなりますか? – piaste

+0

コンストラクタがクエリ内でどのように使用されるかによって異なります。コンストラクタを使用してクエリから返される値を構築する場合(たとえば、最後の 'select'文など)、返される行ごとにメッセージボックスが表示されます。コンストラクタを使用してクエリから返されない中間値を作成すると、メッセージボックスは表示されません。 –

関連する問題