Избавление от ненужного объединения при использовании таблиц ссылок с EF - PullRequest
1 голос
/ 30 декабря 2011

У меня есть схема базы данных:

User  <--  UserRole  -->  Role

В таблице ролей есть несколько столбцов, но меня интересует только первичный ключ, и я могу получить его из внешнего ключа UserRole, чтобы мне вообще не нужно было присоединяться к таблице ролей, но я не могу выяснить, как избежать присоединения к этой таблице с помощью EF.

Я попробовал этот код:

context.Users.Where(u => u.UserId == x).Single().Roles.Select(r => r.RoleId);

Это генерирует два запроса. Один на пользовательской таблице и один на объединении UserRole и Role. Конечно, я могу сократить его до одного запроса, используя .Include ("Roles") или SelectMany (), но этот запрос объединит три таблицы. Есть ли способ избавиться от лишнего соединения? Я хочу, чтобы мой SQL был похож на это:

SELECT u.*, ur.RoleId
FROM User u
LEFT OUTER JOIN UserRole ur on ur.UserId = u.UserId
WHERE ...

На самом деле я использую automapper, но я думаю, что этот пример демонстрирует ту же проблему. Роли - это небольшая таблица, так что на этот раз я могу выдержать снижение производительности, но меня беспокоит, что я не могу получить ее так же эффективно, как рукописный SQL. Кто-нибудь еще сталкивался с этим до меня и нашел решение?

Ответы [ 2 ]

1 голос
/ 30 декабря 2011

Вы можете использовать следующее (я не использую var здесь, чтобы сделать типы явными):

IQueryable<IEnumerable<int>> query = context.Users
    .Where(u => u.UserId == x)
    .Select(u => u.Roles.Select(r => r.RoleId));

IEnumerable<int> result = query.Single();

Это

  • выдает исключение, если пользовательс UserId == x не существует.(Полагаю, вы хотите этого, потому что вы используете Single в вашем примере.)
  • возвращает коллекцию RoleId s пользователя x.Коллекция может быть пустой (result.Count() == 0), если пользователь не в какой-либо роли.
  • создает следующий SQL для queryx == 1), который объединяет только User иUserRoles таблица:

    SELECT 
    [Project1].[UserId] AS [UserId], 
    [Project1].[C1] AS [C1], 
    [Project1].[RoleId] AS [RoleId]
    FROM ( SELECT 
            [Extent1].[UserId] AS [UserId], 
            [Extent2].[RoleId] AS [RoleId], 
           CASE WHEN ([Extent2].[UserId] IS NULL) 
               THEN CAST(NULL AS int) 
               ELSE 1
           END AS [C1]
           FROM  [dbo].[Users] AS [Extent1]
           LEFT OUTER JOIN [dbo].[UserRoles] AS [Extent2]
               ON [Extent1].[UserId] = [Extent2].[UserId]
           WHERE 1 = [Extent1].[UserId]
         )  AS [Project1]
    ORDER BY [Project1].[UserId] ASC, [Project1].[C1] ASC
    

Если вы не хотите различать, если пользователь не существует или существует, но не имеет ролей, вы можете получить намного более простой SQL с этимquery:

IQueryable<int> query = context.Users
    .Where(u => u.UserId == x)
    .SelectMany(u => u.Roles.Select(r => r.RoleId));

IEnumerable<int> result = query.ToList();

Возвращает пустую коллекцию, если у пользователя нет ролей или , если пользователя не существует.Но SQL очень прост:

SELECT 
[Extent1].[RoleId] AS [RoleId]
FROM [dbo].[UserRoles] AS [Extent1]
WHERE 1 = [Extent1].[UserId]

Итак, здесь вообще нет объединения между задействованными таблицами, и запрос использует только таблицу ссылок UserRoles.

0 голосов
/ 30 декабря 2011

Я использую роли более эффективно.

Оформление заказа Флаги для добавления ролей в ваши пользовательские таблицы.

Что делает флаг:

Создает вид Enum с:

Administrator = 1
Moderator = 2
SuperUser = 4
User = 8
Visitor = 16

Вы добавляете атрибут Role в Usertable как Integer.

"User" = Administrator + Moderator --> Role =  3 ( 1 + 2 )
"User" = Moderator + SuperUser     --> Role =  6 ( 2 + 4 )
"User" = SuperUser + User          --> Role = 12 ( 4 + 8 )

Нет соединительной таблицы больше и решает вашу проблему.

(хотя для добавления логики в ваше приложение потребуется дополнительное кодирование)

...