Welcome to mirror list, hosted at ThFree Co, Russian Federation.

RelationshipConstraintValidator.cs « Internal « Update « Mapping « Data « System « System.Data.Entity « referencesource « class « mcs - github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: aa5e72d1ddeec3ac7c2744451904b485be11e415 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
//---------------------------------------------------------------------
// <copyright file="RelationshipConstraintValidator.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------

namespace System.Data.Mapping.Update.Internal
{
    using System.Collections.Generic;
    using System.Data.Common;
    using System.Data.Common.Utils;
    using System.Data.Entity;
    using System.Data.Metadata.Edm;
    using System.Diagnostics;
    using System.Globalization;
    using System.Linq;

    internal partial class UpdateTranslator
    {
        /// <summary>
        /// Class validating relationship cardinality constraints. Only reasons about constraints that can be inferred
        /// by examining change requests from the store.
        /// (no attempt is made to ensure consistency of the store subsequently, since this would require pulling in all
        /// values from the store).
        /// </summary>
        private class RelationshipConstraintValidator
        {
            #region Constructor
            internal RelationshipConstraintValidator(UpdateTranslator updateTranslator)
            {
                m_existingRelationships = new Dictionary<DirectionalRelationship, DirectionalRelationship>(EqualityComparer<DirectionalRelationship>.Default);
                m_impliedRelationships = new Dictionary<DirectionalRelationship, IEntityStateEntry>(EqualityComparer<DirectionalRelationship>.Default);
                m_referencingRelationshipSets = new Dictionary<EntitySet, List<AssociationSet>>(EqualityComparer<EntitySet>.Default);
                m_updateTranslator = updateTranslator;
            }
            #endregion

            #region Fields
            /// <summary>
            /// Relationships registered in the validator.
            /// </summary>
            private readonly Dictionary<DirectionalRelationship, DirectionalRelationship> m_existingRelationships;

            /// <summary>
            /// Relationships the validator determines are required based on registered entities.
            /// </summary>
            private readonly Dictionary<DirectionalRelationship, IEntityStateEntry> m_impliedRelationships;

            /// <summary>
            /// Cache used to store relationship sets with ends bound to entity sets.
            /// </summary>
            private readonly Dictionary<EntitySet, List<AssociationSet>> m_referencingRelationshipSets;

            /// <summary>
            /// Update translator containing session context.
            /// </summary>
            private readonly UpdateTranslator m_updateTranslator;
            #endregion

            #region Methods
            /// <summary>
            /// Add an entity to be tracked by the validator. Requires that the input describes an entity.
            /// </summary>
            /// <param name="stateEntry">State entry for the entity being tracked.</param>
            internal void RegisterEntity(IEntityStateEntry stateEntry)
            {
                EntityUtil.CheckArgumentNull(stateEntry, "stateEntry");

                if (EntityState.Added == stateEntry.State || EntityState.Deleted == stateEntry.State)
                {
                    // We only track added and deleted entities because modifications to entities do not affect
                    // cardinality constraints. Relationships are based on end keys, and it is not
                    // possible to modify key values.
                    Debug.Assert(null != (object)stateEntry.EntityKey, "entity state entry must have an entity key");
                    EntityKey entityKey = EntityUtil.CheckArgumentNull(stateEntry.EntityKey, "stateEntry.EntityKey");
                    EntitySet entitySet = (EntitySet)stateEntry.EntitySet;
                    EntityType entityType = EntityState.Added == stateEntry.State ?
                        GetEntityType(stateEntry.CurrentValues) :
                        GetEntityType(stateEntry.OriginalValues);

                    // figure out relationship set ends that are associated with this entity set
                    foreach (AssociationSet associationSet in GetReferencingAssocationSets(entitySet))
                    {
                        // describe unidirectional relationships in which the added entity is the "destination"
                        var ends = associationSet.AssociationSetEnds;
                        foreach (var fromEnd in ends)
                        {
                            foreach (var toEnd in ends)
                            {
                                // end to itself does not describe an interesting relationship subpart
                                if (object.ReferenceEquals(toEnd.CorrespondingAssociationEndMember, 
                                    fromEnd.CorrespondingAssociationEndMember)) { continue; }

                                // skip ends that don't target the current entity set
                                if (!toEnd.EntitySet.EdmEquals(entitySet)) { continue; }

                                // skip ends that aren't required
                                if (0 == MetadataHelper.GetLowerBoundOfMultiplicity(
                                    fromEnd.CorrespondingAssociationEndMember.RelationshipMultiplicity)) { continue; }

                                // skip ends that don't target the current entity type
                                if (!MetadataHelper.GetEntityTypeForEnd(toEnd.CorrespondingAssociationEndMember)
                                    .IsAssignableFrom(entityType)) { continue; }

                                // register the relationship so that we know it's required
                                DirectionalRelationship relationship = new DirectionalRelationship(entityKey, fromEnd.CorrespondingAssociationEndMember, 
                                    toEnd.CorrespondingAssociationEndMember, associationSet, stateEntry);
                                m_impliedRelationships.Add(relationship, stateEntry);
                            }
                        }
                    }
                }
            }

