回答

收藏

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

技术问答 技术问答 183 人阅读 | 0 人回复 | 2023-09-14

背景
# [; \# r4 b* ~  f我将项目中的LINQ-to-SQL代码更改为Entity Framework。大部分更改都相对简单,但是,我遇到了一个相当大的问题。使用LINQ-to-( _) w4 a! b' E4 u, B
SQL,我能够使用如下存储过程来加载整个对象图(针对模型B):
2 o7 @" w7 `# S  _4 E$ f% ~ViewModel.Model = MyDbContext.usp_ModelA_GetByID(AId).Single();$ {: v" V, ], Q3 @9 a! }. M
List Details =
( o7 I1 F+ o' P    (from b in MyDbContext.usp_ModelB_GetByID(BId)
( K$ j  |* }! _" Z+ Y2 l7 _: x1 p    join c in MyDbContext.usp_ModelC_GetAll()' I$ ?+ p% r+ w$ [* e/ ^5 @
       on b.CId equals c.CId) [4 g( D9 R9 O
    select new ModelB()2 @2 _2 T$ F5 P; p% d5 D/ O+ E
    {( J- x# A0 a2 s* ~1 r
        BId = b.BId,+ S) G1 _2 D5 ?& k
        CId = b.CId,4 O3 L+ Q6 ]3 W5 \, D$ \' D; p' D, K
        C = c
6 ]6 C3 i0 O- o: G- K    }).ToList();
) [. y: D& j1 g5 {6 {/ GViewModel.Model.ModelBs.AddRange(Details);5 l- N/ W' ^: {/ G5 \+ j/ c9 j, i. W
但是,将这段代码转换为EF之后,在访问ViewModel.Model.ModelBs的行上,出现错误“! b) h" Z3 G: V
EntityCommandExecutionException”,并带有内部异常,解释为“对对象’ModelBTable’的SELECT权限被拒绝。”/ N2 x% b) s! j0 I$ h  J
显然,即使我已经从数据库中加载了EF,EF仍在尝试为其获取ModelB。尽管即使添加了实体,我仍然不完全理解为什么要尝试加载这些实体,但是我只能假设由于它本身未加载它们,所以它不认为它们已完全加载并且可能查看了所有实体。我将其加载为“新”的对象。
9 s0 _7 ^: G9 A- l为了绕过EF尝试获取对象本身的尝试,我决定将代码更改为:
7 l( w4 f6 i* C8 W5 E4 H2 P# V2 eViewModel.Model = MyDbContext.usp_ModelA_GetByID(AId).Single();
8 l# U) Z+ q& ?- K& RList Details = - {/ [% q/ F  [/ f1 }) _$ _
    (from b in MyDbContext.usp_ModelB_GetByID(BId)
' n2 L/ _9 A# S6 e9 u    join c in MyDbContext.usp_ModelC_GetAll()
$ J/ B6 ]7 a: M/ B) N" P        on b.CId equals c.CId4 ?: p6 F; n3 C4 H/ U
     select new ModelB()
; A8 n7 p  \" w% x" }     {& Q9 E2 q2 U- X4 K1 Q1 X
        BId = b.BId,
7 B/ O$ d: I) C  y! f- P        CId = c.CId,
7 p& a" X8 s  Q4 P3 n) c' `        C = c0 x' G, g1 M. N+ _
     }).ToList();! K- E" r5 z# U: w/ x, n
