当前位置: 首页 > 面试题库 >

实体框架-使用存储过程急于加载对象图

许永年
2023-03-14
问题内容

背景

我将项目中的LINQ-to-SQL代码更改为Entity Framework。大部分更改都相对简单,但是,我遇到了一个相当大的问题。使用LINQ-to-
SQL,我能够使用如下存储过程来加载整个对象图(针对模型B):

ViewModel.Model = MyDbContext.usp_ModelA_GetByID(AId).Single();
List<ModelB> Details = 
    (from b in MyDbContext.usp_ModelB_GetByID(BId)
    join c in MyDbContext.usp_ModelC_GetAll()
       on b.CId equals c.CId
    select new ModelB()
    {
        BId = b.BId,
        CId = b.CId,
        C = c
    }).ToList();
ViewModel.Model.ModelBs.AddRange(Details);

但是,将这段代码转换为EF之后,在访问ViewModel.Model.ModelBs的行上,出现错误“
EntityCommandExecutionException”,并带有内部异常,解释为“对对象’ModelBTable’的SELECT权限被拒绝。”
显然,即使我已经从数据库中加载了EF,EF仍在尝试为其获取ModelB。尽管即使添加了实体,我仍然不完全理解为什么要尝试加载这些实体,但是我只能假设由于它本身未加载它们,所以它不认为它们已完全加载并且可能查看了所有实体。我将其加载为“新”的对象。

为了绕过EF尝试获取对象本身的尝试,我决定将代码更改为:

ViewModel.Model = MyDbContext.usp_ModelA_GetByID(AId).Single();
List<ModelB> Details = 
    (from b in MyDbContext.usp_ModelB_GetByID(BId)
    join c in MyDbContext.usp_ModelC_GetAll()
        on b.CId equals c.CId
     select new ModelB()
     {
        BId = b.BId,
        CId = c.CId,
        C = c
     }).ToList();
ViewModel.Model.ModelBs = new EntityCollection<ModelB>();
foreach (ModelB detail in Details)
{
    ViewModel.Model.ModelBs.Attach(detail);
}

进行此更改之后,我现在遇到错误“
InvalidOperationException”,并显示消息“无法初始化EntityCollection,因为EntityCollection所属对象的关系管理器已经附加到ObjectContext。InitializeRelatedCollection方法应该仅是在对象图反序列化期间调用,以初始化新的EntityCollection。”。

这很令人困惑,因为我使用相同的上下文来加载所有实体,所以我不确定为什么它不允许我将它们组合在一起。我可以在其他ORM中做到这一点,而不会出现问题。

在研究了此错误之后,我决定尝试一种方法,希望该方法可以诱使EF认为整个对象图是由同一上下文加载的,因此我将代码重写为:

ViewModel.Model = 
    (from a in MyDbContext.usp_ModelA_GetByID(AId)
    select new A()
    {
        AId = a.AId,
        ModelBs = (from b in MyDbContext.usp_ModelB_GetByID(BId)
                  join c in MyDbContext.usp_ModelC_GetAll()
                      on b.CId equals c.CId
                  select new ModelB()
                  {
                      BId = b.BId,
                      CId = b.CId,
                      C = c
                  }).ToEntityCollection()
    }).Single();

ToEntityCollection是我创建的扩展方法,如下所示:

public static EntityCollection<TEntity> ToEntityCollection<TEntity>(
     this IEnumerable<TEntity> source) where TEntity : class, IEntityWithRelationships
{
    EntityCollection<TEntity> set = new EntityCollection<TEntity>();
    foreach (TEntity entity in source)
    {
        set.Attach(entity);
    }
    return set;
}

现在,出现错误“
InvalidOperationException”,并显示消息“此RelatedEnd的所有者为null时,不允许执行请求的操作。使用默认构造函数创建的RelatedEnd对象仅应在序列化期间用作容器。”

在广泛研究了每个错误之后,我仍然找不到与我的问题有关的解决方案。

问题

因此,毕竟,我的问题是:当每个对象都使用Entity Framework 4拥有自己的存储过程时,如何加载整个对象图?

更新

因此,根据目前为止的答案,我觉得我需要在此处包括以下警告:

  1. 我不是在寻找使用单个存储过程来加载整个对象图的答案。我正在寻找一种使用每个实体的get存储过程来加载对象图的方法。我意识到,使用单个存储过程加载对象图在理论上可以实现更好的性能,但是现在,我对代码库的较小更改(尤其是数据库的结构方式)更感兴趣。

  2. 如果您的解决方案需要直接编辑edmx,那么这将是一个不可接受的答案。由于这是一个自动生成的文件,因此直接编辑edmx本质上意味着,通过设计人员进行任何修改,都需要重新进行那些相同的更改。