            // requires: input is an IExtendedDataRecord representing an entity
            // returns: entity type for the given record
            private static EntityType GetEntityType(DbDataRecord dbDataRecord)
            {
                IExtendedDataRecord extendedRecord = dbDataRecord as IExtendedDataRecord;
                Debug.Assert(extendedRecord != null);

                Debug.Assert(BuiltInTypeKind.EntityType == extendedRecord.DataRecordInfo.RecordType.EdmType.BuiltInTypeKind);
                return (EntityType)extendedRecord.DataRecordInfo.RecordType.EdmType;
            }

            /// <summary>
            /// Add a relationship to be tracked by the validator.
            /// </summary>
            /// <param name="associationSet">Relationship set to which the given record belongs.</param>
            /// <param name="record">Relationship record. Must conform to the type of the relationship set.</param>
            /// <param name="stateEntry">State entry for the relationship being tracked</param>
            internal void RegisterAssociation(AssociationSet associationSet, IExtendedDataRecord record, IEntityStateEntry stateEntry)
            {
                EntityUtil.CheckArgumentNull(associationSet, "relationshipSet");
                EntityUtil.CheckArgumentNull(record, "record");
                EntityUtil.CheckArgumentNull(stateEntry, "stateEntry");

                Debug.Assert(associationSet.ElementType.Equals(record.DataRecordInfo.RecordType.EdmType));

                // retrieve the ends of the relationship
                Dictionary<string, EntityKey> endNameToKeyMap = new Dictionary<string, EntityKey>(
                    StringComparer.Ordinal);
                foreach (FieldMetadata field in record.DataRecordInfo.FieldMetadata)
                {
                    string endName = field.FieldType.Name;
                    EntityKey entityKey = (EntityKey)record.GetValue(field.Ordinal);
                    endNameToKeyMap.Add(endName, entityKey);
                }

                // register each unidirectional relationship subpart in the relationship instance
                var ends = associationSet.AssociationSetEnds;
                foreach (var fromEnd in ends)
                {
                    foreach (var toEnd in ends)
                    {
                        // end to itself does not describe an interesting relationship subpart
                        if (object.ReferenceEquals(toEnd.CorrespondingAssociationEndMember, fromEnd.CorrespondingAssociationEndMember)) 
                        { 
                            continue; 
                        }

                        EntityKey toEntityKey = endNameToKeyMap[toEnd.CorrespondingAssociationEndMember.Name];
                        DirectionalRelationship relationship = new DirectionalRelationship(toEntityKey, fromEnd.CorrespondingAssociationEndMember, 
                            toEnd.CorrespondingAssociationEndMember, associationSet, stateEntry);
                        AddExistingRelationship(relationship);
                    }
                }
            }

