C # метод для сравнения полей DateTime, указанных во время выполнения? - PullRequest
0 голосов
/ 01 июня 2018

В моем проекте много объектов с полями даты, и мне часто приходится выбирать все, где одно такое поле находится в пределах диапазона дат.

Например:

public class Contract
{
    public DateTime SignDate { get; set; }
    public DateTime ReleaseDate { get; set; }
}

public class PersonalCheck
{
    public DateTime SignDate { get; set; }
    public DateTime ProcessDate { get; set; }
    public DateTime VoidDate { get; set; }
}

Если я толькозаботился о SignDate, это было бы легко.Я бы объявил интерфейс ...

public interface IObjectWithSignDate
{
    DateTime SignDate { get; set; }
}

... изменить другие мои объекты для наследования от него, а затем создать метод, подобный этому:

    public static IQueryable<T> SignedWithin<T>(this IQueryable<T> items, DateTime start, DateTime end) where T : IObjectWithSignDate
    {
        return items.Where(q => q.SignDate >= start && q.SignDate <= end);
    }

Как мне избежать переписыванияэта функция для ReleaseDate, ProcessDate, VoidDate и т. д.?Могу ли я заставить этот метод принимать IQueryable любого объекта и переменную, сообщающую ему, с каким полем даты следует запускать этот селектор?

Обратите внимание, что это должно быть в состоянии: а) выполнить в LinqToEntities для запуска с базой данныхи б) не добавлять много накладных расходов (как я могу бояться отражения)

1 Ответ

0 голосов
/ 02 июня 2018

Простой, но специфичный

Вы можете добавить метод расширения, подобный этому:

public static class DateTimeExtensions
{
    public static bool IsBetween(this DateTime thisDateTime, DateTime start, DateTime end)
    {
        return thisDateTime >= start && thisDateTime <= end;
    }
}

, который можно тестировать отдельно.

Затем вы можете использовать это в любом поле DateTime, которое вы хотите проверить.Например:

var start = new DateTime(2017, 1, 1);
var end = new DateTime(2017, 12, 31, 23, 59, 59);
IList<Contract> contracts = new List<Contract>(); // or anything enumerable
var contractsSignedBetween = contracts.Where(x => x.SignDate.IsBetween(start, end));
var contractsReleasedBetween = contracts.Where(x => x.ReleaseDate.IsBetween(start, end));

(Обратите внимание, что для даты начала и времени установлено время 00:00:00, а для даты окончания - время 23:59:59 [не стесняйтесь также включать миллисекунды], так что время за последний день включено.)

Создание этого многоразового использования

Если вам понадобится сделать это много, вы можете сделать расширение длячто

public static class EnumerableContractsExtensions
{
    public static IEnumerable<Contract> SignedBetween(this IEnumerable<Contract> contracts, DateTime start, DateTime end)
    {
        return contracts.Where(x => x.SignDate.IsBetween(start, end));
    }
}

и использовать его вот так

 var contractsSignedBetween = contracts.SignedBetween(start, end);

, который также может быть проверен модулем изолированно.

Более гибкий, но специфичный

Используйте выражение, чтобы сказать, какую дату вы хотите ...

public static class EnumerableContractsExtensions
{
    public static IEnumerable<Contract> Between(this IEnumerable<Contract> contracts, Func<Contract, DateTime> selector, DateTime start, DateTime end)
    {
        return contracts.Where(x => selector(x).IsBetween(start, end));
    }
}

, а затем:

var contractsSignedBetween = contracts.Between(x => x.SignDate, start, end);
var contractsReleasedBetween = contracts.Between(x => x.ReleaseDate, start, end);

Гибкое и универсальное

Пройдите всю свинью и сделайте это в общем (хотя вы не можете сделать это методом расширения, потому что это универсально):

public static class EnumerableExtensions
{
    public static IEnumerable<T> Between<T>(IEnumerable<T> items, Func<T, DateTime> selector, DateTime start, DateTime end)
    {
        return items.Where(x => selector(x).IsBetween(start, end));
    }
}

еще раз, это можно проверить самостоятельно и может бытьиспользуется следующим образом:

IList<Contract> contracts = new List<Contract>();
IList<PersonalCheck> personalChecks = new List<PersonalCheck>();
var contractsSignedBetween = EnumerableExtensions.Between(contracts, x => x.SignDate, start, end);
var checksSignedBetween = EnumerableExtensions.Between(personalChecks, x => x.SignDate, start, end);

Сделать его IQueryable

Чтобы сделать эту работу как IQueryable, подход должен перейти кn дерево выражений , поскольку LINQ to Entities не знает, как преобразовать метод в SQL.

public static IQueryable<TSource> Between<TSource, TKey>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, TKey>> keySelector,
    TKey low,
    TKey high)
    where TKey : IComparable<TKey>
{
    Expression key = keySelector.Body;
    Expression lowerBound = Expression.LessThanOrEqual(Expression.Constant(low), key);
    Expression upperBound = Expression.LessThanOrEqual(key, Expression.Constant(high));
    Expression and = Expression.AndAlso(lowerBound, upperBound);
    Expression<Func<TSource, bool>> lambda =
        Expression.Lambda<Func<TSource, bool>>(and, keySelector.Parameters);
    return source.Where(lambda);
}

, который все равно будет использоваться следующим образом:

var contractsSignedBetween = contracts.Between(x => x.SignDate, start, end);

И это работает для других вещей, кроме DateTime с.Надеюсь, это поможет.

...