Создание общего набора методов, которые могут работать с любой таблицей linq - PullRequest
4 голосов
/ 04 июня 2011

Проблема: Мы широко используем шаблон репозитория для упрощения операций чтения / записи в нашем хранилище данных (MS SQL с использованием LINQ) для нескольких приложений и подразделов функциональности.У нас есть ряд методов, которые делают что-то похожее друг на друга.

Например, у нас есть класс методов ProcessAndSortXXXXX.

private static IEnumerable<ClassErrorEntry> ProcessAndSortClassErrorLog(IQueryable<ClassErrorDb> queryable, string sortOrder)
{
    var dynamic = queryable;
    if (!String.IsNullOrEmpty(sortOrder.Trim()))
    {
        dynamic = dynamic.OrderBy(sortOrder);
    }
    return dynamic
        .Select(l =>
            new ClassErrorEntry(l.Id)
            {
                ClassId = l.ClassId,
                Code = l.Code,
                Message = l.Message,
                Severity = l.Severity,
                Target = l.Target
            }
        );
}

... и ...

private static IEnumerable<ClassTimerLogEntry> ProcessAndSortClassTimerLog(IQueryable<ClassTimerDb> queryable, string sortOrder)
{
    var dynamic = queryable;
    if (!String.IsNullOrEmpty(sortOrder.Trim()))
    {
        dynamic = dynamic.OrderBy(sortOrder);
    }
    return dynamic
        .Select(l =>
            new ClassTimerLogEntry(l.Id)
            {
                ClassName = l.ClassName,
                MethodName = l.MethodName,
                StartTime = l.StartTime,
                EndTime = l.EndTime,
                ParentId = l.ParentId,
                ExecutionOrder = l.ExecutionOrder
            }
        );
}

Как вы можете сказать по коду, все они очень похожи, пока вы не посмотрите на сигнатуру, а затем не доберетесь до оператора return, где мы создаем экземпляры ClassErrorEntry и ClassTimerLogEntry.

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

Я хочу иметь возможность передавать аргументы, которые можно использовать для создания экземпляраобъекты и упаковать их в возвращаемый IEnumerable.

Я нашел этот пост от ScottGu , и это дает мне большую часть того, что мне нужно.Выглядит это так (из примера в документации):

var query =
    db.Customers.
    Where("City = @0 and Orders.Count >= @1", "London", 10).
    OrderBy("CompanyName").
    Select("new(CompanyName as Name, Phone)");

Вот где я застрял.Мне нужен указатель или предложение, как я могу передать в таблицах LINQ и DataContext в общем виде, чтобы я мог построить динамический запрос.

Если бы я смоделировал подпись в псевдокоде, я думаю, это выглядело быкак-то так:

protected internal IEnumerable ProcessAndSort(IQueryable source, string selectClause, string whereClause, string orderByClause);

Я понимаю, что готовая подпись может выглядеть иначе, когда мы это выясним.

Спасибо!

Обновление!

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

public static IEnumerable<TResult> ProcessAndSort<T, TResult>(IQueryable<T> queryable, 
    string selector, Expression<Func<T, bool>> predicate, string sortOrder)
{
    var dynamic = queryable.Where(predicate).AsQueryable();
    if (!String.IsNullOrEmpty(sortOrder.Trim()))
    {
        dynamic = dynamic.OrderBy(sortOrder);
    }
    var result= dynamic.Select(selector).Cast<TResult>();

    return result;
}

Вот код, вызывающий этот метод:

[TestMethod]
public void TestAnonymousClass()
{
    var loggingContext = new LoggingDbDataContext(DatabaseConnectionString);
    var repo = new LoggingRepository(loggingContext);

    var result = repo.TestGetClassErrorLog(4407, 10, 0, 
        "new ( ClassId as ClassId, " +
        "Code as Code, " +
        "Message as Message, " +
        "Severity as Severity, " +
        "Target as Target )", "Target");
    TestContext.WriteLine(result.ToList().Count.ToString());
}

