2013-05-13 14 views
16

私たちはビジネスソリューションでORMを選択し、すべてをデカップリングし、ninjectを使って素敵なコンポジションルートを構築したn層ファッションで構成しています。Entity Framework 5クエリで間違ったデータ型

最近では、パーティション分割を使用するデータベースを構築しており、DATE列にいくつかの重要なインデックスがあります。

列はSql Server 2008で正しく宣言されています。また、HasColumnType("Date")命令を使用して、EFマッピングに正しいデータ型を追加しました。

エンティティへのLINQを通じてテーブルを照会するときに型がパラメータと一致するように、それでも、我々はタイプDateTime2と偶数列で作成された上で日付をフィルタリングパラメータはクエリでDateTime2にキャストされています。

この動作にはいくつかの問題があります。まず、EFエンジンにデータベースの列がDATEだと伝えたら、なぜそれをDateTime2にキャストすべきですか?

第2に、このキャストはデータベースにインデックスを無視させるため、パーティション分割を使用しないようにしています。私たちはフィジカルパーティショニングごとに1年間を持っています。もし私が日付範囲を聞いたら、2013年3月に2013年2月にスキャンが1つの物理パーティションでしか起こらないと言いましょう。正しいデータ型DATEを手動で使用すると正しく動作しますが、キャストをDateTime2にすると、すべてのパーティションがスキャンされ、パフォーマンスが大幅に低下します。

Microsoft SQL ServerでMicrosoft ORMがうまく動作しないということは、むしろ愚かなので、私は何かを逃していると思います。

EFがどのように正しいデータ型をクエリで使用しているかに関するドキュメントが見つかりませんでしたので、ここで質問しています。どんな助けもありがとう。

ありがとうございました。

答えて

4

これはEntity Frameworkで可能ではないと私は考えています。 This requested enhancementはおそらくあなたが必要とすることをするでしょう。 This MSDN pageは、SQL Serverの種類とCLRの種類の間のマッピングを示します。 dateがサポートされており、DateTimeにマップされていますが、複数のSQLタイプが同じCLRタイプにマップされているため、EFは明らかに1つのSQLタイプをの好ましいのCLRタイプとして選択しています。

選択コードをストアドプロシージャにラップできますか?もしそうなら、これは合理的な解決策に見えるでしょう。 DbSet{T}.SqlQueryを使用して、spを実行するオブジェクトをマテリアライズすることができます。

コードサンプル

以下のショートコンソールアプリケーションがその概念を示しています。関連エンティティがどのように遅延ロードに成功したかに注目してください。

using System; 
using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.ComponentModel.DataAnnotations; 
using System.ComponentModel.DataAnnotations.Schema; 
using System.Data.Entity; 
using System.Data.SqlClient; 
using System.Linq; 

namespace ConsoleApplication1 
{ 
    [Table("MyEntity")]  
    public class MyEntity 
    { 
     private Collection<MyRelatedEntity> relatedEntities; 

     [Key] 
     public virtual int MyEntityId { get; set; } 

     [DataType(DataType.Date)] 
     public virtual DateTime MyDate { get; set; } 

     [InverseProperty("MyEntity")] 
     public virtual ICollection<MyRelatedEntity> RelatedEntities 
     { 
      get 
      { 
       if (this.relatedEntities == null) 
       { 
        this.relatedEntities = new Collection<MyRelatedEntity>(); 
       } 

       return this.relatedEntities; 
      } 
     } 

     public override string ToString() 
     { 
      return string.Format("Date: {0}; Related: {1}", this.MyDate, string.Join(", ", this.RelatedEntities.Select(q => q.SomeString).ToArray())); 
     } 
    } 

    public class MyRelatedEntity 
    { 
     [Key] 
     public virtual int MyRelatedEntityId { get; set; } 

     public virtual int MyEntityId { get; set; } 

     [ForeignKey("MyEntityId")] 
     public virtual MyEntity MyEntity { get; set; } 

     public virtual string SomeString { get;set;} 
    } 

    public class MyContext : DbContext 
    { 
     public DbSet<MyEntity> MyEntities 
     { 
      get { return this.Set<MyEntity>(); } 
     } 
    } 

    class Program 
    { 
     const string SqlQuery = @"DECLARE @date date; SET @date = @dateIn; SELECT * FROM MyEntity WHERE MyDate > @date"; 

     static void Main(string[] args) 
     { 
      Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>()); 

      using (MyContext context = new MyContext()) 
      { 
       context.MyEntities.Add(new MyEntity 
        { 
         MyDate = DateTime.Today.AddDays(-2), 
         RelatedEntities = 
         { 
          new MyRelatedEntity { SomeString = "Fish" }, 
          new MyRelatedEntity { SomeString = "Haddock" } 
         } 
        }); 

       context.MyEntities.Add(new MyEntity 
       { 
        MyDate = DateTime.Today.AddDays(1), 
        RelatedEntities = 
         { 
          new MyRelatedEntity { SomeString = "Sheep" }, 
          new MyRelatedEntity { SomeString = "Cow" } 
         } 
       }); 

       context.SaveChanges(); 
      } 

      using (MyContext context = new MyContext()) 
      { 
       IEnumerable<MyEntity> matches = context.MyEntities.SqlQuery(
        SqlQuery, 
        new SqlParameter("@dateIn", DateTime.Today)).ToList(); 

       // The implicit ToString method call here invokes lazy-loading of the related entities. 
       Console.WriteLine("Count: {0}; First: {1}.", matches.Count(), matches.First().ToString()); 
      } 