            /// <summary>
            /// Validates cardinality constraints for all added entities/relationships.
            /// </summary>
            internal void ValidateConstraints()
            {
                // ensure all expected relationships exist
                foreach (KeyValuePair<DirectionalRelationship, IEntityStateEntry> expected in m_impliedRelationships)
                {
                    DirectionalRelationship expectedRelationship = expected.Key;
                    IEntityStateEntry stateEntry = expected.Value;

                    // determine actual end cardinality
                    int count = GetDirectionalRelationshipCountDelta(expectedRelationship);

                    if (EntityState.Deleted == stateEntry.State)
                    {
                        // our cardinality expectations are reversed for delete (cardinality of 1 indicates
                        // we want -1 operation total)
                        count = -count;
                    }

                    // determine expected cardinality
                    int minimumCount = MetadataHelper.GetLowerBoundOfMultiplicity(expectedRelationship.FromEnd.RelationshipMultiplicity);
                    int? maximumCountDeclared = MetadataHelper.GetUpperBoundOfMultiplicity(expectedRelationship.FromEnd.RelationshipMultiplicity);
                    int maximumCount = maximumCountDeclared.HasValue ? maximumCountDeclared.Value : count; // negative value
                    // indicates unlimited cardinality

                    if (count < minimumCount || count > maximumCount)
                    {
                        // We could in theory "fix" the cardinality constraint violation by introducing surrogates,
                        // but we risk doing work on behalf of the user they don't want performed (e.g., deleting an
                        // entity or relationship the user has intentionally left untouched).
                        throw EntityUtil.UpdateRelationshipCardinalityConstraintViolation(
                            expectedRelationship.AssociationSet.Name, minimumCount, maximumCountDeclared,
                            TypeHelpers.GetFullName(expectedRelationship.ToEntityKey.EntityContainerName, expectedRelationship.ToEntityKey.EntitySetName), 
                            count, expectedRelationship.FromEnd.Name,
                            stateEntry);
                    }
                }

                // ensure actual relationships have required ends
                foreach (DirectionalRelationship actualRelationship in m_existingRelationships.Keys)
                {
                    int addedCount;
                    int deletedCount;
                    actualRelationship.GetCountsInEquivalenceSet(out addedCount, out deletedCount);
                    int absoluteCount = Math.Abs(addedCount - deletedCount);
                    int minimumCount = MetadataHelper.GetLowerBoundOfMultiplicity(actualRelationship.FromEnd.RelationshipMultiplicity);
                    int? maximumCount = MetadataHelper.GetUpperBoundOfMultiplicity(actualRelationship.FromEnd.RelationshipMultiplicity);

                    // Check that we haven't inserted or deleted too many relationships
                    if (maximumCount.HasValue)
                    {
                        EntityState? violationType = default(EntityState?);
                        int? violationCount = default(int?);
                        if (addedCount > maximumCount.Value)
                        {
                            violationType = EntityState.Added;
                            violationCount = addedCount;
                        }
                        else if (deletedCount > maximumCount.Value)
                        {
                            violationType = EntityState.Deleted;
                            violationCount = deletedCount;
                        }
                        if (violationType.HasValue)
                        {
                            throw EntityUtil.Update(Strings.Update_RelationshipCardinalityViolation(maximumCount.Value,
                                violationType.Value, actualRelationship.AssociationSet.ElementType.FullName,
                                actualRelationship.FromEnd.Name, actualRelationship.ToEnd.Name, violationCount.Value),
                                null, actualRelationship.GetEquivalenceSet().Select(reln => reln.StateEntry));

                        }
                    }

                    // We care about the case where there is a relationship but no entity when
                    // the relationship and entity map to the same table. If there is a relationship
                    // with 1..1 cardinality to the entity and the relationship is being added or deleted,
                    // it is required that the entity is also added or deleted.
                    if (1 == absoluteCount && 1 == minimumCount && 1 == maximumCount) // 1..1 relationship being added/deleted
                    {
                        bool isAdd = addedCount > deletedCount;

                        // Ensure the entity is also being added or deleted
                        IEntityStateEntry entityEntry;

                        // Identify the following error conditions:
                        // - the entity is not being modified at all
                        // - the entity is being modified, but not in the way we expect (it's not being added or deleted)
                        if (!m_impliedRelationships.TryGetValue(actualRelationship, out entityEntry) ||
                            (isAdd && EntityState.Added != entityEntry.State) ||
                            (!isAdd && EntityState.Deleted != entityEntry.State))
                        {
                            throw EntityUtil.UpdateEntityMissingConstraintViolation(actualRelationship.AssociationSet.Name,
                                actualRelationship.ToEnd.Name, actualRelationship.StateEntry);
                        }
                    }
                }
            }

