Применение предиката к объединенной таблице - PullRequest
1 голос
/ 12 марта 2020

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

У меня есть две таблицы:

  • User таблица (ID int, Name varchar(64), Email varchar(128), Active bit)
  • Event таблица (ID int, Date datetime(2), UserID int (внешний ключ), Description varchar(max))

Пользователь может иметь от нуля до многих событий и одно событие связан только с одним пользователем (через внешний ключ).

Теперь у меня есть куча User предикатов:

Expression<Func<User, bool>> nameIsBob = u => u.Name == "Bob";
Expression<Func<User, bool>> isActive = u => u.Active;

, которые я могу применить при запросе к таблице User :

var query1 = ctx.Users.Where(nameIsBob ).Select(u => u);
var query2 = ctx.Users.Where(isActive).Select(u => u);

Однако, если я начну со списка Events и захочу отфильтровать только те события, которые принадлежат определенным пользователям, я хочу сделать что-то подобное, но не могу разобраться в синтаксисе:

var query3 = ctx.Events
    .Where(e => e.User(nameIsBob))
    .Select(e => new { e.EventID, e.User.UserID });

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

var query4 = ctx.Events
    .Where(e => e.User.Where(nameIsBob))
    .Select(e => new { e.EventID, e.User.UserID });

Любой совет, как я могу получить все события, которые принадлежат людям по имени Боб ? (и там может быть несколько пользователей по имени Боб). Я не могу начать с таблицы User, поскольку приведенные выше примеры представляют собой значительно упрощенную версию того, что я делаю (и я надеюсь, что я не описываю XY проблему !)

1 Ответ

1 голос
/ 12 марта 2020

Если это не база данных, а простые сущности на стороне клиента, вы можете скомпилировать свое выражение, вы можете использовать его как метод, который принимает User и возвращает bool:

Expression<Func<User, bool>> nameIsBob = u => u.Name == "Bob";

Func<User, bool> compiledNamedIsBob = nameIsBob.Compile();

var query3 = something.Events
    .Where(e => compiledNameIsBob(e.User))

Это будет извлеките ваш Expression<Func<User, bool>>, извлеките из него Func<User, bool> и создайте Expression<Func<Event, bool>>, где Func<Event, bool> извлекает пользователя события и вызывает Func<User, bool>, используя его ..

ВАЖНО : этот совет по компиляции Expression to Fun c не работает на чем-то похожем на структуру сущностей. EF возьмет записанное выражение. NET и переведет его в SQL, и, хотя это может быть достаточно умным, он не будет использовать автономный метод. NET (полученный от компиляции выражения в Забавьте c, выше) и разберите его на части, переводя составляющие его утверждения в SQL. Единственный способ, которым это может сработать, - это загрузить всю таблицу БД в клиент, а затем вызвать метод. NET для каждой загруженной записи (если не было выполнено работы, чтобы EF отправлял метод. NET в SQLServer, компилируем он там и вызывается как часть запроса. NET функции могут существовать на SQL сервере, но EF этого не делает)


Итак ...

Вы должны понимать, что ctx.Events.Where() занимает Expression<Func<Event, bool>>, и у вас есть Expression<Func<User, bool>> -> вам нужно создать выражение, которое работает с событиями:

Expression<Func<Event, bool>> eventUserNameIsBob = e => e.User.Name == "Bob";

, что вы также можете сделать для события как вы сделали для пользователя:

//like you did this
Expression<Func<User, bool>> nameIsBob = u => u.Name == "Bob";
Expression<Func<User, bool>> isActive = u => u.Active;

//you can do this
Expression<Func<Event, bool>> eventUserNameIsBob = e => e.User.Name == "Bob");

и примените, как вы применяете к таблице пользователя:

var query1 = ctx.Users.Where(nameIsBob); //returns Users
var query2 = ctx.Users.Where(isActive); //returns Users
var query3 = ctx.Events.Where(eventUserNameIsBob).Select(e => e.User); //returns Users, via the Select

Однако, если вы хотите иметь только выражение nameIsBob, а не eventUserNameIsBob выражение Вы должны понимать, что nameIsBob выражение работает только для запрашиваемых коллекций Users , поэтому вам нужно взять все ваши события и выкопать из них пользователей, а также запустить выражение на раскопанном Пользователи, что означает, что вы по существу потеряли Новость вашего события (вы должны получить его обратно, спросив пользователя):

var query4 = ctx.Events.Select(e => e.User).Where(nameIsBob);

Этот действительно возвращает вам "группу пользователей", как и другие запросы (* 1040) *), но вы потеряли событие, если не получите его обратно от пользователя:

var query4 = ctx.Events.Select(e => e.User).Where(nameIsBob).Select(u => u.Event);

Что будет делать ваша БД в этом случае, я не уверен; вам придется проверить это / записать в журнал сгенерированные SQL.

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

var query3 = ctx.Events.Where(e => e.User.Name == "Bob"); //events owned by bobs

Современные ORM БД могут распаковать это, чтобы стать SELECT events.* FROM events JOIN users ON (...) WHERE users.name = 'bob', а не в былые времена, где это будет выглядеть так:

SELECT id FROM users WHERE name = 'bob' --returns list of 1,2,4,5
SELECT * FROM events WHERE userid = 1
SELECT * FROM events WHERE userid = 2
SELECT * FROM events WHERE userid = 4
SELECT * FROM events WHERE userid = 5
...