更新2

因此,经过深思熟虑,我想出了一个解决方案。我所做的就是将ViewModel更改为具有List
ModelBs属性,该属性使用存储过程联接提取数据,而在我看来,我只是将此属性设置为数据源。这绝对不是我认为是最佳的解决方案,因为现在我的ViewModel的行为更像是Model,而不是ViewModel,并且我无法再遍历ModelA类型来获取ModelB列表,但是它可以工作!我仍然不明白为什么可以这样做:

(from b in MyDbContext.usp_ModelB_GetByID(BId)
join c in MyDbContext.usp_ModelC_GetAll()
    on b.CId equals c.CId
select new ModelB()
{
    BId = b.BId,
    CId = b.CId,
    C = c //<------Setting a navigation property and EF figures out that it belongs
}).ToList();

但我做不到:

(from a in MyDbContext.usp_ModelA_GetByID(AId)
select new ModelA()
{
    AId = a.AId,
    ModelBs = MyDbContext.usp_ModelB_GetByID(BId).ToEntityCollection() //<----Won't let me set the navigation property when the navigation property is a collection.
}).Single();

问题答案:

好的,因此,在进一步考虑之后,我想出了一种适用于我想要的解决方案。由于我处于Web环境中,不需要延迟加载对象,因此对于整个DbContext,我都将EnableLazyLoading设置为false。然后,使用称为魔术关系修复的EF功能,可以执行以下操作:

ViewModel.Model = MyDbContext.usp_ModelA_GetByID(AId).Single();
var Details = 
(from b in MyDbContext.usp_ModelB_GetByID(BId)
join c in MyDbContext.usp_ModelC_GetAll()
   on b.CId equals c.CId
select new ModelB()
{
    BId = b.BId,
    CId = b.CId,
    C = c
}).ToList();  
//ToList() executes the proc and projects the plate details into the object 
//graph which never tries to select from the database because LazyLoadingEnabled is
//false.  Then, the magical relationship fix-up allows me to traverse my object graph
//using ViewModel.Model.ModelBs which returns all of the ModelBs loaded into the graph
//that are related to my ModelA.


 类似资料:
  • 问题内容: 我已经创建了这个并且尝试使用实体框架来调用它。下面是用C#编写的代码。 在所有情况下都返回。请让我知道错误。 编辑 -根据给定的答案,不使用存储过程的return语句,因为Entity Framework不支持开箱即用的存储过程返回标量值。让我知道如何从中发送标量数据? 问题答案: 您的存储过程当前返回 标 量值。使用以下步骤来解决此问题: 像这样更改您的存储过程(不要在存储过程中使用

  • 我定义了两个具有一对多外键关系的类,它们首先由数据库生成。 我创建了一个代理对象,并将其添加到DBContext中。 然后我设置了一个外键属性并尝试访问相应的导航属性。 设置外键属性标签\u id不会加载导航属性标签。代码中的最后一行返回。 这些类遵循此处列出的创建POCO代理的要求(调试显示已创建代理car对象,因此我相信所有要求都已满足)。 数据库表标签包含主键为4的项。 我不是在这里调用或,

  • 我目前正在使用实体框架来建立一个论坛使用ASP。NET MVC I有3个主要模型[类别]1--*[论坛]1--*[帖子] 如果我有5个类别,每个类别有4个论坛,每个论坛有1000个帖子,我只想显示类别,当我这样做的时候,这意味着我也选择了20000个帖子?? 因为每个类别对象都有一个列表

  • 我在向数据库添加一个包含与现有对象关系的实体时遇到了麻烦。我搜索了很多,但找不到合适的解决方案。我会尽可能简单地描述这个。 在存储库中,我添加了这样的记录。这是泛型类 现在当我尝试添加这样的新记录时 商店关系被添加为新商店,并获得新ID。有人知道我如何解决这个问题吗? 数据库示例: 这是我关于stackoverflow的第一个问题。

  • 我对Entity Framework 6非常陌生,我想在我的项目中实现存储过程。我有一个存储过程,如下所示: 班级: 我的问题是:如何调用存储过程并向其中传递参数?

  • 问题内容: 我试图从存储过程中获取简单的输出 和我的MVC 但是当我编译时,在以下部分出现错误: 方法’sp_getrandomnumber’的重载没有接受1个参数 名称“ randoms”在当前上下文中不存在 提前非常感谢希什 问题答案: 您的问题是您的SPROC输出是一个int。 请参见Scott Gu的文章中的以下引用。 “ LINQ to SQL将SPROC中的“ out”参数映射为参考参