11using System ;
22using System . Collections . Generic ;
3+ using System . Linq ;
34using Microsoft . Data . SqlClient ;
45using FastMember ;
56using SqlBulkHelpers . Interfaces ;
@@ -9,11 +10,14 @@ namespace SqlBulkHelpers
910 internal static class TypeCache
1011 {
1112 public static readonly Type SqlBulkHelperIdentitySetter = typeof ( ISqlBulkHelperIdentitySetter ) ;
13+ public static readonly Type SqlBulkHelperBigIntIdentitySetter = typeof ( ISqlBulkHelperBigIntIdentitySetter ) ;
1214 }
1315
1416 //BBernard - Base Class for future flexibility...
1517 internal abstract class BaseSqlBulkHelper < T > : BaseHelper < T > where T : class
1618 {
19+ protected static Type CachedEntityType { get ; } = typeof ( T ) ;
20+
1721 #region Constructors
1822
1923 /// <inheritdoc/>
@@ -60,7 +64,7 @@ SqlMergeMatchQualifierExpression matchQualifierExpression
6064 protected class MergeResult
6165 {
6266 public int RowNumber { get ; set ; }
63- public int IdentityId { get ; set ; }
67+ public long IdentityId { get ; set ; }
6468 //public SqlBulkHelpersMergeAction MergeAction { get; set; }
6569 }
6670
@@ -76,6 +80,11 @@ SqlMergeMatchQualifierExpression sqlMatchQualifierExpression
7680
7781 bool uniqueMatchValidationEnabled = sqlMatchQualifierExpression . AssertArgumentIsNotNull ( nameof ( sqlMatchQualifierExpression ) ) . ThrowExceptionIfNonUniqueMatchesOccur ;
7882 bool hasIdentityColumn = identityColumnDefinition != null ;
83+ //Small performance improvement here by pre-determining which, if any, identity setters may be implemented by the client;
84+ // since this isn't the most often use case it's helpful to more efficiently skip the type checks while processing...
85+ bool implementsIntIdentitySetter = TypeCache . SqlBulkHelperIdentitySetter . IsAssignableFrom ( CachedEntityType ) ;
86+ bool implementsBigIntIdentitySetter = TypeCache . SqlBulkHelperBigIntIdentitySetter . IsAssignableFrom ( CachedEntityType ) ;
87+ bool identitySetterInterfaceSupported = implementsIntIdentitySetter || implementsBigIntIdentitySetter ;
7988
8089 //If there was no Identity Column or the validation of Unique Merge actions was disabled then we can
8190 // short circuit the post-processing of results as there is nothing to do...
@@ -89,10 +98,7 @@ SqlMergeMatchQualifierExpression sqlMatchQualifierExpression
8998 // we attempt to use Reflection to set the value...
9099 string identityPropertyName = null ;
91100
92- Type entityType = typeof ( T ) ;
93- TypeAccessor fastTypeAccessor = TypeAccessor . Create ( entityType ) ;
94-
95- if ( hasIdentityColumn && ! TypeCache . SqlBulkHelperIdentitySetter . IsAssignableFrom ( entityType ) )
101+ if ( hasIdentityColumn && ! identitySetterInterfaceSupported )
96102 {
97103 var processingDefinition = SqlBulkHelpersProcessingDefinition . GetProcessingDefinition < T > ( identityColumnDefinition ) ;
98104 identityPropertyName = processingDefinition . IdentityPropDefinition ? . PropertyName ;
@@ -108,9 +114,11 @@ SqlMergeMatchQualifierExpression sqlMatchQualifierExpression
108114 // so there's no reason to filter the merge results anymore; this is more performant.
109115 var uniqueMatchesHashSet = new HashSet < int > ( ) ;
110116
111- var entityResultsList = new List < T > ( ) ;
117+ var identitySetterAction = hasIdentityColumn
118+ ? ResolveIdentitySetterAction ( entityList , identityPropertyName )
119+ : null ;
112120
113- //foreach ( var mergeResult in mergeResultsList.Where(r => r.MergeAction.HasFlag(SqlBulkHelpersMergeAction.Insert)))
121+ var entityResultsList = new List < T > ( ) ;
114122 foreach ( var mergeResult in mergeResultsList )
115123 {
116124 //ONLY Process uniqueness validation if necessary... otherwise skip the logic altogether.
@@ -135,20 +143,7 @@ SqlMergeMatchQualifierExpression sqlMatchQualifierExpression
135143 //ONLY Process Identity value updates if appropriate... otherwise skip the logic altogether.
136144 //NOTE: List is 0 (zero) based, but our RowNumber is 1 (one) based.
137145 var entity = entityList [ mergeResult . RowNumber - 1 ] ;
138- if ( hasIdentityColumn )
139- {
140- //BBernard
141- //If the entity supports our interface we can set the value with native performance via the Interface!
142- if ( entity is ISqlBulkHelperIdentitySetter identitySetterEntity )
143- {
144- identitySetterEntity . SetIdentityId ( mergeResult . IdentityId ) ;
145- }
146- else
147- {
148- //GENERICALLY Set the Identity Value to the Int value returned, this eliminates any dependency on a Base Class!
149- fastTypeAccessor [ entity , identityPropertyName ] = mergeResult . IdentityId ;
150- }
151- }
146+ identitySetterAction ? . Invoke ( entity , mergeResult ) ;
152147
153148 entityResultsList . Add ( entity ) ;
154149 }
@@ -158,5 +153,63 @@ SqlMergeMatchQualifierExpression sqlMatchQualifierExpression
158153 return entityResultsList ;
159154 }
160155
156+ private Action < T , MergeResult > ResolveIdentitySetterAction ( List < T > entityList , string identityPropertyName )
157+ {
158+ var sampleEntity = entityList . FirstOrDefault ( ) ;
159+ switch ( sampleEntity )
160+ {
161+ case null :
162+ //Break out to end for Invalid Operation handling...
163+ break ;
164+ //BBernard
165+ //For Performance if the entity type supports our Interfaces then we use those to set the Identity ID delegating all logic to the class to handle.
166+ case ISqlBulkHelperIdentitySetter _:
167+ //Downcast our MergeResult long IdentityId to int...
168+ return ( entity , mergeResult ) => ( ( ISqlBulkHelperIdentitySetter ) entity ) . SetIdentityId ( ( int ) mergeResult . IdentityId ) ;
169+ case ISqlBulkHelperBigIntIdentitySetter _:
170+ return ( entity , mergeResult ) => ( ( ISqlBulkHelperBigIntIdentitySetter ) entity ) . SetIdentityId ( mergeResult . IdentityId ) ;
171+ default :
172+ {
173+ //Create our TypeAccessor once here, so it can be captured by scope in our Action but is NOT CREATED on each Action execution!
174+ //NOTE: This also helps encapsulate our use of TypeAccessor in case we choose another approach to setting the property in the future!
175+ var fastTypeAccessor = TypeAccessor . Create ( CachedEntityType ) ;
176+ var identityPropType = fastTypeAccessor [ sampleEntity , identityPropertyName ] ? . GetType ( ) ;
177+ if ( identityPropType != null )
178+ {
179+ //BBernard
180+ //For Performance we try to identity the primary Integer types and implement the Setter Action with explicit casting, however
181+ // as a fallback we will attempt to generically convert the type via Convert.ChangeType() for really strange edge cases where the Model type is
182+ // something awkward... (Heaven forbid a string), but hey we'll try to make it work.
183+ if ( identityPropType == typeof ( long ) ) //BIGINT Sql Type
184+ {
185+ //MergeResult IdentityId is already a Long to support the superset of any other Int property types by down-casting...
186+ return ( entity , mergeResult ) => fastTypeAccessor [ entity , identityPropertyName ] = mergeResult . IdentityId ;
187+ }
188+ else if ( identityPropType == typeof ( int ) ) //INT Sql Type
189+ {
190+ return ( entity , mergeResult ) => fastTypeAccessor [ entity , identityPropertyName ] = ( int ) mergeResult . IdentityId ;
191+ }
192+ else if ( identityPropType == typeof ( short ) ) //SMALLINT Sql Type
193+ {
194+ return ( entity , mergeResult ) => fastTypeAccessor [ entity , identityPropertyName ] = ( short ) mergeResult . IdentityId ;
195+ }
196+ else if ( identityPropType == typeof ( byte ) ) //TINYINT Sql Type
197+ {
198+ return ( entity , mergeResult ) => fastTypeAccessor [ entity , identityPropertyName ] = ( byte ) mergeResult . IdentityId ;
199+ }
200+ else //For NUMERIC(X, 0) Sql Type or any other, we attempt to generically change the type to match...
201+ {
202+ return ( entity , mergeResult ) => fastTypeAccessor [ entity , identityPropertyName ] = Convert . ChangeType ( mergeResult . IdentityId , identityPropType ) ;
203+ }
204+ }
205+
206+ //Break out to end for Invalid Operation handling...
207+ break ;
208+ }
209+ }
210+
211+ return ( entity , mergeResult ) => throw new InvalidOperationException ( $ "Unable to properly map the Identity value result into the entity Property [{ identityPropertyName } ] of type [{ entity . GetType ( ) } ].") ;
212+ }
213+
161214 }
162215}
0 commit comments