|
14 | 14 | using NHibernate.Event; |
15 | 15 | using NHibernate.Persister.Collection; |
16 | 16 | using NHibernate.Persister.Entity; |
| 17 | +using NHibernate.Proxy; |
17 | 18 | using NHibernate.Type; |
18 | 19 | using NHibernate.Util; |
19 | 20 |
|
@@ -85,81 +86,76 @@ public async Task CascadeOnAsync(IEntityPersister persister, object parent, obje |
85 | 86 | } |
86 | 87 |
|
87 | 88 | /// <summary> Cascade an action to the child or children</summary> |
88 | | - private Task CascadePropertyAsync(object parent, object child, IType type, CascadeStyle style, string propertyName, object anything, bool isCascadeDeleteEnabled, CancellationToken cancellationToken) |
| 89 | + private async Task CascadePropertyAsync(object parent, object child, IType type, CascadeStyle style, string propertyName, object anything, bool isCascadeDeleteEnabled, CancellationToken cancellationToken) |
89 | 90 | { |
90 | | - if (cancellationToken.IsCancellationRequested) |
91 | | - { |
92 | | - return Task.FromCanceled<object>(cancellationToken); |
93 | | - } |
94 | | - try |
| 91 | + cancellationToken.ThrowIfCancellationRequested(); |
| 92 | + if (child != null) |
95 | 93 | { |
96 | | - if (child != null) |
| 94 | + if (type.IsAssociationType) |
97 | 95 | { |
98 | | - if (type.IsAssociationType) |
| 96 | + IAssociationType associationType = (IAssociationType)type; |
| 97 | + if (CascadeAssociationNow(associationType)) |
99 | 98 | { |
100 | | - IAssociationType associationType = (IAssociationType)type; |
101 | | - if (CascadeAssociationNow(associationType)) |
102 | | - { |
103 | | - return CascadeAssociationAsync(parent, child, type, style, anything, isCascadeDeleteEnabled, cancellationToken); |
104 | | - } |
105 | | - } |
106 | | - else if (type.IsComponentType) |
107 | | - { |
108 | | - return CascadeComponentAsync(parent, child, (IAbstractComponentType)type, propertyName, anything, cancellationToken); |
| 99 | + await (CascadeAssociationAsync(parent, child, type, style, anything, isCascadeDeleteEnabled, cancellationToken)).ConfigureAwait(false); |
109 | 100 | } |
110 | 101 | } |
111 | | - else |
| 102 | + else if (type.IsComponentType) |
112 | 103 | { |
113 | | - // potentially we need to handle orphan deletes for one-to-ones here... |
114 | | - if (type.IsEntityType && ((EntityType)type).IsLogicalOneToOne()) |
| 104 | + await (CascadeComponentAsync(parent, child, (IAbstractComponentType)type, propertyName, anything, cancellationToken)).ConfigureAwait(false); |
| 105 | + } |
| 106 | + } |
| 107 | + else |
| 108 | + { |
| 109 | + // potentially we need to handle orphan deletes for one-to-ones here... |
| 110 | + if (type.IsEntityType && ((EntityType)type).IsLogicalOneToOne()) |
| 111 | + { |
| 112 | + // We have a physical or logical one-to-one and from previous checks we know we |
| 113 | + // have a null value. See if the attribute cascade settings and action-type require |
| 114 | + // orphan checking |
| 115 | + if (style.HasOrphanDelete && action.DeleteOrphans) |
115 | 116 | { |
116 | | - // We have a physical or logical one-to-one and from previous checks we know we |
117 | | - // have a null value. See if the attribute cascade settings and action-type require |
118 | | - // orphan checking |
119 | | - if (style.HasOrphanDelete && action.DeleteOrphans) |
| 117 | + // value is orphaned if loaded state for this property shows not null |
| 118 | + // because it is currently null. |
| 119 | + EntityEntry entry = eventSource.PersistenceContext.GetEntry(parent); |
| 120 | + if (entry != null && entry.Status != Status.Saving) |
120 | 121 | { |
121 | | - // value is orphaned if loaded state for this property shows not null |
122 | | - // because it is currently null. |
123 | | - EntityEntry entry = eventSource.PersistenceContext.GetEntry(parent); |
124 | | - if (entry != null && entry.Status != Status.Saving) |
| 122 | + object loadedValue; |
| 123 | + if (componentPathStack.Count == 0) |
| 124 | + { |
| 125 | + // association defined on entity |
| 126 | + loadedValue = entry.GetLoadedValue(propertyName); |
| 127 | + |
| 128 | + // Check this is not a null carrying proxy. The no-proxy load is currently handled by |
| 129 | + // putting a proxy (!) flagged for unwrapping (even for non-constrained one-to-one, |
| 130 | + // which association may be null) in the loadedState of the parent. The unwrap flag |
| 131 | + // causes it to support having a null implementation, instead of throwing an entity |
| 132 | + // not found error. |
| 133 | + loadedValue = await (eventSource.PersistenceContext.UnproxyAndReassociateAsync(loadedValue, cancellationToken)).ConfigureAwait(false); |
| 134 | + } |
| 135 | + else |
125 | 136 | { |
126 | | - EntityType entityType = (EntityType)type; |
127 | | - object loadedValue; |
128 | | - if (componentPathStack.Count == 0) |
129 | | - { |
130 | | - // association defined on entity |
131 | | - loadedValue = entry.GetLoadedValue(propertyName); |
132 | | - } |
133 | | - else |
134 | | - { |
135 | | - // association defined on component |
136 | | - // todo : this is currently unsupported because of the fact that |
137 | | - // we do not know the loaded state of this value properly |
138 | | - // and doing so would be very difficult given how components and |
139 | | - // entities are loaded (and how 'loaded state' is put into the |
140 | | - // EntityEntry). Solutions here are to either: |
141 | | - // 1) properly account for components as a 2-phase load construct |
142 | | - // 2) just assume the association was just now orphaned and |
143 | | - // issue the orphan delete. This would require a special |
144 | | - // set of SQL statements though since we do not know the |
145 | | - // orphaned value, something a delete with a subquery to |
146 | | - // match the owner. |
147 | | - loadedValue = null; |
148 | | - } |
| 137 | + // association defined on component |
| 138 | + // todo : this is currently unsupported because of the fact that |
| 139 | + // we do not know the loaded state of this value properly |
| 140 | + // and doing so would be very difficult given how components and |
| 141 | + // entities are loaded (and how 'loaded state' is put into the |
| 142 | + // EntityEntry). Solutions here are to either: |
| 143 | + // 1) properly account for components as a 2-phase load construct |
| 144 | + // 2) just assume the association was just now orphaned and |
| 145 | + // issue the orphan delete. This would require a special |
| 146 | + // set of SQL statements though since we do not know the |
| 147 | + // orphaned value, something a delete with a subquery to |
| 148 | + // match the owner. |
| 149 | + loadedValue = null; |
| 150 | + } |
149 | 151 |
|
150 | | - if (loadedValue != null) |
151 | | - { |
152 | | - return eventSource.DeleteAsync(entry.Persister.EntityName, loadedValue, false, null, cancellationToken); |
153 | | - } |
| 152 | + if (loadedValue != null) |
| 153 | + { |
| 154 | + await (eventSource.DeleteAsync(entry.Persister.EntityName, loadedValue, false, null, cancellationToken)).ConfigureAwait(false); |
154 | 155 | } |
155 | 156 | } |
156 | 157 | } |
157 | 158 | } |
158 | | - return Task.CompletedTask; |
159 | | - } |
160 | | - catch (System.Exception ex) |
161 | | - { |
162 | | - return Task.FromException<object>(ex); |
163 | 159 | } |
164 | 160 | } |
165 | 161 |
|
|
0 commit comments