背景
我将项目中的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拥有自己的存储过程时,如何加载整个对象图?
更新
因此,根据目前为止的答案,我觉得我需要在此处包括以下警告:
更新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/