1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
+ using System . Collections ;
5
+ using Microsoft . EntityFrameworkCore . Cosmos . Internal ;
6
+
4
7
namespace Microsoft . EntityFrameworkCore . Cosmos . ChangeTracking . Internal ;
5
8
6
9
/// <summary>
@@ -9,21 +12,30 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal;
9
12
/// any release. You should only use it directly in your code with extreme caution and knowing that
10
13
/// doing so can result in application failures when updating to a new Entity Framework Core release.
11
14
/// </summary>
12
- public sealed class StringDictionaryComparer < TElement , TCollection > : ValueComparer < TCollection >
13
- where TCollection : class , IEnumerable < KeyValuePair < string , TElement > >
15
+ public sealed class StringDictionaryComparer < TDictionary , TElement > : ValueComparer < object > , IInfrastructure < ValueComparer >
14
16
{
17
+ private static readonly MethodInfo CompareMethod = typeof ( StringDictionaryComparer < TDictionary , TElement > ) . GetMethod (
18
+ nameof ( Compare ) , BindingFlags . Static | BindingFlags . NonPublic , [ typeof ( object ) , typeof ( object ) , typeof ( ValueComparer ) ] ) ! ;
19
+
20
+ private static readonly MethodInfo GetHashCodeMethod = typeof ( StringDictionaryComparer < TDictionary , TElement > ) . GetMethod (
21
+ nameof ( GetHashCode ) , BindingFlags . Static | BindingFlags . NonPublic , [ typeof ( IEnumerable ) , typeof ( ValueComparer ) ] ) ! ;
22
+
23
+ private static readonly MethodInfo SnapshotMethod = typeof ( StringDictionaryComparer < TDictionary , TElement > ) . GetMethod (
24
+ nameof ( Snapshot ) , BindingFlags . Static | BindingFlags . NonPublic , [ typeof ( object ) , typeof ( ValueComparer ) ] ) ! ;
25
+
15
26
/// <summary>
16
27
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
17
28
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
18
29
/// any release. You should only use it directly in your code with extreme caution and knowing that
19
30
/// doing so can result in application failures when updating to a new Entity Framework Core release.
20
31
/// </summary>
21
- public StringDictionaryComparer ( ValueComparer elementComparer , bool readOnly )
32
+ public StringDictionaryComparer ( ValueComparer elementComparer )
22
33
: base (
23
- ( a , b ) => Compare ( a , b , ( ValueComparer < TElement > ) elementComparer ) ,
24
- o => GetHashCode ( o , ( ValueComparer < TElement > ) elementComparer ) ,
25
- source => Snapshot ( source , ( ValueComparer < TElement > ) elementComparer , readOnly ) )
34
+ CompareLambda ( elementComparer ) ,
35
+ GetHashCodeLambda ( elementComparer ) ,
36
+ SnapshotLambda ( elementComparer ) )
26
37
{
38
+ ElementComparer = elementComparer ;
27
39
}
28
40
29
41
/// <summary>
@@ -32,63 +44,136 @@ public StringDictionaryComparer(ValueComparer elementComparer, bool readOnly)
32
44
/// any release. You should only use it directly in your code with extreme caution and knowing that
33
45
/// doing so can result in application failures when updating to a new Entity Framework Core release.
34
46
/// </summary>
35
- public override Type Type
36
- => typeof ( TCollection ) ;
47
+ public ValueComparer ElementComparer { get ; }
48
+
49
+ ValueComparer IInfrastructure < ValueComparer > . Instance => ElementComparer ;
50
+
51
+ private static Expression < Func < object ? , object ? , bool > > CompareLambda ( ValueComparer elementComparer )
52
+ {
53
+ var prm1 = Expression . Parameter ( typeof ( object ) , "a" ) ;
54
+ var prm2 = Expression . Parameter ( typeof ( object ) , "b" ) ;
55
+
56
+ return Expression . Lambda < Func < object ? , object ? , bool > > (
57
+ Expression . Call (
58
+ CompareMethod ,
59
+ prm1 ,
60
+ prm2 ,
61
+ #pragma warning disable EF9100
62
+ elementComparer . ConstructorExpression ) ,
63
+ #pragma warning restore EF9100
64
+ prm1 ,
65
+ prm2 ) ;
66
+ }
67
+
68
+ private static Expression < Func < object , int > > GetHashCodeLambda ( ValueComparer elementComparer )
69
+ {
70
+ var prm = Expression . Parameter ( typeof ( object ) , "o" ) ;
71
+
72
+ return Expression . Lambda < Func < object , int > > (
73
+ Expression . Call (
74
+ GetHashCodeMethod ,
75
+ Expression . Convert (
76
+ prm ,
77
+ typeof ( IEnumerable ) ) ,
78
+ #pragma warning disable EF9100
79
+ elementComparer . ConstructorExpression ) ,
80
+ #pragma warning restore EF9100
81
+ prm ) ;
82
+ }
83
+
84
+ private static Expression < Func < object , object > > SnapshotLambda ( ValueComparer elementComparer )
85
+ {
86
+ var prm = Expression . Parameter ( typeof ( object ) , "source" ) ;
37
87
38
- private static bool Compare ( TCollection ? a , TCollection ? b , ValueComparer < TElement > elementComparer )
88
+ return Expression . Lambda < Func < object , object > > (
89
+ Expression . Call (
90
+ SnapshotMethod ,
91
+ prm ,
92
+ #pragma warning disable EF9100
93
+ elementComparer . ConstructorExpression ) ,
94
+ #pragma warning restore EF9100
95
+ prm ) ;
96
+ }
97
+
98
+ private static bool Compare ( object ? a , object ? b , ValueComparer elementComparer )
39
99
{
40
- if ( a is not IReadOnlyDictionary < string , TElement > aDict )
100
+ if ( ReferenceEquals ( a , b ) )
41
101
{
42
- return b is not IReadOnlyDictionary < string , TElement > ;
102
+ return true ;
43
103
}
44
104
45
- if ( b is not IReadOnlyDictionary < string , TElement > bDict || aDict . Count != bDict . Count )
105
+ if ( a is null )
46
106
{
47
- return false ;
107
+ return b is null ;
48
108
}
49
109
50
- if ( ReferenceEquals ( aDict , bDict ) )
110
+ if ( b is null )
51
111
{
52
- return true ;
112
+ return false ;
53
113
}
54
114
55
- foreach ( var ( key , element ) in aDict )
115
+ if ( a is IReadOnlyDictionary < string , TElement ? > aDictionary && b is IReadOnlyDictionary < string , TElement ? > bDictionary )
56
116
{
57
- if ( ! bDict . TryGetValue ( key , out var bValue )
58
- || ! elementComparer . Equals ( element , bValue ) )
117
+ if ( aDictionary . Count != bDictionary . Count )
59
118
{
60
119
return false ;
61
120
}
121
+
122
+ foreach ( var pair in aDictionary )
123
+ {
124
+ if ( ! bDictionary . TryGetValue ( pair . Key , out var bValue )
125
+ || ! elementComparer . Equals ( pair . Value , bValue ) )
126
+ {
127
+ return false ;
128
+ }
129
+ }
130
+
131
+ return true ;
62
132
}
63
133
64
- return true ;
134
+ throw new InvalidOperationException (
135
+ CosmosStrings . BadDictionaryType (
136
+ ( a is IDictionary < string , TElement ? > ? b : a ) . GetType ( ) . ShortDisplayName ( ) ,
137
+ typeof ( IDictionary < , > ) . MakeGenericType ( typeof ( string ) , elementComparer . Type ) . ShortDisplayName ( ) ) ) ;
65
138
}
66
139
67
- private static int GetHashCode ( TCollection source , ValueComparer < TElement > elementComparer )
140
+ private static int GetHashCode ( IEnumerable source , ValueComparer elementComparer )
68
141
{
142
+ if ( source is not IReadOnlyDictionary < string , TElement ? > sourceDictionary )
143
+ {
144
+ throw new InvalidOperationException (
145
+ CosmosStrings . BadDictionaryType (
146
+ source . GetType ( ) . ShortDisplayName ( ) ,
147
+ typeof ( IList < > ) . MakeGenericType ( elementComparer . Type ) . ShortDisplayName ( ) ) ) ;
148
+ }
149
+
69
150
var hash = new HashCode ( ) ;
70
- foreach ( var ( key , element ) in source )
151
+
152
+ foreach ( var pair in sourceDictionary )
71
153
{
72
- hash . Add ( key ) ;
73
- hash . Add ( element , elementComparer ) ;
154
+ hash . Add ( pair . Key ) ;
155
+ hash . Add ( pair . Value == null ? 0 : elementComparer . GetHashCode ( pair . Value ) ) ;
74
156
}
75
157
76
158
return hash . ToHashCode ( ) ;
77
159
}
78
160
79
- private static TCollection Snapshot ( TCollection source , ValueComparer < TElement > elementComparer , bool readOnly )
161
+ private static IReadOnlyDictionary < string , TElement ? > Snapshot ( object source , ValueComparer elementComparer )
80
162
{
81
- if ( readOnly )
163
+ if ( source is not IReadOnlyDictionary < string , TElement ? > sourceDictionary )
82
164
{
83
- return source ;
165
+ throw new InvalidOperationException (
166
+ CosmosStrings . BadDictionaryType (
167
+ source . GetType ( ) . ShortDisplayName ( ) ,
168
+ typeof ( IDictionary < , > ) . MakeGenericType ( typeof ( string ) , elementComparer . Type ) . ShortDisplayName ( ) ) ) ;
84
169
}
85
170
86
- var snapshot = new Dictionary < string , TElement > ( ( ( IReadOnlyDictionary < string , TElement > ) source ) . Count ) ;
87
- foreach ( var ( key , element ) in source )
171
+ var snapshot = new Dictionary < string , TElement ? > ( ) ;
172
+ foreach ( var pair in sourceDictionary )
88
173
{
89
- snapshot . Add ( key , element is null ? default ! : elementComparer . Snapshot ( element ) ) ;
174
+ snapshot [ pair . Key ] = pair . Value == null ? default : ( TElement ? ) elementComparer . Snapshot ( pair . Value ) ;
90
175
}
91
176
92
- return ( TCollection ) ( object ) snapshot ;
177
+ return snapshot ;
93
178
}
94
179
}
0 commit comments