Предложение select, содержащее вызовы не-EF-методов - PullRequest
5 голосов
/ 17 ноября 2009

У меня проблемы с построением запроса Entity Framework LINQ, предложение которого содержит вызовы методов для объектов, не являющихся EF.

Приведенный ниже код является частью приложения, используемого для преобразования данных из одной СУБД в другую схему в другой СУБД. В приведенном ниже коде Role - это мой пользовательский класс, не связанный с СУБД, а все остальные классы созданы Entity Framework из моей схемы БД:

// set up ObjectContext's for Old and new DB schemas
var New = new NewModel.NewEntities();
var Old = new OldModel.OldEntities();

// cache all Role names and IDs in the new-schema roles table into a dictionary
var newRoles = New.roles.ToDictionary(row => row.rolename, row => row.roleid);

// create a list or Role objects where Name is name in the old DB, while
// ID is the ID corresponding to that name in the new DB
var roles = from rl in Old.userrolelinks
            join r in Old.roles on rl.RoleID equals r.RoleID
            where rl.UserID == userId
            select new Role { Name = r.RoleName, ID = newRoles[r.RoleName] };
var list = roles.ToList();

Но вызов ToList дает мне это исключение NotSupportedException:

LINQ to Entities не распознает метод Int32 get_Item (System.String) 'и этот метод не может быть переведен на магазинное выражение

Звучит так, как будто LINQ-to-Entities препятствует моему вызову, чтобы вытащить значение из словаря с указанным именем в качестве ключа. Я, правда, недостаточно разбираюсь в EF, чтобы понять, почему это проблема.

Я использую devart dotConnect для PostgreSQL провайдера инфраструктуры сущностей, хотя на этом этапе я предполагаю, что это не специфичная для СУБД проблема.

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

var roles = from rl in Old.userrolelinks
            join r in Old.roles on rl.RoleID equals r.RoleID
            where rl.UserID == userId
            select r;
var roles2 = from r in roles.AsEnumerable()
            select new Role { Name = r.RoleName, ID = newRoles[r.RoleName] };
var list = roles2.ToList();

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

Во всяком случае, мой вопрос состоит из двух частей:

Во-первых, могу ли я преобразовать этот запрос LINQ во что-то, что Entity Framework примет, в идеале, без разделения на две части?

Во-вторых, я также хотел бы немного разобраться в EF, чтобы понять, почему EF не может наложить свой собственный код .NET поверх доступа к БД. Моя СУБД понятия не имеет, как вызвать метод в классе Dictionary, но почему EF просто не может выполнить эти вызовы метода Dictionary после того, как данные уже получены из БД? Конечно, если бы я хотел составить несколько запросов EF вместе и поместить собственный код .NET в середину, я бы ожидал, что это не удастся, но в этом случае код .NET только в конце, так почему это проблема для EF? Я предполагаю, что ответ что-то вроде «эта функция не вошла в EF 1.0», но я ищу немного больше объяснения о том, почему это достаточно сложно оправдать, чтобы исключить его из EF 1.0.

Ответы [ 2 ]

10 голосов
/ 17 ноября 2009

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

.
New.roles.ToDictionary(row => row.rolename, row => row.roleid);

В этот момент данные перемещаются из БД в клиент и преобразуются в ваш словарь. Пока все хорошо.

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

, то все было бы прекрасно.
var roles = from rl in Old.userrolelinks
            join r in Old.roles on rl.RoleID equals r.RoleID
            where rl.UserID == userId
            select r.RoleName;
var list = roles.ToDictionary(roleName => roleName, newRoles[roleName]);

Таким образом, он разрешает ваш выбор в БД (выбирая только имя пользователя) в качестве предшественника для обработки вызова ToDictionary (что он должен делать на клиенте, как вы ожидаете). По сути, это именно то, что вы делаете во втором примере, потому что AsEnumerable передает данные клиенту, прежде чем использовать их в вызове ToList. Вы можете так же легко изменить его на что-то вроде

var roles = from rl in Old.userrolelinks
            join r in Old.roles on rl.RoleID equals r.RoleID
            where rl.UserID == userId
            select r;
var list = roles.AsEnumerable().Select(r => new Role { Name = r.RoleName, ID = newRoles[r.RoleName] });

и получилось бы то же самое. Вызов AsEnumerable () разрешает запрос, передавая данные клиенту для использования в последующем элементе Select.

Обратите внимание, что я не проверял это, но насколько я понимаю Entity Framework, это мое лучшее объяснение того, что происходит под капотом.

3 голосов
/ 17 ноября 2009

Джейкоб совершенно прав. Вы не можете преобразовать желаемый запрос, не разделив его на две части, потому что Entity Framework не может преобразовать вызов get_Item в запрос SQL.
Единственный способ - написать запрос LINQ to Entities, а затем написать запрос LINQ to Objects к его результату, как советовал Джейкоб.
Проблема связана с Entity-Framework, она не возникает из-за нашей реализации поддержки Entity Framework.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...