            /// <summary>
            /// Determines the net change in relationship count.
            /// For instance, if the directional relationship is added 2 times and deleted 3, the return value is -1.
            /// </summary>
            private int GetDirectionalRelationshipCountDelta(DirectionalRelationship expectedRelationship)
            {
                // lookup up existing relationship from expected relationship
                DirectionalRelationship existingRelationship;
                if (m_existingRelationships.TryGetValue(expectedRelationship, out existingRelationship))
                {
                    int addedCount;
                    int deletedCount;
                    existingRelationship.GetCountsInEquivalenceSet(out addedCount, out deletedCount);
                    return addedCount - deletedCount;
                }
                else
                {
                    // no modifications to the relationship... return 0 (no net change)
                    return 0;
                }
            }

            private void AddExistingRelationship(DirectionalRelationship relationship)
            {
                DirectionalRelationship existingRelationship;
                if (m_existingRelationships.TryGetValue(relationship, out existingRelationship))
                {
                    existingRelationship.AddToEquivalenceSet(relationship);
                }
                else
                {
                    m_existingRelationships.Add(relationship, relationship);
                }
            }

            /// <summary>
            /// Determine which relationship sets reference the given entity set.
            /// </summary>
            /// <param name="entitySet">Entity set for which to identify relationships</param>
            /// <returns>Relationship sets referencing the given entity set</returns>
            private IEnumerable<AssociationSet> GetReferencingAssocationSets(EntitySet entitySet)
            {
                List<AssociationSet> relationshipSets;

                // check if this information is cached
                if (!m_referencingRelationshipSets.TryGetValue(entitySet, out relationshipSets))
                {
                    relationshipSets = new List<AssociationSet>();

                    // relationship sets must live in the same container as the entity sets they reference
                    EntityContainer container = entitySet.EntityContainer;
                    foreach (EntitySetBase extent in container.BaseEntitySets)
                    {
                        AssociationSet associationSet = extent as AssociationSet;

                        if (null != associationSet && !associationSet.ElementType.IsForeignKey)
                        {
                            foreach (var end in associationSet.AssociationSetEnds)
                            {
                                if (end.EntitySet.Equals(entitySet))
                                {
                                    relationshipSets.Add(associationSet);
                                    break;
                                }
                            }
                        }
                    }

                    // add referencing relationship information to the cache
                    m_referencingRelationshipSets.Add(entitySet, relationshipSets);
                }

                return relationshipSets;
            }
            #endregion

            #region Nested types
            /// <summary>
            /// An instance of an actual or expected relationship. This class describes one direction
            /// of the relationship. 
            /// </summary>
            private class DirectionalRelationship : IEquatable<DirectionalRelationship>
            {
                /// <summary>
                /// Entity key for the entity being referenced by the relationship.
                /// </summary>
                internal readonly EntityKey ToEntityKey;

                /// <summary>
                /// Name of the end referencing the entity key.
                /// </summary>
                internal readonly AssociationEndMember FromEnd;

                /// <summary>
                /// Name of the end the entity key references.
                /// </summary>
                internal readonly AssociationEndMember ToEnd;

                /// <summary>
                /// State entry containing this relationship.
                /// </summary>
                internal readonly IEntityStateEntry StateEntry;

                /// <summary>
                /// Reference to the relationship set.
                /// </summary>
                internal readonly AssociationSet AssociationSet;

                /// <summary>
                /// Reference to next 'equivalent' relationship in circular linked list.
                /// </summary>
                private DirectionalRelationship _equivalenceSetLinkedListNext;

