我无法构建实体框架LINQ查询,其select子句包含对非EF对象的方法调用。
以下代码是用于将数据从一个DBMS转换为另一个DBMS上的不同架构的应用程序的一部分。在下面的代码中,Role是我与DBMS无关的自定义类,其他类都是由我的数据库模式由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 for PostgreSQL实体框架提供程序,虽然我现在假设这不是DBMS特定的问题。
我知道我可以通过将查询分成两个查询来实现它,如下所示:
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代码进行分层。我的DBMS不知道如何在Dictionary类上调用一个方法,但是为什么EF在它已经从数据库中提取数据之后不能简单地进行那些Dictionary方法调用?当然,如果我想组合多个EF查询并将自定义.NET代码放在中间,我希望它会失败,但在这种情况下,.NET代码只是在最后,所以为什么这是一个问题呢? EF?我假设答案是“这个功能没有进入EF 1.0”,但我正在寻找更多的解释为什么这很难证明它离开EF 1.0。
答案 0 :(得分:10)
问题在于,在使用Linq的延迟执行时,您必须确定要处理的位置以及要将管道传输到客户端应用程序的数据。在第一个实例中,Linq解析表达式并将所有角色数据作为
的前身New.roles.ToDictionary(row => row.rolename, row => row.roleid);
此时,数据从数据库移动到客户端并转换为您的字典。到目前为止,非常好。
问题是你的第二个Linq表达式要求Linq在第二个数据库上使用数据库上的字典进行转换。换句话说,它试图找出一种方法将整个字典结构传递给DB,以便它可以选择正确的ID值作为查询延迟执行的一部分。我怀疑,如果你将下半部分改为
,它会解决得很好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]);
这样,它就可以解析你在DB上的选择(只选择角色名称)作为处理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中使用。
请注意,我还没有对此进行过测试,但据我了解实体框架,这是我对最新动态的最佳解释。
答案 1 :(得分:3)
雅各布完全正确。
如果不将它拆分为两部分,则无法转换所需的查询,因为实体框架无法将get_Item调用转换为SQL查询。
唯一的方法是编写LINQ to Entities查询,然后将LINQ to Objects查询写入其结果,就像Jacob建议的那样。
问题是特定于实体框架的问题,它不是来自我们对实体框架支持的实现。