【问题标题】:LINQ to Entities does not recognize the method. Guid causing trouble?LINQ to Entities 无法识别该方法。导引麻烦?
【发布时间】:2013-07-26 13:59:15
【问题描述】:

在我的模型类 OwnedModule 中,我将 OwnerID 作为 Guid。

还有一个名为 BusinessUnit 的模型类,它包含来自 OwnedModule 及其 OwnerName 的相同 Guid 值。

我想显示 OwnedModule 的详细信息,它包含有关硬件的所有详细信息,但所有者作为 Guid 值而不是名称,为此我创建了一个视图模型

public class OwnedModuleViewModel
    {
        public long Id { get; set; }
        public string ModuleId { get; set; }
        public string TypeName { get; set; }
        public string KindName { get; set; }
        public string Owner { get; set; }
        public DateTime OwnershipStart { get; set; }
    }
    public class IndexOwnedModule
    {
        public List<OwnedModuleViewModel> OwnedModules { get; set; }
    }

为了在我的存储库中显示所有这些细节,我有以下功能

    public IndexOwnedModule GetAllOwnedModules()
    {

        var modulesList = new IndexOwnedModule();

        var modules = (_dbSis.OwnedModules.OrderBy(module => module.Id).Select(module => new OwnedModuleViewModel
        {
            Id = module.Id,
            ModuleId = module.ModuleId,
            TypeName = module.ModuleType.TypeName,
            Owner = GetModuleOwner(module.ModuleOwnerId),//error here
            OwnershipStart = module.Start
        }));

        modulesList.OwnedModules = modules.ToList();
        return modulesList;

    }

    public string GetModuleOwner(Guid id)
    {
        var ownedModule =_dbSis.Set<BusinessUnit>().FirstOrDefault(t => t.Id == id);
        if (ownedModule != null) return ownedModule.Name;
        return null;
    }

在视图中将 guid 值显示为所有者并不方便,因此我想获取我拥有 GetModuleOwnerName 的名称。

但似乎我将所有者名称设置为视图模型视图的方式是错误的,当我运行应用程序时出现以下错误。

LINQ to Entities 无法识别方法 'System.String GetModuleOwner(System.Guid)' 方法,并且该方法无法转换为存储表达式。

当我评论设置所有者值的行时(Owner = GetModuleOwner(module.ModuleOwnerId)),一切正常。

【问题讨论】:

    标签: asp.net-mvc linq asp.net-mvc-4


    【解决方案1】:

    当实体框架构建您的查询时,它依赖于对传递的表达式树的检查。当遇到方法调用时,它会尝试使用 (see this for the canonical methods) 将其映射到等效的 SQL 方法。因为实体框架不知道OwnedModuleViewModel.GetModuleOwner,所以它无法生成适当的 SQL 查询。在这种情况下,简单的方法是将您的方法的功能嵌入到查询中,而不是调用方法:

    _dbSis.OwnedModules
          .OrderBy(module => module.Id)
          .Select(module => new OwnedModuleViewModel 
              {
                  Id = module.Id,
                  ModuleId = module.ModuleId,
                  TypeName = module.ModuleType.TypeName,
                  Owner = _dbSis.Set<BusinessUnit>()
                                .Where(t => t.Id == module.ModuleOwnerId)
                                .Select(t => t.Name).FirstOrDefault(),
                  OwnershipStart = module.Start
              });
    

    当然,这是假设 _dbSis.Set&lt;BusinessUnit&gt;() 是同一 DbSet&lt;T&gt; 的一部分 DbContext,否则会出现同样的问题。

    【讨论】:

    • 这行得通,但最好通过 using 方法设置 Owner,不会使代码看起来干净
    • @Biplov13 如果您在OwnerModuleBusinessUnit 之间定义了关系,您可以简单地使用OwnerModule 上的导航属性来访问BusinessUnit,而不是进行嵌入式选择。但如果不知道您的模型是如何定义的,我无法确定。
    • 不幸的是,没有导航属性,但还是谢谢
    • 不确定 EF SQL 生成器对该查询的处理效果如何,或者即使它只生成一条语句,或者为来自OwnedModules 的每一行生成一条语句
    • 根据我的经验,EF SQL 生成器非常聪明。我尝试过更聪明、更复杂的做事方式,并且总是回到更简单的语法。查询本身可能看起来很难看(因为它本质上会执行嵌入式选择),但 SQL 数据库在优化查询计划方面非常聪明。
    【解决方案2】:

    在 Linq-To-Entities 中,针对上下文的 Linq 语句被转换为 SQL 语句。将GetModuleOwner() 方法转换为SQL 显然是不可能的。您需要先获取 ModuleOwnerId,然后在另一个步骤中,对每个 ModuleOwnerId 调用 GetModuleOwner()。

    或者您可以重组您的查询以使用连接:

    var modules = from m in _dbSis.OwnedModules
                  join b in _dbSis.BusinessUnit on m.ModuleOwnerId equals b.Id
                  order by m.Id
                  select new OwnedModuleViewModel {
                                          Id = m.Id,
                                          ModuleId = m.ModuleId,
                                          TypeName = m.ModuleType.TypeName,
                                          Owner = b.Name,
                                          OwnershipStart = m.Start};
    
    modulesList.OwnedModules = modules.ToList();
    

    注意:我没有对此进行测试,因此它可能存在一些小的语法错误。

    【讨论】:

    • 我认为它会起作用,减去(我认为)OwnedModuleViewModel()。你真的需要坚持使用 Entity Framework 的对象初始化器语法,否则它会抱怨。
    • 很高兴看到我的方法实际上正在使用,但我觉得有点复杂。也像你说的小错误确实存在,就像编辑说它在第二行末尾期待 ; 并且它没有解析符号 mb 在选择中。无论如何感谢您的选择
    【解决方案3】:

    这可能是另一种选择

    var ownedModules = _dbSis.OwnedModules.OrderBy(module => module.Id).Select(module => new
                {
                    Id = module.Id,
                    ModuleId = module.ModuleId,
                    TypeName = module.ModuleType.TypeName,
                    ModuleOwnerId = module.ModuleOwnerId,
                    OwnershipStart = module.Start
                }).ToList().Select(m => new OwnedModuleViewModel
                {
                    Id = m.Id,
                    ModuleId = m.ModuleId,
                    TypeName = m.TypeName,
                    Owner = GetModuleOwner(m.ModuleOwnerId),
                    OwnershipStart = m.OwnershipStart
                });
                ownedModulesList.OwnedModules = ownedModules.ToList();
                return ownedModulesList;
    

    【讨论】:

    • 使用函数,也没有复杂的查询
    • 你的新答案的问题是它非常无效。在查询中嵌入该方法允许查询执行一次。您所提议的是一个查询以获取OwnerModules,并且对于其中的每一个,您发送另一个查询以获取所有者。如果您有 5 个模块,则执行 6 个查询。如果您有 100 个模块,则执行 101 个查询,等等。虽然它确实有效,但它不能很好地扩展。调用ToList 会创建一个不必要的临时列表。您可以将其替换为AsEnumerable 或将Select 替换为List&lt;T&gt;.ForEach 并更改物化对象。
    • 不客气。使用 EF 有一些怪癖,不幸的是,物化(IQueryable vs IEnumerable)就是其中之一。 ://
    猜你喜欢
    • 2021-11-06
    相关资源
    最近更新 更多