2015-09-24 41 views
7

EF kullanıyorum ve kayıt üzerinde çeşitli işlemler gerçekleştirildiği için çok sayıda tarih aralığı alanına sahip bir veritabanı tablosu var. Şu anda bu tarihlere göre filtrelemeyi içeren bir raporlama sistemi yapıyorum, ancak filtreler (bu aralıktaki bir tarih vb.) Her alanda aynı davranışa sahip olduğundan, filtreleme mantığımı yeniden kullanmak istiyorum. Sadece tek bir tarih filtresi yazın ve her alanda kullanın.LINQ içindeki bir alan filtresini Varlıklar'a yeniden kullanma

Benim ilk filtreleme kodu şöyle görünür:

DateTime? dateOneIsBefore = null; 
DateTime? dateOneIsAfter = null; 

DateTime? dateTwoIsBefore = null; 
DateTime? dateTwoIsAfter = null; 

using (var context = new ReusableDataEntities()) 
{ 
    IEnumerable<TestItemTable> result = context.TestItemTables 
     .Where(record => ((!dateOneIsAfter.HasValue 
       || record.DateFieldOne > dateOneIsAfter.Value) 
      && (!dateOneIsBefore.HasValue 
       || record.DateFieldOne < dateOneIsBefore.Value))) 
     .Where(record => ((!dateTwoIsAfter.HasValue 
       || record.DateFieldTwo > dateTwoIsAfter.Value) 
      && (!dateTwoIsBefore.HasValue 
       || record.DateFieldTwo < dateTwoIsBefore.Value))) 
     .ToList(); 

    return result; 
} 

Bu iyi çalışır, ancak filtre algoritması olarak 'Nerede' yöntemlerinde çoğaltılamaz kodunu azaltmak için tercih ediyorum her tarih alanı için aynıdır.

Ne tercih ediyorum benziyor şeydir (daha sonra filtre değerleri için bir sınıf veya yapı oluşturmak olacaktır) Aşağıdaki belki bir uzantısı yöntemi kullanılarak maç algoritmayı kapsayan nerede:

DateTime? dateOneIsBefore = null; 
DateTime? dateOneIsAfter = null; 

DateTime? dateTwoIsBefore = null; 
DateTime? dateTwoIsAfter = null; 

using (var context = new ReusableDataEntities()) 
{ 
    IEnumerable<TestItemTable> result = context.TestItemTables 
     .WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 
     .WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter) 
     .ToList(); 

    return result; 
} 

benim filtreleme kodu ise aşağıdaki gibi, yukarıdaki kodu kullanarak

internal static IQueryable<TestItemTable> WhereFilter(this IQueryable<TestItemTable> source, Func<TestItemTable, DateTime> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter) 
{ 
    source = source.Where(record => ((!dateIsAfter.HasValue 
      || fieldData.Invoke(record) > dateIsAfter.Value) 
     && (!dateIsBefore.HasValue 
      || fieldData.Invoke(record) < dateIsBefore.Value))); 

    return source; 
} 

:

IEnumerable<TestItemTable> result = context.TestItemTables 
    .WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 
    .WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter) 
    .ToList(); 
01 uzatma yöntemi gibi bir şey olabilir

A first chance exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll 

Additional information: LINQ to Entities does not recognize the method 'System.DateTime Invoke(RAC.Scratch.ReusableDataFilter.FrontEnd.TestItemTable)' method, and this method cannot be translated into a store expression. 

Ben sorun ben benim filtreleme kodunu değiştirmek çünkü eğer, SQL güzel çözmezse bu tekniği olarak sorgulanan belirli bir alan elde etmek için Invoke kullanılmasıdır:

aşağıdaki özel durum alıyorum o hatasız çalışır şunlardır:

IEnumerable<TestItemTable> result = context.TestItemTables 
    .ToList() 
    .AsQueryable() 
    .WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 
    .WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter) 
    .ToList(); 

bununla sorun (uzatma yöntemi ile filtreleme önce tüm masaya ToList kullanarak) kodu tüm veritabanı ve yerine sorgulama nesneleri o kadar sorguları çeker olmasıdır altta yatan veritabanı bu yüzden scalea değildir ble.

Ayrıca Linqkit'ten PredicateBuilder kullanarak araştırdım, ancak Invoke yöntemini kullanmadan kodu yazmanın bir yolunu bulamadı.

Sorunun bölümlerini alan adlarını içeren dizeler olarak ifade edebilecek teknikler olduğunu biliyorum, ancak bu kodu yazmanın daha güvenli bir yolunu kullanmayı tercih ediyorum.

Ayrıca, ideal bir dünyada veritabanını tek bir 'öğe' kaydıyla ilgili birden çok 'tarih' kaydına sahip olacak şekilde yeniden tasarlayabilirim, ancak veritabanı şemasını bu şekilde değiştirme özgürlüğüne sahip değilim.

