From 777988ad638c1f0cfb4efab6c9186171dc2e4a76 Mon Sep 17 00:00:00 2001 From: Chanan Braunstein Date: Thu, 31 May 2018 19:46:27 -0600 Subject: [PATCH] Throw exception when deleting a model used by an association --- src/BlazorDB/BlazorDB.csproj | 2 +- src/BlazorDB/Storage/StorageManagerLoad.cs | 6 +- src/BlazorDB/Storage/StorageManagerSave.cs | 89 ++++++++++++++++------ src/BlazorDB/StorageSet.cs | 14 +--- src/Sample/Pages/Associations.cshtml | 16 ++++ src/Sample/global.json | 2 +- 6 files changed, 86 insertions(+), 43 deletions(-) diff --git a/src/BlazorDB/BlazorDB.csproj b/src/BlazorDB/BlazorDB.csproj index 2175514..10ef9ff 100644 --- a/src/BlazorDB/BlazorDB.csproj +++ b/src/BlazorDB/BlazorDB.csproj @@ -7,7 +7,7 @@ false 7.3 BlazorDB - 0.1.0 + 0.1.1 Chanan Braunstein Blazor localStorage Database In memory, persisted to localstorage, database for .net Blazor browser framework diff --git a/src/BlazorDB/Storage/StorageManagerLoad.cs b/src/BlazorDB/Storage/StorageManagerLoad.cs index c485496..4a19ae9 100644 --- a/src/BlazorDB/Storage/StorageManagerLoad.cs +++ b/src/BlazorDB/Storage/StorageManagerLoad.cs @@ -1,10 +1,10 @@ -using Microsoft.AspNetCore.Blazor; -using Microsoft.Extensions.DependencyInjection; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; +using Microsoft.AspNetCore.Blazor; +using Microsoft.Extensions.DependencyInjection; namespace BlazorDB.Storage { diff --git a/src/BlazorDB/Storage/StorageManagerSave.cs b/src/BlazorDB/Storage/StorageManagerSave.cs index ea1943e..1ef18fc 100644 --- a/src/BlazorDB/Storage/StorageManagerSave.cs +++ b/src/BlazorDB/Storage/StorageManagerSave.cs @@ -21,7 +21,8 @@ public int SaveContextToLocalStorage(StorageContext context) return total; } - private static IReadOnlyDictionary LoadMetadataList(StorageContext context, IEnumerable storageSets, Type contextType) + private static IReadOnlyDictionary LoadMetadataList(StorageContext context, + IEnumerable storageSets, Type contextType) { var map = new Dictionary(); foreach (var prop in storageSets) @@ -48,7 +49,7 @@ private static int SaveStorageSets(StorageContext context, int total, Type conte var storageSetValue = prop.GetValue(context); var modelType = prop.PropertyType.GetGenericArguments()[0]; var storageTableName = Util.GetStorageTableName(contextType, modelType); - + EnsureAllModelsHaveIds(storageSetValue, modelType, metadataMap); EnsureAllAssociationsHaveIds(context, storageSetValue, modelType, storageSets, metadataMap); @@ -63,36 +64,43 @@ private static int SaveStorageSets(StorageContext context, int total, Type conte return total; } - private static void EnsureAllAssociationsHaveIds(StorageContext context, object storageSetValue, Type modelType, List storageSets, IReadOnlyDictionary metadataMap) + private static void EnsureAllAssociationsHaveIds(StorageContext context, object storageSetValue, Type modelType, + List storageSets, IReadOnlyDictionary metadataMap) { var storageSetType = StorageManagerUtil.GenericStorageSetType.MakeGenericType(modelType); var method = storageSetType.GetMethod(StorageManagerUtil.GetEnumerator); - var enumerator = (IEnumerator)method.Invoke(storageSetValue, new object[] { }); + var enumerator = (IEnumerator) method.Invoke(storageSetValue, new object[] { }); while (enumerator.MoveNext()) { var model = enumerator.Current; foreach (var prop in model.GetType().GetProperties()) { - if (prop.GetValue(model) == null || (!StorageManagerUtil.IsInContext(storageSets, prop) && - !StorageManagerUtil.IsListInContext(storageSets, prop))) continue; - if (StorageManagerUtil.IsInContext(storageSets, prop)) EnsureOneAssociationHasId(context, prop.GetValue(model), prop.PropertyType, storageSets, metadataMap); - if (StorageManagerUtil.IsListInContext(storageSets, prop)) EnsureManyAssociationHasId(context, prop.GetValue(model), prop, storageSets, metadataMap); + if (prop.GetValue(model) == null || !StorageManagerUtil.IsInContext(storageSets, prop) && + !StorageManagerUtil.IsListInContext(storageSets, prop)) continue; + if (StorageManagerUtil.IsInContext(storageSets, prop)) + EnsureOneAssociationHasId(context, prop.GetValue(model), prop.PropertyType, storageSets, + metadataMap); + if (StorageManagerUtil.IsListInContext(storageSets, prop)) + EnsureManyAssociationHasId(context, prop.GetValue(model), prop, storageSets, metadataMap); } } } - private static void EnsureManyAssociationHasId(StorageContext context, object listObject, PropertyInfo prop, List storageSets, IReadOnlyDictionary metadataMap) + private static void EnsureManyAssociationHasId(StorageContext context, object listObject, PropertyInfo prop, + List storageSets, IReadOnlyDictionary metadataMap) { var method = listObject.GetType().GetMethod(StorageManagerUtil.GetEnumerator); - var enumerator = (IEnumerator)method.Invoke(listObject, new object[] { }); + var enumerator = (IEnumerator) method.Invoke(listObject, new object[] { }); while (enumerator.MoveNext()) { var model = enumerator.Current; - EnsureOneAssociationHasId(context, model, prop.PropertyType.GetGenericArguments()[0], storageSets, metadataMap); + EnsureOneAssociationHasId(context, model, prop.PropertyType.GetGenericArguments()[0], storageSets, + metadataMap); } } - private static void EnsureOneAssociationHasId(StorageContext context, object associatedModel, Type propType, List storageSets, IReadOnlyDictionary metadataMap) + private static void EnsureOneAssociationHasId(StorageContext context, object associatedModel, Type propType, + List storageSets, IReadOnlyDictionary metadataMap) { var idProp = GetIdProperty(associatedModel); var id = Convert.ToString(idProp.GetValue(associatedModel)); @@ -102,26 +110,56 @@ private static void EnsureOneAssociationHasId(StorageContext context, object ass metadata.MaxId = metadata.MaxId + 1; SaveAssociationModel(context, associatedModel, propType, storageSets, metadata.MaxId); } + else + { + EnsureAssociationModelExistsOrThrow(context, Convert.ToInt32(id), storageSets, propType); + } } - private static void EnsureAllModelsHaveIds(object storageSetValue, Type modelType, IReadOnlyDictionary metadataMap) + private static void EnsureAssociationModelExistsOrThrow(StorageContext context, int id, + IEnumerable storageSets, Type propType) + { + var q = from p in storageSets + where p.PropertyType.GetGenericArguments()[0] == propType + select p; + var storeageSetProp = q.Single(); + var storeageSet = storeageSetProp.GetValue(context); + var listProp = storeageSet.GetType().GetProperty(StorageManagerUtil.List, StorageManagerUtil.Flags); + var list = listProp.GetValue(storeageSet); + var method = list.GetType().GetMethod(StorageManagerUtil.GetEnumerator); + var enumerator = (IEnumerator) method.Invoke(list, new object[] { }); + var found = false; + while (enumerator.MoveNext()) + { + var model = enumerator.Current; + if (id != GetId(model)) continue; + found = true; + break; + } + + if (!found) + throw new InvalidOperationException( + $"A model of type: {propType.Name} with Id {id} was deleted but still being used by an association. Remove it from the association as well."); + } + + private static void EnsureAllModelsHaveIds(object storageSetValue, Type modelType, + IReadOnlyDictionary metadataMap) { var storageSetType = StorageManagerUtil.GenericStorageSetType.MakeGenericType(modelType); var method = storageSetType.GetMethod(StorageManagerUtil.GetEnumerator); var metadata = metadataMap[Util.GetFullyQualifiedTypeName(modelType)]; - var enumerator = (IEnumerator)method.Invoke(storageSetValue, new object[] { }); + var enumerator = (IEnumerator) method.Invoke(storageSetValue, new object[] { }); while (enumerator.MoveNext()) { var model = enumerator.Current; - if (GetId(model) == 0) - { - metadata.MaxId = metadata.MaxId + 1; - SetId(model, metadata.MaxId); - } + if (GetId(model) != 0) continue; + metadata.MaxId = metadata.MaxId + 1; + SetId(model, metadata.MaxId); } } - private static void SaveMetadata(string storageTableName, List guids, Type context, Type modelType, Metadata oldMetadata) + private static void SaveMetadata(string storageTableName, List guids, Type context, Type modelType, + Metadata oldMetadata) { var metadata = new Metadata { @@ -169,8 +207,8 @@ private static string ScanModelForAssociations(object model, List var result = serializedModel; foreach (var prop in model.GetType().GetProperties()) { - if (prop.GetValue(model) == null || (!StorageManagerUtil.IsInContext(storageSets, prop) && - !StorageManagerUtil.IsListInContext(storageSets, prop))) continue; + if (prop.GetValue(model) == null || !StorageManagerUtil.IsInContext(storageSets, prop) && + !StorageManagerUtil.IsListInContext(storageSets, prop)) continue; if (StorageManagerUtil.IsInContext(storageSets, prop)) result = FixOneAssociation(model, prop, result); if (StorageManagerUtil.IsListInContext(storageSets, prop)) result = FixManyAssociation(model, prop, result); @@ -203,18 +241,19 @@ private static string FixOneAssociation(object model, PropertyInfo prop, string return result; } - private static int SaveAssociationModel(StorageContext context, object associatedModel, Type propType, IEnumerable storageSets, int id) + private static int SaveAssociationModel(StorageContext context, object associatedModel, Type propType, + IEnumerable storageSets, int id) { var q = from p in storageSets where p.PropertyType.GetGenericArguments()[0] == propType - select p; + select p; var storeageSetProp = q.Single(); var storeageSet = storeageSetProp.GetValue(context); var listProp = storeageSet.GetType().GetProperty(StorageManagerUtil.List, StorageManagerUtil.Flags); var list = listProp.GetValue(storeageSet); var addMethod = list.GetType().GetMethod(StorageManagerUtil.Add); SetId(associatedModel, id); - addMethod.Invoke(list, new[] { associatedModel }); + addMethod.Invoke(list, new[] {associatedModel}); return id; } diff --git a/src/BlazorDB/StorageSet.cs b/src/BlazorDB/StorageSet.cs index 76d5806..121be53 100644 --- a/src/BlazorDB/StorageSet.cs +++ b/src/BlazorDB/StorageSet.cs @@ -85,19 +85,7 @@ IEnumerator IEnumerable.GetEnumerator() { return List.GetEnumerator(); } - - //TODO: Consider using an "Id table" - private int SetId(TModel item) - { - var prop = item.GetType().GetProperty("Id"); - if (prop == null) throw new ArgumentException("Model must have an Id property"); - var max = List.Select(i => (int) prop.GetValue(i)).Concat(new[] {0}).Max(); - - var id = max + 1; - prop.SetValue(item, id); - return id; - } - + private static int GetId(TModel item) { var prop = item.GetType().GetProperty("Id"); diff --git a/src/Sample/Pages/Associations.cshtml b/src/Sample/Pages/Associations.cshtml index 5da0f53..7f96bb0 100644 --- a/src/Sample/Pages/Associations.cshtml +++ b/src/Sample/Pages/Associations.cshtml @@ -18,6 +18,10 @@ Load Person +

Delete Test (Should throw exception)

+ +Add a Person with Addresses then delete Address +

Result

@if (_person != null) @@ -76,4 +80,16 @@ _person = Context.People[1]; StateHasChanged(); } + + void OnDelete(UIMouseEventArgs e) + { + var person = new Person { FirstName = "John", LastName = "Smith" }; + var address = new Address { Street = "221 Baker Streeet", City = "This should be a refrence to address since Address exists in the context" }; + person.HomeAddress = address; + Context.People.Add(person); + Context.SaveChanges(); + + Context.Addresses.RemoveAt(0); + Context.SaveChanges(); + } } \ No newline at end of file diff --git a/src/Sample/global.json b/src/Sample/global.json index 5502b6f..d65e3d5 100644 --- a/src/Sample/global.json +++ b/src/Sample/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "2.1.300-preview2-008533" + "version": "2.1.300" } }