Workaround: Ein Objekt mit demselben Schlüssel ist bereits im ObjectStateManager vorhanden...
Wer mit dem Entity Framework in der Version 1 arbeitet, kennt sicherlich die verschiedenen Stufen der Herausforderungen. Eine dieser Herausforderung in einer mehrschichtigen Anwendung kann zum Beispiel sein:
Ein Objekt mit demselben Schlüssel ist bereits im ObjectStateManager vorhanden. Der ObjectStateManager kann nicht mehrere Objekte mit demselben Schlüssel nachverfolgen.
Im Blog http://bernhardelbl.spaces.live.com/blog/cns!DB54AE2C5D84DB78!238.entry fand ich eine interessante Zusammenfassung und ein Beispiel, dass ich in meine Workaround EF - Extensions aufgenommen habe. Es funktionierte bei mir nur nicht auf Anhieb. Die erste Fehlermeldung die mich begrüsste war:
Die Auflistung wurde geändert. Der Enumerationsvorgang kann möglicherweise nicht ausgeführt werden.
Nach dieser Anpassung kamen noch ein paar Sideeffects aber mittlerweile läuft es bei mir. Da das Beispiel mit rekursiven Durchläufen arbeitet, habe ich noch eine Abbruchbedingung sowie zwei Erweiterungsmethoden hinzugefügt.
32 public static class EfExtensions
33 {
200 // Other entity workarounds....
201
202 /// <summary>
203 /// Adds the or attach.
204 /// </summary>
205 /// <param name="context">The context.</param>
206 /// <param name="entitySetName">Name of the entity set.</param>
207 /// <param name="entity">The entity.</param>
208 public static void AddOrAttach(this ObjectContext context, string entitySetName, EntityObject entity)
209 {
210 AddObjectOrAttach(context, entitySetName, entity, 2);
211 }
212
213 public static void AddOrAttach(this ObjectContext context, string entitySetName, EntityObject entity, int maxDepth)
214 {
215 AddObjectOrAttach(context, entitySetName, entity, maxDepth);
216 }
217
218 /// <summary>
219 /// Adds or attaches an entity object to the context
220 /// </summary>
221 /// <param name="context">The context.</param>
222 /// <param name="entitySetName">Name of the entity set.</param>
223 /// <param name="entity">The entity.</param>
224 /// <param name="maxDepth">The max depth.</param>
225 public static void AddObjectOrAttach(
226 ObjectContext context,
227 string entitySetName,
228 EntityObject entity,
229 int maxDepth)
230 {
231 if (entity.EntityKey == null || entity.EntityKey.EntityKeyValues == null)
232 {
233 AddObject(context, entitySetName, entity, maxDepth);
234 }
235 else
236 {
237 Attach(context, entity, maxDepth);
238 }
239 }
240
241 /// <summary>
242 /// Adds an entity object to the context
243 /// </summary>
244 /// <param name="context">The context.</param>
245 /// <param name="entitySetName">Name of the entity set.</param>
246 /// <param name="entity">The entity.</param>
247 /// <param name="maxDepth">The max depth.</param>
248 public static void AddObject(
249 ObjectContext context,
250 string entitySetName,
251 IEntityWithRelationships entity,
252 int maxDepth)
253 {
254 // remove all relations
255 List<RelationsShipMapping> map = new List<RelationsShipMapping>();
256 RemoveRelationships(map, entity, -1, maxDepth);
257
258 // add the entity
259 context.AddObject(entitySetName, entity);
260
261 // recreate all relationships
262 AddRelationships(context, map, entity as EntityObject);
263 }
264
265 /// <summary>
266 /// Attaches an entity object to the context
267 /// </summary>
268 public static void Attach(ObjectContext context, EntityObject entity, int maxDepth)
269 {
270 // remove all relations
271 List<RelationsShipMapping> map = new List<RelationsShipMapping>();
272 RemoveRelationships(map, entity, -1, maxDepth);
273
274 // attach the entity
275 context.Attach(entity);
276
277 // recreate all relationships
278 AddRelationships(context, map, entity);
279 }
280
281 /// <summary>
282 /// Attaches all related entity object and recreates all relationships
283 /// </summary>
284 static void AddRelationships(ObjectContext context, List<RelationsShipMapping> map, EntityObject addedEntity)
285 {
286 int layer = -1;
287 // loop through all layers
288 do
289 {
290 layer++;
291 // return all entity object from a specific layer
292 IEnumerable<RelationsShipMapping> rs = from r in map
293 where r.Layer == layer
294 select r;
295
296 // check if there are no further related entity objects
297 if (rs.Count<RelationsShipMapping>() == 0)
298 break;
299
300 // move through the remembered mappings of the current layer
301 foreach (RelationsShipMapping mapping in rs)
302 {
303 // check if we need to attach the related entity object
304 IEntityWithKey entityWithKey = mapping.TargetEntity as IEntityWithKey;
305
306 if (entityWithKey != null && entityWithKey.EntityKey != null && entityWithKey.EntityKey.EntityKeyValues != null)
307 {
308 // search entities in the object context.
309 mapping.TargetEntity = GetCurrentEntityObject(context, mapping.TargetEntity as EntityObject, addedEntity);
310 mapping.Entity = GetCurrentEntityObject(context, mapping.Entity as EntityObject, addedEntity);
311 }
312
313 // check if it´s a EntityCollection or EntityReference relationship
314 if (mapping.IsReference == false)
315 {
316 // add the related collection entity object
317 MethodInfo mi = typeof(RelationshipManager).GetMethod("GetRelatedCollection").MakeGenericMethod(mapping.TargetEntity.GetType());
318
319 object col = mi.Invoke(
320 mapping.Entity.RelationshipManager,
321 new object[] { mapping.RelationshipName, mapping.TargetRole });
322
323 MethodInfo miContains = col.GetType().GetMethod("Contains");
324 var isAdded = miContains.Invoke(col, new object[] { mapping.TargetEntity });
325
326 if ((bool)isAdded == false && ((EntityObject)mapping.TargetEntity).EntityState != EntityState.Added)
327 {
328 MethodInfo miAdd = col.GetType().GetMethod("Add");
329 miAdd.Invoke(col, new object[] { mapping.TargetEntity });
330 }
331 }
332 else
333 {
334 // set the related reference entity object
335 MethodInfo mi = typeof(RelationshipManager).GetMethod("GetRelatedReference").MakeGenericMethod(mapping.TargetEntity.GetType());
336
337 EntityReference er = (EntityReference)mi.Invoke(
338 mapping.Entity.RelationshipManager,
339 new object[] { mapping.RelationshipName, mapping.TargetRole });
340 if (er.GetType().GetProperty("Value").GetValue(er, null) == null)
341 {
342 er.GetType().GetProperty("Value").SetValue(er, mapping.TargetEntity, null);
343 }
344 }
345 }
346 }
347 while (true);
348 }
349
350 private static IEntityWithRelationships GetCurrentEntityObject(ObjectContext context, EntityObject entity, EntityObject addedEntity)
351 {
352 if (entity.EntityState == EntityState.Detached)
353 {
354 object currentEntityInDb = null;
355 Type type = context.GetType();
356 context.MetadataWorkspace.LoadFromAssembly(type.Assembly);
357
358 if (context.TryGetObjectByKey(entity.EntityKey, out currentEntityInDb))
359 {
360 return currentEntityInDb as IEntityWithRelationships;
361 }
362 else
363 {
364 if (entity.EntityKey.EntitySetName == addedEntity.EntityKey.EntitySetName &&
365 entity.EntityKey.EntityKeyValues == addedEntity.EntityKey.EntityKeyValues)
366 {
367 return addedEntity;
368 }
369 else
370 {
371 context.Attach(entity);
372 }
373 }
374 }
375
376 return entity;
377 }
378
379 /// <summary>
380 /// Removes all related entity objects
381 /// </summary>
382 /// <param name="map">The map.</param>
383 /// <param name="entity">The entity.</param>
384 /// <param name="layer">The layer.</param>
385 /// <param name="maxDepth">The max depth.</param>
386 private static void RemoveRelationships(
387 List<RelationsShipMapping> map,
388 IEntityWithRelationships entity,
389 int layer,
390 int maxDepth)
391 {
392 // the layer represents the stack in the relationships
393 // for example: the related entities to main entity object is layer 0,
394 // and the related entites to the related entites is layer 1, etc...
395 layer++;
396
397 // get a collection of all related conceptual entities.
398 IEnumerable<IRelatedEnd> en = entity.RelationshipManager.GetAllRelatedEnds();
399
400 foreach (IRelatedEnd end in en)
401 {
402 // check if the relation is an EntityCollection
403 IEnumerable col = end as IEnumerable;
404
405 if (col != null)
406 {
407 string colRelationshipName = (string)col.GetType().GetProperty("RelationshipName").GetValue(col, null);
408
409 string colSourceRoleName = (string)col.GetType().GetProperty("SourceRoleName").GetValue(col, null);
410
411 string colTargetRoleName = (string)col.GetType().
412 GetProperty("TargetRoleName").GetValue(col, null);
413
414 List<IEntityWithRelationships> relationships = GetRelationships(col);
415
416 List<IEntityWithRelationships> mem = new List<IEntityWithRelationships>();
417
418 for (int idx = relationships.Count - 1; idx >= 0; idx--)
419 {
420 IEntityWithRelationships item = relationships[idx];
421
422 // check if the relationship is already added the map
423 int alreadyExists = (from c in map
424 where c.RelationshipName == colRelationshipName &&
425 c.TargetRole == colSourceRoleName
426 select c).Count();
427
428 if (alreadyExists > 0)
429 continue;
430
431 // remember the relationships and add it to the map
432 RelationsShipMapping mapping = new RelationsShipMapping();
433 mapping.Layer = layer;
434 mapping.RelationshipName = colRelationshipName;
435 mapping.TargetRole = colTargetRoleName;
436 mapping.Entity = entity;
437 mapping.TargetEntity = item;
438 mapping.IsReference = false;
439 map.Add(mapping);
440 mem.Add(item);
441 RemoveRelationships(map, item, layer, maxDepth);
442 }
443
444 // remove the entity collection relationships
445 foreach (IEntityWithRelationships item in mem)
446 {
447 end.Remove(item);
448 }
449 }
450
451 // check if the relation is an EntityReference
452 EntityReference er = end as EntityReference;
453
454 if (er != null)
455 {
456 System.Reflection.PropertyInfo pValue = er.GetType().GetProperty("Value");
457 IEntityWithRelationships value = (IEntityWithRelationships)pValue.GetValue(er, null);
458
459 if (value == null || er == null)
460 continue;
461
462 // check if the reference is already added the map
463 int alreadyExists = (from c in map
464 where c.RelationshipName == er.RelationshipName &&
465 c.TargetRole == er.SourceRoleName
466 select c).Count();
467
468 if (alreadyExists > 0)
469 continue;
470
471 // remember the relationships and add it to the map
472 RelationsShipMapping mapping = new RelationsShipMapping();
473 mapping.Layer = layer;
474 mapping.RelationshipName = er.RelationshipName;
475 mapping.TargetRole = er.TargetRoleName;
476 mapping.Entity = entity;
477 mapping.TargetEntity = value;
478 mapping.IsReference = true;
479 map.Add(mapping);
480
481 // remove the entity reference relationships
482 pValue.SetValue(er, null, null);
483 RemoveRelationships(map, value, layer, maxDepth);
484 }
485 }
486 }
487
488 /// <summary>
489 /// Gets the relationships.
490 /// </summary>
491 /// <param name="col">The col.</param>
492 /// <returns>The IEntityWithRelationships collection.</returns>
493 private static List<IEntityWithRelationships> GetRelationships(IEnumerable col)
494 {
495 IEnumerator enu = col.GetEnumerator();
496 List<IEntityWithRelationships> relationships = new List<IEntityWithRelationships>();
497 while (enu.MoveNext())
498 {
499 relationships.Add(enu.Current as IEntityWithRelationships);
500 }
501
502 return relationships;
503 }
504
505 /// <summary>
506 /// Used to remember relationships in the entity object
507 /// </summary>
508 internal class RelationsShipMapping
509 {
510 public int Layer { get; set; }
511 public string RelationshipName { get; set; }
512 public string TargetRole { get; set; }
513 public IEntityWithRelationships Entity { get; set; }
514 public IEntityWithRelationships TargetEntity { get; set; }
515 public bool IsReference { get; set; }
516 }
517 }
Noch einen Hinweis zum Schluss: Ich setze den Workaround zurzeit in einer Situation ein, in der dieser gut funktioniert. Die Rekursionstiefe ist hierbei auf 2 Ebenen gesetzt. Dieses Beispiel verarbeitet zwar auch Collections, ist aber nicht für Massenoperationen geeignet.
Bleibt nur zu hoffen, dass in der Version 2 soetwas nicht mehr notwendig ist. ;-)
- 0 Kommentar(e)


Mein Kommentar