Uzantıyı yazmam için başka bir yol var mı, bu nedenle Invoke kullanılmıyor mu, yoksa filtreleme kodumun farklı bir şekilde yeniden kullanılmasına mı gerek var?

cevap

2

Evet, LinqKit buradan gitmenin yoludur. Ancak sizin de uzantısı yönteminde bazı parçaları eksik:

internal static IQueryable<TestItemTable> WhereFilter(this IQueryable<TestItemTable> source, Expression<Func<TestItemTable, DateTime>> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter) 
{ 
    source = source.AsExpandable().Where(record => ((!dateIsAfter.HasValue 
      || fieldData.Invoke(record) > dateIsAfter.Value) 
      && (!dateIsBefore.HasValue 
      || fieldData.Invoke(record) < dateIsBefore.Value))); 

    return source; 
} 

Ben Expression<Func<TestItemTable, DateTime>> 2nd parametreyi değiştirerek LinqKit en AsExpandable() yöntemine eksik çağrı ekledi. Bu şekilde, Invoke(), LinqKit'in Invoke() numarasını arayarak büyüsünü yapabilirdi.

Kullanımı: Senin durumunda

IEnumerable<TestItemTable> result = context.TestItemTables 
    .WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 
    .WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter) 
    .ToList(); 
3

çok çoğaltılması yoktur, ama yine de ben (örnek olarak) ham ifadelerle istediğinizi yapabilirsiniz konuları ele alacağız:

internal static class QueryableExtensions { 
    internal static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, Expression<Func<T, DateTime>> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter) { 
     if (dateIsAfter == null && dateIsBefore == null) 
      return source; 
     // this represents you "record" parameter in lambda 
     var arg = Expression.Parameter(typeof(T), "record"); 
     // this is the name of your field ("DateFieldOne") 
     var dateFieldName = ((MemberExpression)fieldData.Body).Member.Name; 
     // this is expression "c.DateFieldOne" 
     var dateProperty = Expression.Property(arg, typeof(T), dateFieldName); 
     Expression left = null; 
     Expression right = null; 
     // this is "c.DateFieldOne > dateIsAfter" 
     if (dateIsAfter != null) 
      left = Expression.GreaterThan(dateProperty, Expression.Constant(dateIsAfter.Value, typeof(DateTime))); 
     // this is "c.DateFieldOne < dateIsBefore" 
     if (dateIsBefore != null) 
      right = Expression.LessThan(dateProperty, Expression.Constant(dateIsBefore.Value, typeof(DateTime))); 
     // now we either combine with AND or not, depending on values 
     Expression<Func<T, bool>> combined; 
     if (left != null && right != null) 
      combined = Expression.Lambda<Func<T, bool>>(Expression.And(left, right), arg); 
     else if (left != null) 
      combined = Expression.Lambda<Func<T, bool>>(left, arg); 
     else 
      combined = Expression.Lambda<Func<T, bool>>(right, arg); 
     // applying that to where and done. 
     source = source.Where(combined); 
     return source; 
    } 
} 
en az

WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 

ilk başta garip görünebilir ifadelerle çalışarak, fakat:

Çağrı beklediğiniz gibi olduğunu basit durumlar için çok karmaşık bir şey yoktur.

+0

boş olmayan bir tarih filtresi değeriyle ... Ben gerçi bir şey eksik olabilir, teşekkürler (özürlerimi - Belki örnek kod bu dahil olması gerekirdi), örneğin: DateTime? dateOneIsAfter = Yeni tarihi (2000, 12, 31); özel durum almak: türü 'System.InvalidCastException' işlenmeyen bir özel durum ReusableDataFilter.FrontEnd.exe oluştu Ek bilgi: yazmak için 'System.Linq.Expressions.UnaryExpression' türündeki nesne artığını açılamıyor 'Sistem .Linq.Expressions.MemberExpression'. hattında : var dateFieldName = ((MemberExpression) fieldData.Body) .Member.Name; cevabını güncellenmiş – NvR

+0

@NvR olmayan null tarihleri ​​ile çalışmak. – Evk

+0

OP, bu kod hakkında ancak İstisna'dan şikayet ederken, kodu bir şekilde değiştirmesi gerektiği anlamına gelir, 'fieldData' e => e.DateFieldOne 'gibi bir şey olurdu ve "Body" kesinlikle bir "MemberExpression" olmalıdır (ama bir şekilde bir 'UnaryExpresion' var, o aslında kullanabilir' e => e.DateFieldOne.Value'). Orijinal kodunuz ('DateTime?' Ile) 'e => DateFieldOne' için çalışmalı, mevcut kod' e => e.DateFieldOne.Value' için çalışmalıdır. Bu yüzden çaba için + 1 yapardım. OP İfade hakkında bilgi edinmek için bir şans atlamak olabilir. – Hopeless

İlgili konular