|
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