Фильтры предиката DRY LINQ для нескольких таблиц - PullRequest
2 голосов
/ 29 июля 2010

Предположим, у меня есть две таблицы, TableA и TableB. Каждая запись в A имеет одну или несколько связанных записей в B. Скажем, я хочу повторно использовать фильтр, используя предикаты. Я мог бы сделать что-то вроде этого (Linq-to-SQL, кстати):

private Expression<Func<ARecord, bool>> FilterPredicate()
{
    return x => x.Name == "Test";
}

private IQueryable<ARecord> GetRecords()
{
    return DataContext.TableA.Where(FilterPredicate());
}

Это отлично работает, но, скажем, я хотел найти в TableB, но использовал тот же «фильтр» для внешнего ключа. Я хочу выполнить запрос, приведенный ниже, без необходимости переписывать FilterPredicate, как он связан с TableB.

var query = from b in DataContext.B
            where b.A.Name == "Test"
            select b;

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

Редактировать. Чтобы уточнить, я не ищу способ применения предиката к типам ARecord и BRecord. Я ищу способ (в любом случае, не обязательно в соответствии с тем, о чем я уже думал), чтобы он также не нуждался в этом предикате:

private Expression<Func<BRecord, bool>> FilterPredicate2()
{
    return x => x.A.Name == "Test";
}

Заранее спасибо.

Ответы [ 2 ]

3 голосов
/ 29 июля 2010

Вы можете сделать это, определив интерфейс через A и B.

public interface IHasName // contrived, I know
{
    string Name {get;}
}

Классы LINQ-To-SQL являются частичными, поэтому в вашей части определения частичного класса вы можете добавить интерфейс, напримерИтак:

public partial class A : IHasName {}
public partial class B : IHasName {}

Как видите, реализация не требуется, поскольку свойство Name реализовано в сгенерированной части Linq-To-Sql.

Теперь ограничьте ваш предикат типами, реализующимиИнтерфейс IHasName, и все готово:

private Expression<Func<T, bool>> FilterPredicate(string name) where T : IHasName
{
    return x => x.Name == name;
}

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

public static T GetByName<T>(this IQueryable<T> queryable, 
                             string name) where T : IHasName
{
    return queryable.Where(FilterPredicate(name)).SingleOrDefault();
}

Небольшое предостережение: конечно,свойство в интерфейсе («Имя») должно точно соответствовать имени свойства в реализующих классах.Предположим, у вас есть класс C со свойством «MyName».Вы можете испытать желание реализовать интерфейс IHasName следующим образом:

public partial class C : IHasName
{
    public string Name {return MyName;} 
} 

Это, конечно, не будет работать, так как синтаксический анализатор выражений Linq-To-Sql будет использовать «Name» вместо фактического свойства «MyName»поэтому он не сможет сопоставить это выражение с допустимым SQL.

0 голосов
/ 29 июля 2010

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

var query = from b in DataContext.B 
            select b;

и применить к нему следующее:

x => x.A.Name == "Test"

Не имея дубликат этого предиката, который я использую при запускезапрос к таблице A:

x => x.Name == "Test"

Итак, я полагаю, что решение состоит в том, чтобы «отменить» запрос, начиная с таблицы A, например:

var query = from a in DataContext.A
            join b in B on a equals b.A
            select b;

query = query.Where(FilterPredicate());

Я думалможет переписать запросы неэффективно, но, похоже, это не так.

...