ViewModel.Model.ModelBs = new EntityCollection();
+ z) O* e  t* \! Lforeach (ModelB detail in Details)
) V$ ]5 U1 c4 [* C: V# l{- b3 ^0 R* n3 {* A) l( w; D
    ViewModel.Model.ModelBs.Attach(detail);
: g  P0 k% t: w: p2 w# v1 W+ `}) Q- e$ d0 ^, y$ X
进行此更改之后,我现在遇到错误“
2 y9 g2 G+ i2 e" E7 G6 g) @InvalidOperationException”,并显示消息“无法初始化EntityCollection,因为EntityCollection所属对象的关系管理器已经附加到ObjectContext。InitializeRelatedCollection方法应该仅是在对象图反序列化期间调用,以初始化新的EntityCollection。”。2 M# T9 T' g5 w/ |8 H! ?
这很令人困惑,因为我使用相同的上下文来加载所有实体,所以我不确定为什么它不允许我将它们组合在一起。我可以在其他ORM中做到这一点,而不会出现问题。5 a  [8 m& `% v. z, N0 a
在研究了此错误之后,我决定尝试一种方法,希望该方法可以诱使EF认为整个对象图是由同一上下文加载的,因此我将代码重写为:9 O; E) ^: L. V! \- a
ViewModel.Model =
- q# r2 S1 O% V+ m' F3 @    (from a in MyDbContext.usp_ModelA_GetByID(AId)
; _, V2 u6 {. \9 [    select new A()4 V; g: r1 w8 V3 L& e
    {* e3 H1 C+ q& Z& a
        AId = a.AId,; [+ e* @, w7 `# Z. Q
        ModelBs = (from b in MyDbContext.usp_ModelB_GetByID(BId)9 T: s7 m+ S: c7 l. t
                  join c in MyDbContext.usp_ModelC_GetAll()
7 g' p5 ?; o0 d2 K& c                      on b.CId equals c.CId9 T) s9 Z& u, P1 r2 M
                  select new ModelB()5 t' M, t9 z6 A* C* i, B4 W
                  {: \- b, Z2 o( W/ M' Z* Z, x+ N
                      BId = b.BId,- J1 X+ I3 z' \( ]3 p
                      CId = b.CId,
9 r, }  r  S  ~2 Z                      C = c1 D9 T2 N' F, x" f+ \4 k& Y# A
                  }).ToEntityCollection()
8 S8 t& F  W, n1 ^: O    }).Single();
6 w! W1 G' u2 ^: E5 e" w; oToEntityCollection是我创建的扩展方法,如下所示:
' H! Q8 }: N, p0 Ppublic static EntityCollection ToEntityCollection(
2 S- C1 ]& `. V/ \: I" N8 K! X& b     this IEnumerable source) where TEntity : class, IEntityWithRelationships4 L/ O) ~! I# E* s9 o6 }. ~. ]* \
{' {7 X# A% z! f9 J$ k& h
    EntityCollection set = new EntityCollection();" ]7 Q  L! p1 t0 @4 M5 i
    foreach (TEntity entity in source)& T' }# T+ Y6 g. X
    {
$ ]1 d% Z$ p8 g; B9 F/ N0 q# P        set.Attach(entity);
& ]: x3 E4 T6 O: `    }
9 t% l: d, K. \    return set;/ }1 G) i  ], d  L3 r8 g( [* H
}
+ d4 P4 s& T! D' T9 o现在,出现错误“+ l) W8 A- b- o" C7 ]2 _9 a' p
InvalidOperationException”,并显示消息“此RelatedEnd的所有者为null时,不允许执行请求的操作。使用默认构造函数创建的RelatedEnd对象仅应在序列化期间用作容器。”+ t/ y4 _  a. P: M+ U
在广泛研究了每个错误之后,我仍然找不到与我的问题有关的解决方案。3 ]! @3 V* t' S( `/ x
问题
' r5 B( E( ~( L9 h1 ?因此,毕竟,我的问题是:当每个对象都使用Entity Framework 4拥有自己的存储过程时,如何加载整个对象图?4 l8 c2 R, U8 c) n; _
更新
: t) @" @6 }) G2 V/ h; B1 |& }5 O因此,根据目前为止的答案,我觉得我需要在此处包括以下警告:
" Z* D6 b0 F; J" Z1 Y% Y[ol]. y/ C/ |& v# X7 k# H$ W8 o
我不是在寻找使用单个存储过程来加载整个对象图的答案。我正在寻找一种使用每个实体的get存储过程来加载对象图的方法。我意识到,使用单个存储过程加载对象图在理论上可以实现更好的性能,但是现在,我对代码库的较小更改(尤其是数据库的结构方式)更感兴趣。
* m% T4 @. z9 h: q3 U
. W+ w- H$ h9 h: \* z+ E* E如果您的解决方案需要直接编辑edmx,那么这将是一个不可接受的答案。由于这是一个自动生成的文件,因此直接编辑edmx本质上意味着,通过设计人员进行任何修改,都需要重新进行那些相同的更改。5 g! c) e# l* A5 E8 b

