@@ -39,74 +39,109 @@ public SqlServerCollectionParameterFactory(
3939 }
4040
4141 /// <inheritdoc />
42- public IQueryable < T > CreateScalarQuery < T > ( DbContext ctx , IEnumerable < T > values )
42+ public IQueryable < T > CreateScalarQuery < T > ( DbContext ctx , IReadOnlyCollection < T > values , bool applyDistinct )
4343 {
4444 var entityType = ctx . Model . GetEntityType ( typeof ( ScalarCollectionParameter < T > ) ) ;
45- var parameterInfo = _cache . GetOrAdd ( entityType , GetScalarParameterInfo < T > ) ;
45+ var parameterInfo = _cache . GetOrAdd ( entityType ,
46+ static ( type , args ) => GetScalarParameterInfo < T > ( args . _stringBuilderPool , args . _sqlGenerationHelper , type ) ,
47+ ( _stringBuilderPool , _sqlGenerationHelper ) ) ;
4648
47- var parameter = new SqlParameter
48- {
49- DbType = DbType . String ,
50- SqlDbType = SqlDbType . NVarChar ,
51- Value = parameterInfo . ParameterFactory ( values , _jsonSerializerOptions )
52- } ;
49+ var parameterValue = parameterInfo . ParameterFactory ( values , _jsonSerializerOptions ) ;
5350
54- return ctx . Set < ScalarCollectionParameter < T > > ( ) . FromSqlRaw ( parameterInfo . Statement , parameter ) . Select ( e => e . Value ) ;
51+ return ctx . Set < ScalarCollectionParameter < T > > ( )
52+ . FromSqlRaw ( applyDistinct ? parameterInfo . StatementWithDistinct : parameterInfo . Statement ,
53+ CreateTopParameter ( parameterValue ) ,
54+ CreateJsonParameter ( parameterValue ) )
55+ . Select ( e => e . Value ) ;
5556 }
5657
5758 /// <inheritdoc />
58- public IQueryable < T > CreateComplexQuery < T > ( DbContext ctx , IEnumerable < T > objects )
59+ public IQueryable < T > CreateComplexQuery < T > ( DbContext ctx , IReadOnlyCollection < T > objects , bool applyDistinct )
5960 where T : class
6061 {
6162 var entityType = ctx . Model . GetEntityType ( typeof ( T ) ) ;
6263 var parameterInfo = _cache . GetOrAdd ( entityType , GetComplexParameterInfo < T > ) ;
6364
64- var parameter = new SqlParameter
65- {
66- DbType = DbType . String ,
67- SqlDbType = SqlDbType . NVarChar ,
68- Value = parameterInfo . ParameterFactory ( objects , _jsonSerializerOptions )
69- } ;
65+ var parameterValue = parameterInfo . ParameterFactory ( objects , _jsonSerializerOptions ) ;
7066
71- return ctx . Set < T > ( ) . FromSqlRaw ( parameterInfo . Statement , parameter ) ;
67+ return ctx . Set < T > ( ) . FromSqlRaw ( applyDistinct ? parameterInfo . StatementWithDistinct : parameterInfo . Statement ,
68+ CreateTopParameter ( parameterValue ) ,
69+ CreateJsonParameter ( parameterValue ) ) ;
7270 }
7371
74- private CollectionParameterInfo GetScalarParameterInfo < T > ( IEntityType entityType )
72+ private static SqlParameter CreateJsonParameter ( JsonCollectionParameter parameterValue )
7573 {
76- var storeObject = StoreObjectIdentifier . Create ( entityType , StoreObjectType . Table ) ?? throw new Exception ( $ "Could not create StoreObjectIdentifier for table '{ entityType . Name } '.") ;
74+ return new SqlParameter
75+ {
76+ DbType = DbType . String ,
77+ SqlDbType = SqlDbType . NVarChar ,
78+ Value = parameterValue
79+ } ;
80+ }
7781
78- var property = entityType . GetProperties ( ) . Single ( ) ;
82+ private static SqlParameter CreateTopParameter ( JsonCollectionParameter jsonCollection )
83+ {
84+ return new SqlParameter
85+ {
86+ DbType = DbType . Int64 ,
87+ SqlDbType = SqlDbType . BigInt ,
88+ Value = jsonCollection
89+ } ;
90+ }
7991
80- var columnName = property . GetColumnName ( storeObject ) ?? throw new Exception ( $ "The property '{ property . Name } ' has no column name.") ;
81- var escapedColumnName = _sqlGenerationHelper . DelimitIdentifier ( columnName ) ;
82- var columnType = property . GetColumnType ( storeObject ) ;
92+ private static CollectionParameterInfo GetScalarParameterInfo < T > (
93+ ObjectPool < StringBuilder > stringBuilderPool ,
94+ ISqlGenerationHelper sqlGenerationHelper ,
95+ IEntityType entityType )
96+ {
97+ var property = entityType . GetProperties ( ) . Single ( ) ;
8398 var converter = property . GetValueConverter ( ) ;
84- var sb = _stringBuilderPool . Get ( ) ;
99+
100+ return new CollectionParameterInfo ( BuildScalarStatement ( stringBuilderPool , sqlGenerationHelper , entityType , property , false ) ,
101+ BuildScalarStatement ( stringBuilderPool , sqlGenerationHelper , entityType , property , true ) ,
102+ CreateParameterFactory < T > ( converter ) ) ;
103+ }
104+
105+ private static string BuildScalarStatement (
106+ ObjectPool < StringBuilder > stringBuilderPool ,
107+ ISqlGenerationHelper sqlGenerationHelper ,
108+ IEntityType entityType ,
109+ IProperty property ,
110+ bool applyDistinct )
111+ {
112+ var sb = stringBuilderPool . Get ( ) ;
85113
86114 try
87115 {
88- sb . Append ( "SELECT " ) . Append ( escapedColumnName ) . Append ( " FROM OPENJSON({0}, '$') WITH (" )
89- . Append ( escapedColumnName ) . Append ( " " ) . Append ( columnType ) . Append ( " '$')" ) ;
116+ var storeObject = StoreObjectIdentifier . Create ( entityType , StoreObjectType . Table ) ?? throw new Exception ( $ "Could not create StoreObjectIdentifier for table '{ entityType . Name } '.") ;
117+ var columnType = property . GetColumnType ( storeObject ) ;
118+ var columnName = property . GetColumnName ( storeObject ) ?? throw new Exception ( $ "The property '{ property . Name } ' has no column name.") ;
119+ var escapedColumnName = sqlGenerationHelper . DelimitIdentifier ( columnName ) ;
120+
121+ sb . Append ( "SELECT " ) ;
122+
123+ if ( applyDistinct )
124+ sb . Append ( "DISTINCT " ) ;
90125
91- var parameterFactory = CreateParameterFactory < T > ( converter ) ;
126+ sb . Append ( "TOP({0}) " ) . Append ( escapedColumnName ) . Append ( " FROM OPENJSON({1}, '$') WITH (" )
127+ . Append ( escapedColumnName ) . Append ( " " ) . Append ( columnType ) . Append ( " '$')" ) ;
92128
93- return new CollectionParameterInfo ( sb . ToString ( ) , parameterFactory ) ;
129+ return sb . ToString ( ) ;
94130 }
95131 finally
96132 {
97- _stringBuilderPool . Return ( sb ) ;
133+ stringBuilderPool . Return ( sb ) ;
98134 }
99135 }
100136
101137 private CollectionParameterInfo GetComplexParameterInfo < T > ( IEntityType entityType )
102138 {
103- var sqlStatement = CreateSqlStatementForComplexType ( entityType ) ;
104- var parameterFactory = CreateParameterFactory < T > ( null ) ;
105-
106- return new CollectionParameterInfo ( sqlStatement , parameterFactory ) ;
139+ return new CollectionParameterInfo ( CreateSqlStatementForComplexType ( entityType , false ) ,
140+ CreateSqlStatementForComplexType ( entityType , true ) ,
141+ CreateParameterFactory < T > ( null ) ) ;
107142 }
108143
109- private string CreateSqlStatementForComplexType ( IEntityType entityType )
144+ private string CreateSqlStatementForComplexType ( IEntityType entityType , bool withDistinct )
110145 {
111146 var sb = _stringBuilderPool . Get ( ) ;
112147 var withClause = _stringBuilderPool . Get ( ) ;
@@ -117,6 +152,11 @@ private string CreateSqlStatementForComplexType(IEntityType entityType)
117152
118153 sb . Append ( "SELECT " ) ;
119154
155+ if ( withDistinct )
156+ sb . Append ( "DISTINCT " ) ;
157+
158+ sb . Append ( "TOP({0}) " ) ;
159+
120160 var isFirst = true ;
121161
122162 foreach ( var property in entityType . GetProperties ( ) )
@@ -131,14 +171,13 @@ private string CreateSqlStatementForComplexType(IEntityType entityType)
131171 var escapedColumnName = _sqlGenerationHelper . DelimitIdentifier ( columnName ) ;
132172 var columnType = property . GetColumnType ( storeObject ) ?? throw new Exception ( $ "The property '{ property . Name } ' has no column type.") ;
133173
134- sb . Append ( "[" ) . Append ( columnName ) . Append ( "]" ) ;
135-
174+ sb . Append ( escapedColumnName ) ;
136175 withClause . Append ( escapedColumnName ) . Append ( " " ) . Append ( columnType ) . Append ( $ " '$.{ property . Name } '") ;
137176
138177 isFirst = false ;
139178 }
140179
141- sb . Append ( " FROM OPENJSON({0 }, '$') WITH (" ) . Append ( withClause ) . Append ( ")" ) ;
180+ sb . Append ( " FROM OPENJSON({1 }, '$') WITH (" ) . Append ( withClause ) . Append ( ")" ) ;
142181
143182 return sb . ToString ( ) ;
144183 }
@@ -153,7 +192,7 @@ private static Func<IEnumerable, JsonSerializerOptions, JsonCollectionParameter>
153192 ValueConverter ? converter )
154193 {
155194 if ( converter is null )
156- return ( values , options ) => new JsonCollectionParameter < T > ( ( IEnumerable < T > ) values , options ) ;
195+ return static ( values , options ) => new JsonCollectionParameter < T > ( ( IReadOnlyCollection < T > ) values , options ) ;
157196
158197 var itemType = typeof ( T ) ;
159198 var parameterType = typeof ( JsonCollectionParameter < , > ) . MakeGenericType ( itemType , converter . ProviderClrType ) ;
@@ -163,13 +202,13 @@ private static Func<IEnumerable, JsonSerializerOptions, JsonCollectionParameter>
163202
164203 var ctor = parameterType . GetConstructors ( ) . Single ( ) ;
165204 var ctorCall = Expression . New ( ctor ,
166- Expression . Convert ( valuesParam , typeof ( IEnumerable < T > ) ) ,
205+ Expression . Convert ( valuesParam , typeof ( IReadOnlyCollection < T > ) ) ,
167206 optionsParam ,
168207 Expression . Constant ( converter . ConvertToProvider ) ) ;
169208
170209 return Expression . Lambda < Func < IEnumerable , JsonSerializerOptions , JsonCollectionParameter > > ( ctorCall , valuesParam , optionsParam )
171210 . Compile ( ) ;
172211 }
173212
174- private readonly record struct CollectionParameterInfo ( string Statement , Func < IEnumerable , JsonSerializerOptions , JsonCollectionParameter > ParameterFactory ) ;
213+ private readonly record struct CollectionParameterInfo ( string Statement , string StatementWithDistinct , Func < IEnumerable , JsonSerializerOptions , JsonCollectionParameter > ParameterFactory ) ;
175214}
0 commit comments