      Console.Read(); 
     } 
    } 
} 
+0

私は大量のナビゲーションプロパティで遅延読み込みを使用しています。私は熱心にすべてを読み込み、手作業でオブジェクトグラフを作成する必要があります。これは非常に複雑で深いので、オプションではありません。たぶんNHibernateのような別のORMに移動するのが正しい選択でしょう。 –

+0

@MatteoMosca - 私は問題を誤解していない限り、実際にはそうは思わない。 EFでは、sp *の結果を*添付されたエンティティ(MSDNによれば "コンテキストによって追跡される")として実現することができるので、遅延読み込みが機能するはずです。できないことは、このメソッドを使って* eager * loadingをサポートすることです。 – Olly

+0

これは興味深いかもしれません。私はこの機能が欠けていたと思う。私はできるだけ早くそれを見ていきます。 –

2

解決策はありません。 datetime2(7)以外のSQLクエリでパラメータ型を使用した.NETのDateTimeパラメータを含むLINQ-to-Entitesクエリは見たことがありません。私はあなたがそれを取り除くことができるとは思わない。

のプロパティSomeNumberを持つエンティティがあるとします。 SomeNumber8以上である

....Where(e => e.SomeNumber >= 7.3).... 

おそらくすべてのエンティティ:あなたはこのようなクエリのためにどのような結果を期待します。 (浮動小数点10進)パラメータ7.3がタイプintにキャストされる場合は、7.37を丸める方法(間違った結果につながる)または8にする必要がありますか?私のクエリは>=と言い、DBの型は整数であることを知っているので、8への丸めが正しい必要があります。 <=を使用する場合は、7への丸めが正しい必要があります。 ==を使用する場合は、まったく丸めてはいけません。結果が空でなければならないことを知っています。このWhere句をfalseに直接変換することができます。そして!=~true。しかし、7.0のパラメータは特殊なケースです。等....あなたが最初の場所でintパラメータ(7または8)を使用して、欲しいものをクライアント側で決定します

さて、この例のジレンマは、簡単な解決策を持っています。

DateTimeのソリューションは、.NETにDateタイプがないため、単純ではありません。 DateTimeパラメータを持つクエリは、常にフォーム...

DateTime dateTime = new DateTime(2013, 5, 13, 10, 30, 0); 
....Where(e => e.SomeDateTime >= dateTime).... 

を持つことになります...とSomeDateTimeは、SQL Serverでdateとして格納されている場合は、再び丸めジレンマを持っています。 2013.05.13または2013.05.14にキャストする必要がありますか?上記のクエリの場合、クライアントは14日以降のすべてのエンティティを確実に期待します。

さて、私のDateTimeパラメータの時間部分が真夜中の場合は、日付部分にキャストしてください。 >=を翌日などにキャストして使用すると、いつでもdatetime2(7)にキャストできます。クエリの結果は常に正しく、(.NET)クライアントが期待するとおりです。正しいです...しかし、恐らく最適でないインデックスの使用法で。

+0

私はあなたの最初の例が問題でなければならない理由を理解していません。 SQL Serverでは既に、整数以外の値を使用して整数列をフィルタリングできます。このスクリプトを考えてみましょう:https://gist.github。com/kappa7194/5574037フロートを使用して 'MyColumn'をフィルタリングすることはできますが、SQL Serverはそのカラムをフロートにキャストしません:https://gist.github.com/kappa7194/5574040 Entity Frameworkは関係していますか? – Albireo

+0

あなたの説明ははっきりしていますが、まだ私は何かが欠落していると思います。 SQLにDate型がある場合、EFは.Net DateTimeオブジェクトの時間部分を無視できます。真夜中の時刻にDateTimeを返す.Dateプロパティもあります。おそらく、私たちのデータベースで.Netに準拠しているタイプのみを使用するべきであることを示唆していますか?それはかなり制限されているようです。 –

+0

これはより適切な例です:https://gist.github.com/kappa7194/5574997ここでSQL Serverは指定した 'DATETIME'値' GT'を検索する 'Clustered Index Seek'を実行します。私はEntity Frameworkを単純にその値を基になるデータベースに渡し(サポートしている場合)、それを実行させます。 – Albireo

4

.NETとSQLサーバーのDateTime型の範囲が異なります。

.NETのDateTimeの範囲である:0000-JAN-01から9999 - 12月 - 31 SQLのDateTimeの範囲は次のとおりです。1900-JAN-01、2079 - 6月 - 06

範囲を一致させるには、EFあなたを変換します.NET DateTimeとSQLサーバーのDateTime2タイプは.NET DateTimeの範囲と同じ範囲です。

日付のプロパティが割り当てられておらず、EF経由でSQLサーバーに渡されたときにのみ問題が発生すると思います。日付に特定の値が割り当てられていない場合は、既定でDateTime.Minが0000-Jan-01で、DateTime2に変換されます。

DateTimeプロパティをnullable - > DateTimeにすることができますか?または、SQL DateTime範囲を満たすようにDateTime.Minを変換するヘルパーを作成します。

うまくいけば、これが役に立ちます。

+1

実際、そうではありません。私たちの問題の1つの例は、検索範囲として2つの日付を使用する検索に関連しています。 dbの列は、DateTimeまたはDateTime2ではありません。私は2つの有効な.net DateTime値を完全に範囲内に送ります(例えば、2013年3月1日、21013年1月)。変換はまだ起こります。 –

関連する問題