                private readonly int _hashCode;

                internal DirectionalRelationship(EntityKey toEntityKey, AssociationEndMember fromEnd, AssociationEndMember toEnd, AssociationSet associationSet, IEntityStateEntry stateEntry)
                {
                    ToEntityKey = EntityUtil.CheckArgumentNull(toEntityKey, "toEntityKey");
                    FromEnd = EntityUtil.CheckArgumentNull(fromEnd, "fromEnd");
                    ToEnd = EntityUtil.CheckArgumentNull(toEnd, "toEnd");
                    AssociationSet = EntityUtil.CheckArgumentNull(associationSet, "associationSet");
                    StateEntry = EntityUtil.CheckArgumentNull(stateEntry, "stateEntry");
                    _equivalenceSetLinkedListNext = this;

                    _hashCode = toEntityKey.GetHashCode() ^
                        fromEnd.GetHashCode() ^
                        toEnd.GetHashCode() ^
                        associationSet.GetHashCode();
                }

                /// <summary>
                /// Requires: 'other' must refer to the same relationship metadata and the same target entity and
                /// must not already be a part of an equivalent set.
                /// Adds the given relationship to linked list containing all equivalent relationship instances
                /// for this relationship (e.g. all orders associated with a specific customer)
                /// </summary>
                internal void AddToEquivalenceSet(DirectionalRelationship other)
                {
                    Debug.Assert(null != other, "other must not be null");
                    Debug.Assert(this.Equals(other), "other must be another instance of the same relationship target");
                    Debug.Assert(Object.ReferenceEquals(other._equivalenceSetLinkedListNext, other), "other must not be part of an equivalence set yet");
                    DirectionalRelationship currentSuccessor = this._equivalenceSetLinkedListNext;
                    this._equivalenceSetLinkedListNext = other;
                    other._equivalenceSetLinkedListNext = currentSuccessor;
                }

                /// <summary>
                /// Returns all relationships in equivalence set.
                /// </summary>
                internal IEnumerable<DirectionalRelationship> GetEquivalenceSet()
                {
                    // yield everything in circular linked list
                    DirectionalRelationship current = this;
                    do
                    {
                        yield return current;
                        current = current._equivalenceSetLinkedListNext;
                    }
                    while (!object.ReferenceEquals(current, this));
                }

                /// <summary>
                /// Determines the number of add and delete operations contained in this equivalence set.
                /// </summary>
                internal void GetCountsInEquivalenceSet(out int addedCount, out int deletedCount)
                {
                    addedCount = 0;
                    deletedCount = 0;
                    // yield everything in circular linked list
                    DirectionalRelationship current = this;
                    do
                    {
                        if (current.StateEntry.State == EntityState.Added)
                        {
                            addedCount++;
                        }
                        else if (current.StateEntry.State == EntityState.Deleted)
                        {
                            deletedCount++;
                        }
                        current = current._equivalenceSetLinkedListNext;
                    }
                    while (!object.ReferenceEquals(current, this));
                }

                public override int GetHashCode()
                {
                    return _hashCode;
                }

                public bool Equals(DirectionalRelationship other)
                {
                    if (object.ReferenceEquals(this, other)) { return true; }
                    if (null == other) { return false; }
                    if (ToEntityKey != other.ToEntityKey) { return false; }
                    if (AssociationSet != other.AssociationSet) { return false; }
                    if (ToEnd != other.ToEnd) { return false; }
                    if (FromEnd != other.FromEnd) { return false; }
                    return true;
                }

                public override bool Equals(object obj)
                {
                    Debug.Fail("use only typed Equals method");
                    return Equals(obj as DirectionalRelationship);
                }

                public override string ToString()
                {
                    return String.Format(CultureInfo.InvariantCulture, "{0}.{1}-->{2}: {3}",
                        AssociationSet.Name, FromEnd.Name, ToEnd.Name, StringUtil.BuildDelimitedList(ToEntityKey.EntityKeyValues, null, null));
                }
            }
            #endregion
        }
    }
}