, I( P: N8 h$ f7 A& R6 Z  N$ r[/ol]
6 w6 \8 F4 @4 O' b更新2; ]/ [0 K# S; Y$ h& s
因此,经过深思熟虑,我想出了一个解决方案。我所做的就是将ViewModel更改为具有List0 B  s4 |, a4 `! t6 l+ {% j/ c
ModelBs属性,该属性使用存储过程联接提取数据,而在我看来,我只是将此属性设置为数据源。这绝对不是我认为是最佳的解决方案,因为现在我的ViewModel的行为更像是Model,而不是ViewModel,并且我无法再遍历ModelA类型来获取ModelB列表,但是它可以工作!我仍然不明白为什么可以这样做:
! f2 K8 N1 V( e9 H: n2 }- A# g(from b in MyDbContext.usp_ModelB_GetByID(BId)
7 B/ F0 W% k5 ]! O. Ojoin c in MyDbContext.usp_ModelC_GetAll()) j2 _8 \* A" F* F5 I" c* \
    on b.CId equals c.CId
" N3 M9 [3 c& ^' v& y5 Rselect new ModelB()3 L5 b+ z, l, a9 n  V0 W; ~, R2 J# c
{( e, J* E" ^- E' u& V
    BId = b.BId,
: J6 V% b6 D& y1 p2 F5 D" Z    CId = b.CId,7 V6 e; v+ ]7 W& ]
    C = c //但我做不到:  T$ w8 J5 r) Y; j6 Z& h1 w$ d# z
(from a in MyDbContext.usp_ModelA_GetByID(AId)+ p+ a1 V; Z0 X( I! P" G9 G1 c
select new ModelA()9 g7 W( ]: Y6 ]2 A" J) |4 {
{
: p- m/ P) A/ m2 D- [: }- x    AId = a.AId,
7 c+ L5 B0 x1 X3 g    ModelBs = MyDbContext.usp_ModelB_GetByID(BId).ToEntityCollection() //好的,因此,在进一步考虑之后,我想出了一种适用于我想要的解决方案。由于我处于Web环境中,不需要延迟加载对象,因此对于整个DbContext,我都将EnableLazyLoading设置为false。然后,使用称为魔术关系修复的EF功能,可以执行以下操作:
& W; R# n, L( oViewModel.Model = MyDbContext.usp_ModelA_GetByID(AId).Single();
. C4 G6 Q: B6 x% u, C; o# [var Details =
6 l! y& R0 O6 @; H6 g(from b in MyDbContext.usp_ModelB_GetByID(BId)2 h5 s4 A& A' G: `# x, _
join c in MyDbContext.usp_ModelC_GetAll()$ g  j1 W% A( O3 K8 S  q
   on b.CId equals c.CId
3 O1 R8 a( O. ~select new ModelB()
; ?! |5 p1 k6 y5 m( A1 Y& x{6 Y+ I; B- b) T5 y* ?8 S# h: X
    BId = b.BId,9 ~1 i) ~2 Q; l2 |
    CId = b.CId,7 Q9 S( C/ |' m+ ]
    C = c
% d, Y+ }9 S1 Z, r9 z& G}).ToList();  
9 u3 S8 a/ \6 V( U//ToList() executes the proc and projects the plate details into the object
: r8 @$ o6 P; i& W) C' _//graph which never tries to select from the database because LazyLoadingEnabled is
, l, e: ~7 S$ _3 Z/ X" e- k//false.  Then, the magical relationship fix-up allows me to traverse my object graph
( z9 A* u6 N6 ]1 @% R; G( x2 E//using ViewModel.Model.ModelBs which returns all of the ModelBs loaded into the graph$ k* [1 S) _7 R
//that are related to my ModelA.
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则