c# - Entity Framework -使用存储过程急于加载对象图

标签 c# sql sql-server linq entity-framework

背景

我将项目中的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。尽管即使添加了实体,我仍然不完全理解为什么要尝试加载实体,但是我只能假设,因为它本身未加载实体,所以不相信它们已完全加载,并且可能查看了所有实体。我将其加载为"new"的对象。

为了绕过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拥有自己的存储过程时,如何加载整个对象图?

更新

因此,根据目前为止的答案,我觉得我需要在此处包括以下警告:
  • 我不是在寻找使用单个存储过程来加载整个对象图的答案。我正在寻找一种使用每个实体的get存储过程来加载对象图的方法。我意识到,使用单个存储过程加载对象图在理论上可以执行得更好,但是目前,我对代码库的较小更改(尤其是数据库的结构方式)更感兴趣。
  • 如果您的解决方案需要直接编辑edmx,那么它将是 Not Acceptable 答案。由于这是一个自动生成的文件,因此直接编辑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();
    

    最佳答案

    可以用相当简单的方式完成此操作,但需要一些人工。 Here is an MSDN post处理具有多个结果集的存储过程,该过程同时显示了代码优先和数据库优先的方法。

    例子:

    加载EntityB过程:

    create proc dbo.Get_EntityB_by_EntityAId( @aId int )
    as
    
    select distinct
        b.EntityBId
        , b.Description
    from
        EntityA a
        left outer join EntityB b
         on a.PrimaryEntityB_EntityBId = b.EntityBId
        left outer join EntityB b2
         on a.AlternativeEntityB_EntityBId = b2.EntityBId
    where
        a.EntityAId = @aId
    go
    

    加载EntityA proc(调用load B proc)
    create proc dbo.Get_EntityA_by_Id( @id int )
    as
    
    -- use a select statement
    select 
        a.EntityAId
        , a.Description
        , a.PrimaryEntityB_EntityBId
        , a.AlternativeEntityB_EntityBId
    from
        EntityA a
    where
        a.EntityAId = @id
    
    -- and/or other sprocs
    exec dbo.Get_EntityB_by_EntityAId @id
    
    go
    

    实体类
    [Table("EntityA")]
    public partial class EntityA
    {
        public int EntityAId { get; set; }
        public string Description { get; set; }
    
    
        public virtual EntityB PrimaryEntityB { get; set; }
    
        public virtual EntityB AlternativeEntityB { get; set; }
    }
    
    
    [Table("EntityB")]
    public partial class EntityB
    {
        public int EntityBId { get; set; }
        public string Description { get; set; }
    
        [InverseProperty("PrimaryEntityB")]
        public virtual ICollection<EntityA> EntityAsViaPrimary { get; set; }
        [InverseProperty( "AlternativeEntityB" )]
        public virtual ICollection<EntityA> EntityAsViaAlternative { get; set; }
    }
    

    调用sproc并处理结果的方法(对于该方法,您可以根据需要返回一个EntityA)
    public static void EagerLoadEntityA( int aId )
    {
        using( var db = new TestEntities() )
        {
            // if using code first
            db.Database.Initialize( false );
    
            var cmd = db.Database.Connection.CreateCommand();
            cmd.CommandText = "dbo.Get_EntityA_by_Id";
    
            db.Database.Connection.Open();
    
            try
            {
                var reader = cmd.ExecuteReader();
    
                var objContext = ( ( IObjectContextAdapter )db ).ObjectContext;
    
                var aEntities = objContext
                    .Translate<EntityA>( reader, "EntityAs", MergeOption.AppendOnly );
    
                reader.NextResult();
    
                var bEntities = objContext
                    .Translate<EntityB>( reader, "EntityBs", MergeOption.AppendOnly );
    
            }
            finally
            {
                db.Database.Connection.Close();
            }
        }
    }
    

    用法:
    EagerLoadEntityA( 1234 );
    var entityA = db.EntityAs.Find( 1234 ); // cached
    var primB = entityA.PrimaryEntityB; // this is already loaded
    

    关于c# - Entity Framework -使用存储过程急于加载对象图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21538728/

    相关文章:

    c# - 无法从C#中的相同类但使用不同方法访问对象属性

    sql - SQL Server 2008中是否有更有效的按日期分组的方法

    mysql - rails : returning the latest data of fields

    sql-server - 如何在另一个数据库中创建引用调用者中正确的 sys.objects 表的 UDF 或 View ?

    c# - 在 C# 中,我如何根据值而不只是字母顺序对基于第二列的锯齿状二维矩形数组进行升序排序?

    C# const int 和 const someStruct 之间的区别。为什么 const someStruct 不是 "compile-time constant"?

    java - 如何在 Jooq 中使用 SelfJoin?

    sql-server - 如何安全地向SQLServer2017 Linux Docker容器提供SA密码?

    sql-server - 无法找到哪一行导致转换错误

    c# - ASP.NET MVC 2 和 ComponentModel.DataAnnotations 验证 : minimum value attribute