Skip to content

Commit 4ca2097

Browse files
authored
Merge pull request #110 from microsoft/andrueastman/fixExcessSubscriptions
Fix for duplicate subscription creation in InMemoryBackingStore
2 parents 807aecc + 91209a6 commit 4ca2097

File tree

4 files changed

+67
-18
lines changed

4 files changed

+67
-18
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.3.1] - 2023-08-08
11+
12+
### Fixed
13+
14+
- Fixed a bug where excess duplicate subscriptions would be created on the same property in the backing store causing performance issues in some scenarios. Related to https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/1994
15+
1016
## [1.3.0] - 2023-08-01
1117

1218
### Added

Microsoft.Kiota.Abstractions.Tests/Store/InMemoryBackingStoreTests.cs

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
using System;
1+
using System;
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Linq;
5+
using System.Reflection;
56
using Microsoft.Kiota.Abstractions.Store;
67
using Microsoft.Kiota.Abstractions.Tests.Mocks;
78
using Xunit;
@@ -71,7 +72,7 @@ public void TestsBackingStoreEmbeddedInModel()
7172
};
7273
// Assert by retrieving only changed values
7374
testUser.BackingStore.ReturnOnlyChangedValues = true;
74-
var changedValues = testUser.BackingStore.Enumerate();
75+
var changedValues = testUser.BackingStore.Enumerate().ToArray();
7576
Assert.NotEmpty(changedValues);
7677
Assert.Single(changedValues);
7778
Assert.Equal("businessPhones", changedValues.First().Key);
@@ -123,7 +124,7 @@ public void TestsBackingStoreEmbeddedInModelWithCollectionPropertyReplacedWithNe
123124
};
124125
// Assert by retrieving only changed values
125126
testUser.BackingStore.ReturnOnlyChangedValues = true;
126-
var changedValues = testUser.BackingStore.Enumerate();
127+
var changedValues = testUser.BackingStore.Enumerate().ToArray();
127128
Assert.NotEmpty(changedValues);
128129
Assert.Single(changedValues);
129130
Assert.Equal("businessPhones", changedValues.First().Key);
@@ -148,11 +149,11 @@ public void TestsBackingStoreEmbeddedInModelWithCollectionPropertyReplacedWithNu
148149
testUser.BusinessPhones = null;
149150
// Assert by retrieving only changed values
150151
testUser.BackingStore.ReturnOnlyChangedValues = true;
151-
var changedValues = testUser.BackingStore.Enumerate();
152+
var changedValues = testUser.BackingStore.Enumerate().ToArray();
152153
Assert.NotEmpty(changedValues);
153154
Assert.Single(changedValues);
154155
Assert.Equal("businessPhones", changedValues.First().Key);
155-
var changedValuesToNull = testUser.BackingStore.EnumerateKeysForValuesChangedToNull();
156+
var changedValuesToNull = testUser.BackingStore.EnumerateKeysForValuesChangedToNull().ToArray();
156157
Assert.NotEmpty(changedValuesToNull);
157158
Assert.Single(changedValuesToNull);
158159
Assert.Equal("businessPhones", changedValues.First().Key);
@@ -175,7 +176,7 @@ public void TestsBackingStoreEmbeddedInModelWithCollectionPropertyModifiedByAdd(
175176
testUser.BusinessPhones.Add("+1 234 567 891");
176177
// Assert by retrieving only changed values
177178
testUser.BackingStore.ReturnOnlyChangedValues = true;
178-
var changedValues = testUser.BackingStore.Enumerate();
179+
var changedValues = testUser.BackingStore.Enumerate().ToArray();
179180
Assert.NotEmpty(changedValues);
180181
Assert.Single(changedValues);
181182
Assert.Equal("businessPhones", changedValues.First().Key);
@@ -199,13 +200,17 @@ public void TestsBackingStoreEmbeddedInModelWithBySettingNestedIBackedModel()
199200
};
200201
// Assert by retrieving only changed values
201202
testUser.BackingStore.ReturnOnlyChangedValues = true;
202-
var changedValues = testUser.BackingStore.Enumerate();
203+
var changedValues = testUser.BackingStore.Enumerate().ToArray();
203204
Assert.NotEmpty(changedValues);
204205
Assert.Single(changedValues);
205206
Assert.Equal("manager", changedValues.First().Key);
206207
var manager = changedValues.First().Value as TestEntity;
207208
Assert.NotNull(manager);
208209
Assert.Equal("2fe22fe5-1132-42cf-90f9-1dc17e325a74",manager.Id);
210+
var testUserSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.BackingStore);
211+
Assert.Empty(testUserSubscriptions);// subscription only is added in nested store
212+
var managerSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.Manager.BackingStore);
213+
Assert.Single(managerSubscriptions);
209214
}
210215
[Fact]
211216
public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModel()
@@ -227,13 +232,17 @@ public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModel()
227232
};
228233
// Assert by retrieving only changed values
229234
testUser.BackingStore.ReturnOnlyChangedValues = true;
230-
var changedValues = testUser.BackingStore.Enumerate();
235+
var changedValues = testUser.BackingStore.Enumerate().ToArray();
231236
Assert.NotEmpty(changedValues);
232237
Assert.Single(changedValues);
233238
Assert.Equal("manager", changedValues.First().Key);//Backingstore should detect manager property changed
234239
var manager = changedValues.First().Value as TestEntity;
235240
Assert.NotNull(manager);
236241
Assert.Equal("2fe22fe5-1132-42cf-90f9-1dc17e325a74",manager.Id);
242+
var testUserSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.BackingStore);
243+
Assert.Empty(testUserSubscriptions);// subscription only is added in nested store
244+
var managerSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.Manager.BackingStore);
245+
Assert.Single(managerSubscriptions);
237246
}
238247
[Fact]
239248
public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModelReturnsAllNestedProperties()
@@ -256,17 +265,21 @@ public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModelRetu
256265
// Assert by retrieving only changed values
257266
testUser.BackingStore.ReturnOnlyChangedValues = true;
258267
testUser.Manager.BackingStore.ReturnOnlyChangedValues = true;
259-
var changedValues = testUser.BackingStore.Enumerate();
268+
var changedValues = testUser.BackingStore.Enumerate().ToArray();
260269
Assert.NotEmpty(changedValues);
261270
Assert.Single(changedValues);
262-
Assert.Equal("manager", changedValues.First().Key);//Backingstore should detect manager property changed
271+
Assert.Equal("manager", changedValues.First().Key);//BackingStore should detect manager property changed
263272
var changedNestedValues = testUser.Manager.BackingStore.Enumerate().ToDictionary(x => x.Key, y=> y.Value);
264273
Assert.Equal(4,changedNestedValues.Count);
265274
Assert.True(changedNestedValues.ContainsKey("id"));
266275
Assert.True(changedNestedValues.ContainsKey("businessPhones"));
267276
var manager = changedValues.First().Value as TestEntity;
268277
Assert.NotNull(manager);
269278
Assert.Equal("2fe22fe5-1132-42cf-90f9-1dc17e325a74",manager.Id);
279+
var testUserSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.BackingStore);
280+
Assert.Empty(testUserSubscriptions);// subscription only is added in nested store
281+
var managerSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.Manager.BackingStore);
282+
Assert.Single(managerSubscriptions);
270283
}
271284
[Fact]
272285
public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModelCollectionProperty()
@@ -291,13 +304,17 @@ public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModelColl
291304
};
292305
// Assert by retrieving only changed values
293306
testUser.BackingStore.ReturnOnlyChangedValues = true;
294-
var changedValues = testUser.BackingStore.Enumerate();
307+
var changedValues = testUser.BackingStore.Enumerate().ToArray();
295308
Assert.NotEmpty(changedValues);
296309
Assert.Single(changedValues);
297310
Assert.Equal("colleagues", changedValues.First().Key);//Backingstore should detect manager property changed
298311
var colleagues = testUser.BackingStore.Get<List<TestEntity>>("colleagues");
299312
Assert.NotNull(colleagues);
300313
Assert.Equal("2fe22fe5-1132-42cf-90f9-1dc17e325a74",colleagues[0].Id);
314+
var testUserSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.BackingStore);
315+
Assert.Empty(testUserSubscriptions);// subscription only is added in nested store
316+
var colleagueSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.Colleagues[0].BackingStore);
317+
Assert.Single(colleagueSubscriptions);// only one subscription to be invoked for the collection "colleagues"
301318
}
302319
[Fact]
303320
public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModelCollectionPropertyReturnsAllNestedProperties()
@@ -323,14 +340,18 @@ public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModelColl
323340
// Assert by retrieving only changed values
324341
testUser.BackingStore.ReturnOnlyChangedValues = true;
325342
testUser.Colleagues[0].BackingStore.ReturnOnlyChangedValues = true; //serializer will do this.
326-
var changedValues = testUser.BackingStore.Enumerate();
343+
var changedValues = testUser.BackingStore.Enumerate().ToArray();
327344
Assert.NotEmpty(changedValues);
328345
Assert.Single(changedValues);
329-
Assert.Equal("colleagues", changedValues.First().Key);//Backingstore should detect manager property changed
346+
Assert.Equal("colleagues", changedValues.First().Key);//BackingStore should detect manager property changed
330347
var changedNestedValues = testUser.Colleagues[0].BackingStore.Enumerate().ToDictionary(x => x.Key, y=> y.Value);
331348
Assert.Equal(4,changedNestedValues.Count);
332349
Assert.True(changedNestedValues.ContainsKey("id"));
333350
Assert.True(changedNestedValues.ContainsKey("businessPhones"));
351+
var testUserSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.BackingStore);
352+
Assert.Empty(testUserSubscriptions);// subscription only is added in nested store
353+
var colleagueSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.Colleagues[0].BackingStore);
354+
Assert.Single(colleagueSubscriptions);// only one subscription to be invoked for the collection "colleagues"
334355
}
335356
[Fact]
336357
public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModelCollectionPropertyWithExtraValueReturnsAllNestedProperties()
@@ -357,7 +378,7 @@ public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModelColl
357378
// Assert by retrieving only changed values
358379
testUser.BackingStore.ReturnOnlyChangedValues = true;
359380
testUser.Colleagues.First().BackingStore.ReturnOnlyChangedValues = true; //serializer will do this.
360-
var changedValues = testUser.BackingStore.Enumerate();
381+
var changedValues = testUser.BackingStore.Enumerate().ToArray();
361382
Assert.NotEmpty(changedValues);
362383
Assert.Single(changedValues);
363384
Assert.Equal("colleagues", changedValues.First().Key);//Backingstore should detect manager property changed
@@ -367,6 +388,10 @@ public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModelColl
367388
Assert.True(changedNestedValues.ContainsKey("businessPhones"));
368389
var businessPhones = ((Tuple<ICollection, int>)changedNestedValues["businessPhones"]).Item1;
369390
Assert.Equal(2, businessPhones.Count);
391+
var testUserSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.BackingStore);
392+
Assert.Empty(testUserSubscriptions);// subscription only is added in nested store
393+
var colleagueSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.Colleagues[0].BackingStore);
394+
Assert.Single(colleagueSubscriptions);// only one subscription to be invoked for the collection "colleagues"
370395
}
371396
[Fact]
372397
public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModelCollectionPropertyWithExtraIBackedModelValueReturnsAllNestedProperties()
@@ -396,7 +421,7 @@ public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModelColl
396421
// Assert by retrieving only changed values
397422
testUser.BackingStore.ReturnOnlyChangedValues = true;
398423
testUser.Colleagues[0].BackingStore.ReturnOnlyChangedValues = true; //serializer will do this.
399-
var changedValues = testUser.BackingStore.Enumerate().ToDictionary(x => x.Key, y=> y.Value);;
424+
var changedValues = testUser.BackingStore.Enumerate().ToDictionary(x => x.Key, y=> y.Value);
400425
Assert.NotEmpty(changedValues);
401426
Assert.Single(changedValues);
402427
Assert.Equal("colleagues", changedValues.First().Key);//Backingstore should detect manager property changed
@@ -410,6 +435,24 @@ public void TestsBackingStoreEmbeddedInModelWithByUpdatingNestedIBackedModelColl
410435
Assert.True(changedNestedValues.ContainsKey("businessPhones"));
411436
var businessPhones = ((Tuple<ICollection, int>)changedNestedValues["businessPhones"]).Item1;
412437
Assert.Single(businessPhones);
438+
var testUserSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.BackingStore);
439+
Assert.Empty(testUserSubscriptions);// subscription only is added in nested store
440+
var colleagueSubscriptions = GetSubscriptionsPropertyFromBackingStore(testUser.Colleagues[0].BackingStore);
441+
Assert.Single(colleagueSubscriptions);// only one subscription to be invoked for the collection "colleagues"
442+
}
443+
444+
/// <summary>
445+
/// Helper function to pull out the private `subscriptions` collection property from the InMemoryBackingStore class
446+
/// </summary>
447+
/// <param name="backingStore"></param>
448+
/// <returns></returns>
449+
private static IDictionary<string, Action<string, object, object>> GetSubscriptionsPropertyFromBackingStore(IBackingStore backingStore)
450+
{
451+
if(backingStore is not InMemoryBackingStore inMemoryBackingStore)
452+
return default;
453+
454+
var subscriptionsFieldInfo = typeof(InMemoryBackingStore).GetField("subscriptions", BindingFlags.NonPublic | BindingFlags.Instance);
455+
return (IDictionary<string, Action<string, object, object>>)subscriptionsFieldInfo?.GetValue(inMemoryBackingStore);
413456
}
414457
}
415458
}