Последняя строка TestContext.WriteLine(result.ToList().Count.ToString()); выдает исключение System.InvalidOperationException: No coercion operator is defined between types 'DynamicClass1' and 'Utilities.Logging.ClassErrorEntry'.

Этот фрагмент кода, хотя и завершается ошибкой:

[TestMethod]
public void TestNamedClass()
{
    var loggingContext = new LoggingDbDataContext(DatabaseConnectionString);
    var repo = new LoggingRepository(loggingContext);

    var result = repo.TestGetClassErrorLog(4407, 10, 0,
        "new ClassErrorEntry(Id) { ClassId = ClassId, " +
        "Code = Code, " +
        "Message = Message, " +
        "Severity = Severity, " +
        "Target = Target }", "Target");
    TestContext.WriteLine(result.ToList().Count.ToString());
}

Эта ошибка не выполняется при ошибке синтаксического анализа.Test method eModal.Repositories.Test.RepositoryBaseTest.TestConcreteClass threw exception: System.Linq.Dynamic.ParseException: '(' expected, found 'ClassErrorEntry' ('Identifier') at char 19 in 'new ClassErrorEntry(Id) { ChassisAuthId = ChassisAuthId, Code = Code, Message = Message, Severity = Severity, Target = Target }'

Я не уверен, что позиция символа является подозрительной, поскольку позиция 19-го символа - (, а тип, переданный в метод Validate, указывает позицию 4 или первую 'C'.

Ответы [ 2 ]

1 голос
/ 11 июня 2011

Я бы полностью посоветовал вам против делать слабо типизированные запросы только ради повторного использования кода.
Повторное использование кода для повышения удобства сопровождения, но слабая типизация может убить его, если использовать его неправильно. Написав свои запросы в виде простого текста, вы эффективно усложняете реорганизацию и изменение классов и вводите множество неясных зависимостей.

Я предлагаю вам взглянуть на LinqKit , который позволяет комбинировать Expression с. Например, мы написали метод Paging, который разбивает запрос по страницам и использует его в проекте разных типов:

var query = CompiledQuery.Compile(
    BuildFolderExpr( folder, false )
        .Select( msg => selector.Invoke( msg, userId ) ) // re-use selector expression
        .OrderBy( mv => mv.DateCreated, SortDirection.Descending )
        .Paging() // re-use paging expression
        .Expand() // LinqKit method that "injects" referenced expressions
    )

public static Expression<Func<T1, T2, PagingParam, IQueryable<TItem>>> Paging<T1, T2, TItem>(
    this Expression<Func<T1, T2, IQueryable<TItem>>> expr )
{
    return ( T1 v1, T2 v2, PagingParam p ) => expr.Invoke( v1, v2 ).Skip( p.From ).Take( p.Count );
}

В моем примере BuildMessageExpr возвращает относительно простое выражение выбора (которое уже зависит от folder и другого параметра), и различные методы повторно используют это выражение, применяя фильтрацию, упорядочение, получение счетчика, дальнейший выбор с использованием выражения выбора передается в качестве параметра и так далее. Как только запрос создан, он кэшируется для будущего использования, когда параметры похожи.

0 голосов
/ 04 июня 2011

Это не прямой ответ на ваш вопрос.

Как вы уже сказали, у вас довольно много кода, который выглядит похожим, но возвращает разные типы.Если вы пойдете дальше и будете искать общую реализацию этого подхода, результат может иметь несколько взломов, вы все равно можете пропустить некоторый неудобный SQL или проверить тип объекта или сделать некоторое отражение кунг-фу.Вы все еще можете выбрать этот проход, и на самом деле у кого-то может появиться разумная идея, которая не будет выглядеть как грязный хак.

Другой вариант - использовать правильный ORM с общим шаблоном репозитория и внедрением зависимостей (гугл ссылка ).Ваш уровень доступа к данным будет выглядеть намного проще и проще в обслуживании.

...