2016-10-28 17 views
2

以下に示すように、null可能なDateTime(DateTime?)を各モデル項目のユーザーに文字列として出力するMVCプロジェクトがあります(私のユーザーインターフェイスにはページングしかありません。少数のレコードに対してこの計算を実行します。つまり、この部分は大丈夫です。Entity Frameworkコードの検索文字列を使用してDateTimeを最初に検索する

foreach (DocumentEvent item in Model.items) 
    @(item?.TimeUtc?.ToString() ?? "N/A") 

検索機能を追加したいと思います。私は次のように探してみたが、各レコードを列挙、AsEnumerableは私のリストを実体化し、私はC#の世界では、今だから、これはパフォーマンスではありません。

代わり
using (var context = new ClientEventsContext()) 
    var items = context.Events.AsEnumerable().Where(x => { 
     (x?.TimeUtc?.ToString() ?? "N/A").Contains(model.search) 
    }); 

私は私のSQL Serverデータベースを利用したいです。私の現在のロジックと同じ結果をもたらすAsEnumerableなしで、上記のコードのSQL Serverに優しいクエリを作成するにはどうすればよいですか?

+0

だけAsEnumerableを脱いでのIQueryableレベルではあなたがすることはできません – Fals

+0

@Falsをクエリを実行し、それが発生しますエンティティフレームワークは '(x?.TimeUtc?.ToString()??" N/A ")。Contains(model.search)'をSQLクエリに変換できないため、例外が発生します。 – Alexandru

+1

一般的に、日付と時刻を照会するための範囲検索を使用し、それらをネイティブデータ形式にします。 'Contains'文字列検索はかなり役に立たないでしょう。私は '' 0 ''や '/' 'を検索するかもしれません。 –

答えて

2

は、あなたが構築して使用することができます方法ですLINQ to Entities M/d/yyyy h:mm:ss tt形式の日付から文字列への互換変換。クエリ内にそのモンスターを埋め込むのではなく、カスタムの「マーカー」メソッドを使用して、ExpressionVisitorを使用して実装をバインドします。このようにして、必要に応じて、クエリの可読性に影響を与えずにフォーマットを試して変更することができます(制御引数を追加することもできます)。

まず、実装:

public static class EFExtensions 
{ 
    public static string ToCustomDateFormat(this DateTime value) 
    { 
     // Should never happen 
     throw new InvalidOperationException(); 
    } 

    public static IQueryable<T> ApplyCustomDateFormat<T>(this IQueryable<T> source) 
    { 
     var expression = new CustomDateFormatBinder().Visit(source.Expression); 
     if (source.Expression == expression) return source; 
     return source.Provider.CreateQuery<T>(expression); 
    } 

    class CustomDateFormatBinder : ExpressionVisitor 
    { 
     protected override Expression VisitMethodCall(MethodCallExpression node) 
     { 
      if (node.Method.DeclaringType == typeof(EFExtensions) && node.Method.Name == "ToCustomDateFormat") 
      { 
       var date = Visit(node.Arguments[0]); 
       var year = DatePart(date, v => DbFunctions.Right("0000" + v.Year, 4)); 
       var month = DatePart(date, v => v.Month.ToString()); 
       var day = DatePart(date, v => v.Day.ToString()); 
       var hour = DatePart(date, v => (1 + (v.Hour + 11) % 12).ToString()); 
       var minute = DatePart(date, v => DbFunctions.Right("0" + v.Minute, 2)); 
       var second = DatePart(date, v => DbFunctions.Right("0" + v.Second, 2)); 
       var amPM = DatePart(date, v => v.Hour < 12 ? "AM" : "PM"); 
       var dateSeparator = Expression.Constant("/"); 
       var timeSeparator = Expression.Constant(":"); 
       var space = Expression.Constant(" "); 
       var result = Expression.Call(
        typeof(string).GetMethod("Concat", new Type[] { typeof(string[]) }), 
        Expression.NewArrayInit(typeof(string), 
         month, dateSeparator, day, dateSeparator, year, space, 
         hour, timeSeparator, minute, timeSeparator, second, space, amPM)); 
       return result;  
      } 
      return base.VisitMethodCall(node); 
     } 

     Expression DatePart(Expression date, Expression<Func<DateTime, string>> part) 
     { 
      var parameter = part.Parameters[0]; 
      parameterMap.Add(parameter, date); 
      var body = Visit(part.Body); 
      parameterMap.Remove(parameter); 
      return body; 
     } 

     Dictionary<ParameterExpression, Expression> parameterMap = new Dictionary<ParameterExpression, Expression>(); 

     protected override Expression VisitParameter(ParameterExpression node) 
     { 
      Expression replacement; 
      return parameterMap.TryGetValue(node, out replacement) ? replacement : node; 
     } 
    } 
} 

その後、用法:

var items = context.Events 
    .Where(x => x.TimeUtc != null && 
     x.TimeUtc.Value.ToCustomDateFormat().Contains(model.search)) 
    .ApplyCustomDateFormat(); 
+0

これはとても美しいコードです。私はあなたにもっと多くのポイントを与えることができたらいいと思う。 – Alexandru

+1

あなたは歓迎されています:)あなたの言葉は、いくつかの仮想ポイント以上の価値があります! –

3

解決策が見つかりました。この構文は動作しているようです:

using (var context = new ClientEventsContext()) 
    var items = context.Events.Where(x => { 
     x.TimeUtc.HasValue && x.TimeUtc.Value.ToString().Contains(model.search) 
    }); 
+0

ですが、そのソリューションでAsEnumerableを使用しています。 EF 6.1の後では、クエリでToString()を使用できると思います。 – yosbel

+0

@yosoy申し訳ありませんが、私はそれをコピーし、質問からそれを貼り付けるときにそれを削除するのを忘れました、それはポイントなしで動作します。答えを編集しました。 – Alexandru

+0

よろしくお願いします。 :) – yosbel

2

生のクエリには、何も変換し、例えば、SQLレベルで仕事をするSqlFunctionsライブラリを使用して別の解決策:ここ

using (var context = new ClientEventsContext()) 
var items = context.Events.Where(x => 
    SqlFunctions.PatIndex(model.search, 
    SqlFunctions.DateName('your pattern here', x?.TimeUtc) ?? "N/A").Value > -1 
); 
+0

'StringConvert'は' double'か 'decimal'だけを使うようですか? – Alexandru

+0

@Alexandru各プリミティブにはオーバーロードがあります。 – Fals

+2

「DateTime?」ではなく、私が知る限り。あなたが私を信じていない場合は、自分自身を参照してください:https://msdn.microsoft.com/en-us/library/system.data.objects.sqlclient.sqlfunctions_methods(v=vs.110).aspx 'Checksum'私はそれをどのように使用するか分からない。あなたが呼び出しようとしているメソッドで何かがオフになっている可能性があります。 – Alexandru

関連する問題