src/Microsoft.Kiota.Abstractions.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<PackageProjectUrl>https://aka.ms/kiota/docs</PackageProjectUrl>
1515
<EmbedUntrackedSources>true</EmbedUntrackedSources>
1616
<Deterministic>true</Deterministic>
17-
<VersionPrefix>1.3.0</VersionPrefix>
17+
<VersionPrefix>1.3.1</VersionPrefix>
1818
<VersionSuffix></VersionSuffix>
1919
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
2020
<SignAssembly>false</SignAssembly>

src/store/InMemoryBackingStore.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public void Set<T>(string key, T? value)
7474
{
7575
backedModel.BackingStore.InitializationCompleted = false;// All its properties are dirty as the model has been touched.
7676
Set(key, value);
77-
});
77+
},key); // use property name(key) as subscriptionId to prevent excess subscription creation in the event this is called again
7878
}
7979
// if its an IBackedModel collection property to the store, subscribe to item properties' BackingStores and use the events to flag the collection property is "dirty"
8080
if(value is ICollection collectionValues)
@@ -86,7 +86,7 @@ public void Set<T>(string key, T? value)
8686
model.BackingStore.Subscribe((keyString, oldObject, newObject) =>
8787
{
8888
Set(key, value);
89-
});
89+
},key);// use property name(key) as subscriptionId to prevent excess subscription creation in the event this is called again
9090
});
9191
}
9292

0 commit comments

Comments
 (0)