public void Replace_EntityExistsMoreThanOnce() { ChangeSet changeSet = this.GenerateChangeSet(); ChangeSetEntry op1 = changeSet.ChangeSetEntries.Skip(0).First(); ChangeSetEntry op2 = changeSet.ChangeSetEntries.Skip(1).First(); ChangeSetEntry op3 = changeSet.ChangeSetEntries.Skip(2).First(); A currentEntity = new A(), originalEntity = new A(), returnedEntity = new A(); op1.Entity = currentEntity; op1.OriginalEntity = originalEntity; op2.Entity = currentEntity; op2.OriginalEntity = originalEntity; op3.Entity = currentEntity; op3.OriginalEntity = null; changeSet.Replace(currentEntity, returnedEntity); // Verify we returned the original Assert.AreSame(op1.Entity, currentEntity, "Expected to find the current entity."); Assert.AreSame(op1.OriginalEntity, originalEntity, "Expected to find the original entity."); Assert.AreSame(returnedEntity, changeSet.EntitiesToReplace[op1.Entity], "Expected to find the returned entity."); Assert.AreSame(op2.Entity, currentEntity, "Expected to find the current entity."); Assert.AreSame(op2.OriginalEntity, originalEntity, "Expected to find the original entity."); Assert.AreSame(returnedEntity, changeSet.EntitiesToReplace[op2.Entity], "Expected to find the returned entity."); Assert.AreSame(op3.Entity, currentEntity, "Expected to find the current entity."); Assert.IsNull(op3.OriginalEntity, "Expected to find a null original entity."); Assert.AreSame(returnedEntity, changeSet.EntitiesToReplace[op3.Entity], "Expected to find the returned entity."); }
private static string GetAuthorizeMethodName(ChangeSetEntry entry) { switch (entry.Type) { case ChangeSetEntryType.DataModification: DataModificationEntry dataModification = (DataModificationEntry)entry; string operationName = null; if (dataModification.IsNew) { operationName = ConventionBasedChangeSetConstants.AuthorizeMethodDataModificationInsert; } else if (dataModification.IsUpdate) { operationName = ConventionBasedChangeSetConstants.AuthorizeMethodDataModificationUpdate; } else if (dataModification.IsDelete) { operationName = ConventionBasedChangeSetConstants.AuthorizeMethodDataModificationDelete; } return(operationName + dataModification.EntitySetName); case ChangeSetEntryType.ActionInvocation: ActionInvocationEntry actionEntry = (ActionInvocationEntry)entry; return(ConventionBasedChangeSetConstants.AuthorizeMethodActionInvocationExecute + actionEntry.ActionName); default: throw new InvalidOperationException(string.Format( CultureInfo.InvariantCulture, Resources.InvalidChangeSetEntryType, entry.Type)); } }
public void Changeset_OriginalInvalidForInserts() { // can't specify an original for an insert operation TimestampEntityA curr = new TimestampEntityA { ID = 1, Version = new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 }, ValueA = "Foo", ValueB = "Bar" }; TimestampEntityA orig = new TimestampEntityA { ID = 1, Version = new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 }, ValueA = "x", ValueB = "x" }; ChangeSetEntry entry = new ChangeSetEntry(1, curr, orig, DomainOperation.Insert); ChangeSet cs = null; ExceptionHelper.ExpectInvalidOperationException(delegate { cs = new ChangeSet(new ChangeSetEntry[] { entry }); }, string.Format(Resource.InvalidChangeSet, Resource.InvalidChangeSet_InsertsCantHaveOriginal)); // get original should throw for insert operations entry = new ChangeSetEntry(1, curr, null, DomainOperation.Insert); cs = new ChangeSet(new ChangeSetEntry[] { entry }); ExceptionHelper.ExpectInvalidOperationException(delegate { cs.GetOriginal(curr); }, string.Format(Resource.ChangeSet_OriginalNotValidForInsert)); }
/// <inheritdoc/> public Task <bool> AuthorizeAsync( SubmitContext context, ChangeSetEntry entry, CancellationToken cancellationToken) { Ensure.NotNull(context, "context"); bool result = true; Type returnType = typeof(bool); string methodName = ConventionBasedChangeSetAuthorizer.GetAuthorizeMethodName(entry); MethodInfo method = this.targetType.GetQualifiedMethod(methodName); if (method != null && method.IsFamily && method.ReturnType == returnType) { object target = null; if (!method.IsStatic) { target = context.GetApiService <ApiBase>(); if (target == null || !this.targetType.IsAssignableFrom(target.GetType())) { return(Task.FromResult(result)); } } var parameters = method.GetParameters(); if (parameters.Length == 0) { result = (bool)method.Invoke(target, null); } } return(Task.FromResult(result)); }
public async Task DomainService_CallSubmitDirectly() { DomainServiceDescription description = DomainServiceDescription.GetDescription(typeof(DomainMethod_ValidProvider_MultipleMethods)); List <ChangeSetEntry> changeSetEntries = new List <ChangeSetEntry>(); ChangeSetEntry processCityOperation = new ChangeSetEntry(); processCityOperation.Entity = new City { Name = "Redmond", CountyName = "King", StateName = "WA" }; processCityOperation.DomainOperationEntry = description.GetCustomMethod(typeof(City), "ProcessCity"); processCityOperation.Operation = DomainOperation.Update; processCityOperation.EntityActions = new EntityActionCollection { { "ProcessCity", new object[] { new byte[] { byte.MaxValue, byte.MinValue, 123 } } } }; changeSetEntries.Add(processCityOperation); ChangeSet changeset = new ChangeSet(changeSetEntries); DomainMethod_ValidProvider_MultipleMethods myTestProvider = ServerTestHelper.CreateInitializedDomainService <DomainMethod_ValidProvider_MultipleMethods>(DomainOperationType.Submit); await myTestProvider.SubmitAsync(changeset, CancellationToken.None); // check that the domain services have invoked the domain method correctly by checking the internal variables set Assert.AreEqual <string>("ProcessCity_", myTestProvider.Invoked); Assert.AreEqual <int>(3, myTestProvider.InputData.Length); Assert.AreEqual <byte>(123, myTestProvider.InputData[2]); }
public void Submit_Tree_Success() { Order order = new Order { OrderID = 1, OrderDate = DateTime.Now }; Order_Detail d1 = new Order_Detail { ProductID = 1 }; Order_Detail d2 = new Order_Detail { ProductID = 2 }; Dictionary <string, int[]> detailsAssociation = new Dictionary <string, int[]>(); detailsAssociation.Add("Order_Details", new int[] { 2, 3 }); ChangeSetEntry[] changeSet = new ChangeSetEntry[] { new ChangeSetEntry { Id = 1, Entity = order, Operation = ChangeOperation.Insert, Associations = detailsAssociation }, new ChangeSetEntry { Id = 2, Entity = d1, Operation = ChangeOperation.Insert }, new ChangeSetEntry { Id = 3, Entity = d2, Operation = ChangeOperation.Insert } }; ChangeSetEntry[] resultChangeSet = this.ExecuteSubmit(TestConstants.CatalogUrl + "Submit", "Catalog", changeSet); Assert.Equal(3, resultChangeSet.Length); Assert.True(resultChangeSet.All(p => !p.HasError)); }
public void Submit_Validation_Failure() { Microsoft.Web.Http.Data.Test.Models.EF.Product newProduct = new Microsoft.Web.Http.Data.Test.Models.EF.Product { ProductID = 1, ProductName = String.Empty, UnitPrice = -1 }; Microsoft.Web.Http.Data.Test.Models.EF.Product updateProduct = new Microsoft.Web.Http.Data.Test.Models.EF.Product { ProductID = 1, ProductName = new string('x', 50), UnitPrice = 55.77M }; ChangeSetEntry[] changeSet = new ChangeSetEntry[] { new ChangeSetEntry { Id = 1, Entity = newProduct, Operation = ChangeOperation.Insert }, new ChangeSetEntry { Id = 2, Entity = updateProduct, Operation = ChangeOperation.Update } }; HttpResponseMessage response = this.ExecuteSelfHostRequest("http://testhost/NorthwindEFTest/Submit", "NorthwindEFTest", changeSet); changeSet = response.Content.ReadAsAsync<ChangeSetEntry[]>().Result; // errors for the new product ValidationResultInfo[] errors = changeSet[0].ValidationErrors.ToArray(); Assert.Equal(2, errors.Length); Assert.True(changeSet[0].HasError); // validation rule inferred from EF model Assert.Equal("ProductName", errors[0].SourceMemberNames.Single()); Assert.Equal("The ProductName field is required.", errors[0].Message); // validation rule coming from buddy class Assert.Equal("UnitPrice", errors[1].SourceMemberNames.Single()); Assert.Equal("The field UnitPrice must be between 0 and 1000000.", errors[1].Message); // errors for the updated product errors = changeSet[1].ValidationErrors.ToArray(); Assert.Equal(1, errors.Length); Assert.True(changeSet[1].HasError); // validation rule inferred from EF model Assert.Equal("ProductName", errors[0].SourceMemberNames.Single()); Assert.Equal("The field ProductName must be a string with a maximum length of 40.", errors[0].Message); }
public void Changeset_OriginalInvalidForInserts() { // can't specify an original for an insert operation Product curr = new Product { ProductID = 1 }; Product orig = new Product { ProductID = 1 }; ChangeSetEntry entry = new ChangeSetEntry { Id = 1, Entity = curr, OriginalEntity = orig, Operation = ChangeOperation.Insert }; ChangeSet cs = null; Assert.Throws <InvalidOperationException>(delegate { cs = new ChangeSet(new ChangeSetEntry[] { entry }); }, String.Format(Resource.InvalidChangeSet, Resource.InvalidChangeSet_InsertsCantHaveOriginal)); // get original should throw for insert operations entry = new ChangeSetEntry { Id = 1, Entity = curr, OriginalEntity = null, Operation = ChangeOperation.Insert }; cs = new ChangeSet(new ChangeSetEntry[] { entry }); Assert.Throws <InvalidOperationException>(delegate { cs.GetOriginal(curr); }, String.Format(Resource.ChangeSet_OriginalNotValidForInsert)); }
/// <inheritdoc/> public Task OnExecutedEntryAsync( SubmitContext context, ChangeSetEntry entry, CancellationToken cancellationToken) { return(this.InvokeFilterMethodAsync(context, entry, "ed")); }
private static string GetMethodName(ChangeSetEntry entry, string suffix) { switch (entry.Type) { case ChangeSetEntryType.DataModification: DataModificationEntry dataModification = (DataModificationEntry)entry; string operationName = null; if (dataModification.IsNew) { operationName = "Insert"; } else if (dataModification.IsUpdate) { operationName = "Updat"; } else if (dataModification.IsDelete) { operationName = "Delet"; } return("On" + operationName + suffix + dataModification.EntitySetName); case ChangeSetEntryType.ActionInvocation: ActionInvocationEntry actionEntry = (ActionInvocationEntry)entry; return("OnExecut" + suffix + actionEntry.ActionName); default: throw new InvalidOperationException(string.Format( CultureInfo.InvariantCulture, Resources.InvalidChangeSetEntryType, entry.Type)); } }
public static ChangeSetEntry GetCustomUpdateChangeSetEntry(OperationContext context, Expression expression, object original) { context.OperationName = Utility.GetNameFromLambda(expression); IEnumerable <object> parameterValues = Utility.GetParametersFromLambda(expression); object entity = parameterValues.First(); ChangeSetEntry changeSetEntry = new ChangeSetEntry(Utility.DefaultChangeSetEntryId, entity, original, DomainOperation.Update); DomainOperationEntry domainOperationEntry = context.DomainServiceDescription.GetCustomMethod(entity.GetType(), context.OperationName); if (domainOperationEntry == null) { throw new InvalidOperationException(string.Format( CultureInfo.CurrentCulture, Resources.NoCustomUpdateOperation, context.OperationName, context.DomainServiceDescription.DomainServiceType)); } changeSetEntry.EntityActions = new EntityActionCollection { { context.OperationName, parameterValues.Skip(1).ToArray() }, }; return(changeSetEntry); }
/// <summary> /// Updates each entry in the ChangeSet with its corresponding conflict info. /// </summary> /// <param name="operationConflictMap">Map of conflicts to their corresponding operations entries.</param> private void SetChangeSetConflicts(Dictionary <ObjectStateEntry, ChangeSetEntry> operationConflictMap) { object storeValue; EntityKey refreshEntityKey; foreach (var conflictEntry in operationConflictMap) { ObjectStateEntry stateEntry = conflictEntry.Key; if (stateEntry.State == EntityState.Unchanged) { continue; } // Note: we cannot call Refresh StoreWins since this will overwrite Current entity and remove the optimistic concurrency ex. ChangeSetEntry operationInConflict = conflictEntry.Value; refreshEntityKey = RefreshContext.CreateEntityKey(stateEntry.EntitySet.Name, stateEntry.Entity); RefreshContext.TryGetObjectByKey(refreshEntityKey, out storeValue); operationInConflict.StoreEntity = storeValue; // StoreEntity will be null if the entity has been deleted in the store (i.e. Delete/Delete conflict) bool isDeleted = (operationInConflict.StoreEntity == null); if (isDeleted) { operationInConflict.IsDeleteConflict = true; } else { // Determine which members are in conflict by comparing original values to the current DB values PropertyDescriptorCollection propDescriptors = TypeDescriptor.GetProperties(operationInConflict.Entity.GetType()); List <string> membersInConflict = new List <string>(); object originalValue; PropertyDescriptor pd; for (int i = 0; i < stateEntry.OriginalValues.FieldCount; i++) { originalValue = stateEntry.OriginalValues.GetValue(i); if (originalValue is DBNull) { originalValue = null; } string propertyName = stateEntry.OriginalValues.GetName(i); pd = propDescriptors[propertyName]; if (pd == null) { // This might happen in the case of a private model // member that isn't mapped continue; } if (!Object.Equals(originalValue, pd.GetValue(operationInConflict.StoreEntity))) { membersInConflict.Add(pd.Name); } } operationInConflict.ConflictMembers = membersInConflict; } } }
public async Task SubmitAsync_Exceptions() { var mockDomainClient = new CitiesMockDomainClient(); Cities.CityDomainContext ctx = new Cities.CityDomainContext(mockDomainClient); DomainOperationException ex = new DomainOperationException("Submit Failed!", OperationErrorStatus.ServerError, 42, "StackTrace"); // If cancellation results in request beeing cancelled the result should be cancelled mockDomainClient.SubmitCompletedResult = Task.FromException <SubmitCompletedResult>(ex); ctx.Cities.Add(new City() { Name = "NewCity", StateName = "NN", CountyName = "NewCounty" }); Assert.IsTrue(ctx.EntityContainer.HasChanges); var submitEx = await ExceptionHelper.ExpectExceptionAsync <SubmitOperationException>(() => ctx.SubmitChangesAsync()); // verify the exception properties Assert.AreEqual(string.Format(Resource.DomainContext_SubmitOperationFailed, ex.Message), submitEx.Message); Assert.AreEqual(ex.StackTrace, submitEx.StackTrace); Assert.AreEqual(ex.Status, submitEx.Status); Assert.AreEqual(ex.ErrorCode, submitEx.ErrorCode); Assert.IsTrue(ctx.EntityContainer.HasChanges); // now test with validation exception var changeSet = ctx.EntityContainer.GetChanges(); IEnumerable <ChangeSetEntry> entries = ChangeSetBuilder.Build(changeSet); ChangeSetEntry entry = entries.First(); entry.ValidationErrors = new ValidationResultInfo[] { new ValidationResultInfo("Foo", new string[] { "Bar" }) }; mockDomainClient.SubmitCompletedResult = Task.FromResult(new SubmitCompletedResult(changeSet, entries)); submitEx = await ExceptionHelper.ExpectExceptionAsync <SubmitOperationException>(() => ctx.SubmitChangesAsync()); // verify the exception properties Assert.AreEqual(Resource.DomainContext_SubmitOperationFailed_Validation, submitEx.Message); Assert.AreEqual(OperationErrorStatus.ValidationFailed, submitEx.Status); Assert.AreEqual(1, submitEx.EntitiesInError.Count); Assert.AreEqual(entry.ClientEntity, submitEx.EntitiesInError.First()); // now test again with conflicts entries = ChangeSetBuilder.Build(changeSet); entry = entries.First(); entry.ConflictMembers = new string[] { nameof(City.CountyName) }; entry.StoreEntity = new City() { CountyName = "OtherCounty" }; mockDomainClient.SubmitCompletedResult = Task.FromResult(new SubmitCompletedResult(changeSet, entries)); submitEx = await ExceptionHelper.ExpectExceptionAsync <SubmitOperationException>(() => ctx.SubmitChangesAsync()); // verify the exception properties Assert.AreEqual(Resource.DomainContext_SubmitOperationFailed_Conflicts, submitEx.Message); Assert.AreEqual(OperationErrorStatus.Conflicts, submitEx.Status); Assert.AreEqual(1, submitEx.EntitiesInError.Count); Assert.AreEqual(entry.ClientEntity, submitEx.EntitiesInError.First()); }
private static string GetErrorMessageForConflicts(ChangeSetEntry changeSetEntry) { return(string.Format( CultureInfo.CurrentCulture, "There are conflicts for one or more members on the entity '{0}': {1}.", changeSetEntry.Entity.GetType(), string.Join(", ", changeSetEntry.ConflictMembers))); }
private static string GetErrorMessageForValidation(ChangeSetEntry changeSetEntry) { return(string.Format( CultureInfo.CurrentCulture, "Validation failed for the entity '{0}' with one or more errors: {1}.", changeSetEntry.Entity.GetType(), string.Join(", ", changeSetEntry.ValidationErrors.Select(vri => ErrorUtility.GetErrorMessageForValidation(vri))))); }
/// <inheritdoc/> public Task OnExecutedEntryAsync( SubmitContext context, ChangeSetEntry entry, CancellationToken cancellationToken) { return(this.InvokeFilterMethodAsync( context, entry, ConventionBasedChangeSetConstants.FilterMethodNamePostFilterSuffix)); }
/// <summary> /// Record an operation invocation, and validate that all parent operations /// have been completed /// </summary> /// <param name="entity"></param> private void SetOperationInvoked(object entity) { VerifyOperationOrdering(entity, false); // record the operation invocation ChangeSetEntry op = this.ChangeSet.ChangeSetEntries.Single(p => p.Entity == entity); _invokedOperations.Add(op); }
public void TestAssociations_UpdatedReferencingNew() { NorthwindEntityContainer ec = new NorthwindEntityContainer(); Product p1 = new Product { ProductID = 1, CategoryID = 1 }; Product p2 = new Product { ProductID = 2, CategoryID = 2 }; Category c1 = new Category { CategoryID = 1 }; Category c2 = new Category { CategoryID = 2 }; ec.LoadEntities(new Entity[] { p1, p2, c1, c2 }); // take two existing parents (the FK side of the association) // access their existing children Category prevCat = p1.Category; Assert.IsNotNull(prevCat); prevCat = p2.Category; Assert.IsNotNull(prevCat); // create two new children Category newCat1 = new Category { CategoryID = 3 }; Category newCat2 = new Category { CategoryID = 4 }; // assign the two new children p1.Category = newCat1; p2.Category = newCat2; EntityChangeSet cs = ec.GetChanges(); Assert.AreEqual(2, cs.AddedEntities.Count); Assert.AreEqual(2, cs.ModifiedEntities.Count); List <ChangeSetEntry> entries = ChangeSetBuilder.Build(cs); ChangeSetEntry entry = entries.Single(p => p.Entity == p1); // the bug was that we weren't populating the association map in this // scenario since previously we required BOTH parent and child to be new. // We've relaxed that to ensure that if the child is new, the association // shows up in the map. Assert.IsNotNull(entry.Associations); int[] ids = entry.Associations["Category"]; Category referenced = (Category)entries.Single(p => p.Id == ids.Single()).Entity; Assert.AreSame(newCat1, referenced); }
private bool InvokeSaveChanges(bool retryOnConflict) { try { this.ObjectContext.SaveChanges(); } catch (OptimisticConcurrencyException ex) { // Map the operations that could have caused a conflict to an entity. Dictionary <ObjectStateEntry, ChangeSetEntry> operationConflictMap = new Dictionary <ObjectStateEntry, ChangeSetEntry>(); foreach (ObjectStateEntry conflict in ex.StateEntries) { ChangeSetEntry entry = this.ChangeSet.ChangeSetEntries.SingleOrDefault(p => object.ReferenceEquals(p.Entity, conflict.Entity)); if (entry == null) { // If we're unable to find the object in our changeset, propagate // the original exception throw; } operationConflictMap.Add(conflict, entry); } this.SetChangeSetConflicts(operationConflictMap); // Call out to any user resolve code and resubmit if all conflicts // were resolved if (retryOnConflict && this.ResolveConflicts(ex.StateEntries)) { // clear the conflics from the entries foreach (ChangeSetEntry entry in this.ChangeSet.ChangeSetEntries) { entry.StoreEntity = null; entry.ConflictMembers = null; entry.IsDeleteConflict = false; } // If all conflicts were resolved attempt a resubmit return(this.InvokeSaveChanges(/* retryOnConflict */ false)); } // if the conflict wasn't resolved, call the error handler this.OnError(new DomainServiceErrorInfo(ex)); // if there was a conflict but no conflict information was // extracted to the individual entries, we need to ensure the // error makes it back to the client if (!this.ChangeSet.HasError) { throw; } return(false); } return(true); }
public void Submit_Multiple_Success() { Order order = new Order { OrderID = 1, OrderDate = DateTime.Now }; Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" }; ChangeSetEntry[] changeSet = new ChangeSetEntry[] { new ChangeSetEntry { Id = 1, Entity = order, Operation = ChangeOperation.Insert }, new ChangeSetEntry { Id = 2, Entity = product, Operation = ChangeOperation.Update } }; ChangeSetEntry[] resultChangeSet = this.ExecuteSubmit(TestConstants.CatalogUrl + "Submit", "Catalog", changeSet); Assert.Equal(2, resultChangeSet.Length); Assert.True(resultChangeSet.All(p => !p.HasError)); }
public void GetOriginal() { ChangeSet changeSet = this.GenerateChangeSet(); ChangeSetEntry op = changeSet.ChangeSetEntries.First(); A currentEntity = new A(), originalEntity = new A(); op.Entity = currentEntity; op.OriginalEntity = originalEntity; A changeSetOriginalEntity = changeSet.GetOriginal(currentEntity); // Verify we returned the original Assert.AreSame(originalEntity, changeSetOriginalEntity, "Expected to find original entity."); }
public void Constructor_Initialization() { ChangeSet changeSet; IEnumerable <ChangeSetEntry> ops = this.GenerateEntityOperations(false); changeSet = new ChangeSet(ops); Assert.AreEqual(ops.Count(), ops.Intersect(changeSet.ChangeSetEntries).Count(), "Expected ChangeSetEntries count to match what was provided to the constructor"); ChangeSetEntry changeSetEntry = new ChangeSetEntry(0, new E(), new E() { E_ID2 = 5 }, DomainOperation.Update); Assert.IsTrue(changeSetEntry.HasMemberChanges); }
/// <inheritdoc/> public Task ValidateEntityAsync( SubmitContext context, ChangeSetEntry entry, ValidationResults validationResults, CancellationToken cancellationToken) { Ensure.NotNull(validationResults, "validationResults"); DataModificationEntry dataModificationEntry = entry as DataModificationEntry; if (dataModificationEntry != null) { object entity = dataModificationEntry.Entity; // TODO GitHubIssue#50 : should this PropertyDescriptorCollection be cached? PropertyDescriptorCollection properties = new DataAnnotations.AssociatedMetadataTypeTypeDescriptionProvider(entity.GetType()) .GetTypeDescriptor(entity).GetProperties(); DataAnnotations.ValidationContext validationContext = new DataAnnotations.ValidationContext(entity); foreach (PropertyDescriptor property in properties) { validationContext.MemberName = property.Name; IEnumerable <DataAnnotations.ValidationAttribute> validationAttributes = property.Attributes.OfType <DataAnnotations.ValidationAttribute>(); foreach (DataAnnotations.ValidationAttribute validationAttribute in validationAttributes) { object value = property.GetValue(entity); DataAnnotations.ValidationResult validationResult = validationAttribute.GetValidationResult(value, validationContext); if (validationResult != DataAnnotations.ValidationResult.Success) { validationResults.Add(new ValidationResult() { Id = validationAttribute.GetType().FullName, Message = validationResult.ErrorMessage, Severity = ValidationSeverity.Error, Target = entity, PropertyName = property.Name }); } } } } return(Task.WhenAll()); }
public void Replace() { ChangeSet changeSet = this.GenerateChangeSet(); ChangeSetEntry op = changeSet.ChangeSetEntries.First(); A currentEntity = new A(), originalEntity = new A(), returnedEntity = new A(); op.Entity = currentEntity; op.OriginalEntity = originalEntity; changeSet.Replace(currentEntity, returnedEntity); Assert.AreSame(op.Entity, currentEntity, "Expected to find current entity."); Assert.AreSame(op.OriginalEntity, originalEntity, "Expected to find original entity."); Assert.AreSame(returnedEntity, changeSet.EntitiesToReplace[op.Entity], "Expected to find returned entity."); }
public void GetOriginal() { ChangeSet changeSet = this.GenerateChangeSet(); ChangeSetEntry op = changeSet.ChangeSetEntries.First(); Product currentEntity = new Product(); Product originalEntity = new Product(); op.Entity = currentEntity; op.OriginalEntity = originalEntity; Product changeSetOriginalEntity = changeSet.GetOriginal(currentEntity); // Verify we returned the original Assert.Same(originalEntity, changeSetOriginalEntity); }
public void Submit_Tree_Success() { Order order = new Order { OrderID = 1, OrderDate = DateTime.Now }; Order_Detail d1 = new Order_Detail { ProductID = 1 }; Order_Detail d2 = new Order_Detail { ProductID = 2 }; Dictionary<string, int[]> detailsAssociation = new Dictionary<string, int[]>(); detailsAssociation.Add("Order_Details", new int[] { 2, 3 }); ChangeSetEntry[] changeSet = new ChangeSetEntry[] { new ChangeSetEntry { Id = 1, Entity = order, Operation = ChangeOperation.Insert, Associations = detailsAssociation }, new ChangeSetEntry { Id = 2, Entity = d1, Operation = ChangeOperation.Insert }, new ChangeSetEntry { Id = 3, Entity = d2, Operation = ChangeOperation.Insert } }; ChangeSetEntry[] resultChangeSet = this.ExecuteSubmit(TestConstants.CatalogUrl + "Submit", "Catalog", changeSet); Assert.Equal(3, resultChangeSet.Length); Assert.True(resultChangeSet.All(p => !p.HasError)); }
private static object[] GetParameters(ChangeSetEntry entry) { switch (entry.Type) { case ChangeSetEntryType.DataModification: DataModificationEntry dataModification = (DataModificationEntry)entry; return(new object[] { dataModification.Entity }); case ChangeSetEntryType.ActionInvocation: ActionInvocationEntry actionEntry = (ActionInvocationEntry)entry; return(actionEntry.GetArgumentArray()); default: throw new InvalidOperationException(string.Format( CultureInfo.InvariantCulture, Resources.InvalidChangeSetEntryType, entry.Type)); } }
public void Submit_Authorization_Success() { TestAuthAttribute.Reset(); Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" }; ChangeSetEntry[] changeSet = new ChangeSetEntry[] { new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Update } }; ChangeSetEntry[] resultChangeSet = this.ExecuteSubmit("http://testhost/TestAuth/Submit", "TestAuth", changeSet); Assert.Equal(1, resultChangeSet.Length); Assert.True(TestAuthAttribute.Log.SequenceEqual(new string[] { "Global", "Class", "SubmitMethod", "UserMethod" })); }
public void Submit_Validation_Failure() { Microsoft.Web.Http.Data.Test.Models.EF.Product newProduct = new Microsoft.Web.Http.Data.Test.Models.EF.Product { ProductID = 1, ProductName = String.Empty, UnitPrice = -1 }; Microsoft.Web.Http.Data.Test.Models.EF.Product updateProduct = new Microsoft.Web.Http.Data.Test.Models.EF.Product { ProductID = 1, ProductName = new string('x', 50), UnitPrice = 55.77M }; ChangeSetEntry[] changeSet = new ChangeSetEntry[] { new ChangeSetEntry { Id = 1, Entity = newProduct, Operation = ChangeOperation.Insert }, new ChangeSetEntry { Id = 2, Entity = updateProduct, Operation = ChangeOperation.Update } }; HttpResponseMessage response = this.ExecuteSelfHostRequest("http://testhost/NorthwindEFTest/Submit", "NorthwindEFTest", changeSet); changeSet = response.Content.ReadAsAsync <ChangeSetEntry[]>().Result; // errors for the new product ValidationResultInfo[] errors = changeSet[0].ValidationErrors.ToArray(); Assert.Equal(2, errors.Length); Assert.True(changeSet[0].HasError); // validation rule inferred from EF model Assert.Equal("ProductName", errors[0].SourceMemberNames.Single()); Assert.Equal("The ProductName field is required.", errors[0].Message); // validation rule coming from buddy class Assert.Equal("UnitPrice", errors[1].SourceMemberNames.Single()); Assert.Equal("The field UnitPrice must be between 0 and 1000000.", errors[1].Message); // errors for the updated product errors = changeSet[1].ValidationErrors.ToArray(); Assert.Equal(1, errors.Length); Assert.True(changeSet[1].HasError); // validation rule inferred from EF model Assert.Equal("ProductName", errors[0].SourceMemberNames.Single()); Assert.Equal("The field ProductName must be a string with a maximum length of 40.", errors[0].Message); }
public void Submit_ResolveActions_UnsupportedAction() { Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" }; ChangeSetEntry[] changeSet = new ChangeSetEntry[] { new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Delete } }; HttpConfiguration configuration = new HttpConfiguration(); HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(configuration, "NorthwindEFTestController", typeof(NorthwindEFTestController)); DataControllerDescription description = DataControllerDescription.GetDescription(controllerDescriptor); Assert.Throws <InvalidOperationException>( () => DataController.ResolveActions(description, changeSet), String.Format(Resource.DataController_InvalidAction, "Delete", "Product")); }
private Task InvokeFilterMethodAsync( SubmitContext context, ChangeSetEntry entry, string methodNameSuffix) { string methodName = ConventionBasedChangeSetEntryFilter.GetMethodName(entry, methodNameSuffix); object[] parameters = ConventionBasedChangeSetEntryFilter.GetParameters(entry); MethodInfo method = this.targetType.GetQualifiedMethod(methodName); if (method != null && (method.ReturnType == typeof(void) || typeof(Task).IsAssignableFrom(method.ReturnType))) { object target = null; if (!method.IsStatic) { target = context.ApiContext.GetProperty( typeof(Api).AssemblyQualifiedName); if (target == null || !this.targetType.IsAssignableFrom(target.GetType())) { return(Task.WhenAll()); } } ParameterInfo[] methodParameters = method.GetParameters(); if (ConventionBasedChangeSetEntryFilter.ParametersMatch(methodParameters, parameters)) { object result = method.Invoke(target, parameters); Task resultTask = result as Task; if (resultTask != null) { return(resultTask); } } } return(Task.WhenAll()); }
public void DomainService_CallSubmitDirectly() { DomainServiceDescription description = DomainServiceDescription.GetDescription(typeof(DomainMethod_ValidProvider_MultipleMethods)); List<ChangeSetEntry> changeSetEntries = new List<ChangeSetEntry>(); ChangeSetEntry processCityOperation = new ChangeSetEntry(); processCityOperation.Entity = new City { Name = "Redmond", CountyName = "King", StateName = "WA" }; processCityOperation.DomainOperationEntry = description.GetCustomMethod(typeof(City), "ProcessCity"); processCityOperation.Operation = DomainOperation.Update; processCityOperation.EntityActions = new EntityActionCollection { { "ProcessCity", new object[] { new byte[] { byte.MaxValue, byte.MinValue, 123 } } } }; changeSetEntries.Add(processCityOperation); ChangeSet changeset = new ChangeSet(changeSetEntries); DomainMethod_ValidProvider_MultipleMethods myTestProvider = ServerTestHelper.CreateInitializedDomainService<DomainMethod_ValidProvider_MultipleMethods>(DomainOperationType.Submit); myTestProvider.Submit(changeset); // check that the domain services have invoked the domain method correctly by checking the internal variables set Assert.AreEqual<string>("ProcessCity_", myTestProvider.Invoked); Assert.AreEqual<int>(3, myTestProvider.InputData.Length); Assert.AreEqual<byte>(123, myTestProvider.InputData[2]); }
public void Submit_Authorization_Fail_Global() { TestAuthAttribute.Reset(); Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" }; ChangeSetEntry[] changeSet = new ChangeSetEntry[] { new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Update } }; TestAuthAttribute.FailLevel = "Global"; HttpResponseMessage response = this.ExecuteSelfHostRequest("http://testhost/TestAuth/Submit", "TestAuth", changeSet); Assert.True(TestAuthAttribute.Log.SequenceEqual(new string[] { "Global" })); Assert.Equal("Not Authorized", response.ReasonPhrase); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); }
/// <summary> /// Helper method performs a submit operation against a given proxy instance. /// </summary> /// <param name="domainService">The type of <see cref="DomainService"/> to perform this query operation against.</param> /// <param name="context">The current context.</param> /// <param name="domainServiceInstances">The list of tracked <see cref="DomainService"/> instances that any newly created /// <see cref="DomainServices"/> will be added to.</param> /// <param name="currentOriginalEntityMap">The mapping of current and original entities used with the utility <see cref="DomainServiceProxy.AssociateOriginal"/> method.</param> /// <param name="entity">The entity being submitted.</param> /// <param name="operationName">The name of the submit operation. For CUD operations, this can be null.</param> /// <param name="parameters">The submit operation parameters.</param> /// <param name="domainOperation">The type of submit operation.</param> /// <exception cref="ArgumentNullException">if <paramref name="context"/> is null.</exception> /// <exception cref="ArgumentNullException">if <paramref name="entity"/> is null.</exception> /// <exception cref="OperationException">if operation errors are thrown during execution of the submit operation.</exception> public static void Submit(Type domainService, DomainServiceContext context, IList <DomainService> domainServiceInstances, IDictionary <object, object> currentOriginalEntityMap, object entity, string operationName, object[] parameters, DomainOperation domainOperation) { context = new DomainServiceContext(context, DomainOperationType.Submit); DomainService service = CreateDomainServiceInstance(domainService, context, domainServiceInstances); object originalEntity = null; currentOriginalEntityMap.TryGetValue(entity, out originalEntity); // if this is an update operation, regardless of whether original // values have been specified, we need to mark the operation as // modified bool hasMemberChanges = domainOperation == DomainOperation.Update; // when custom methods are invoked, the operation type // is Update if (domainOperation == DomainOperation.Custom) { domainOperation = DomainOperation.Update; } ChangeSetEntry changeSetEntry = new ChangeSetEntry(1, entity, originalEntity, domainOperation); changeSetEntry.HasMemberChanges = hasMemberChanges; if (!string.IsNullOrEmpty(operationName)) { changeSetEntry.EntityActions = new List <Serialization.KeyValue <string, object[]> >(); changeSetEntry.EntityActions.Add(new Serialization.KeyValue <string, object[]>(operationName, parameters)); } ChangeSet changeSet = new ChangeSet(new[] { changeSetEntry }); service.SubmitAsync(changeSet, CancellationToken.None) .GetAwaiter().GetResult(); if (changeSetEntry.HasError) { throw new OperationException(Resource.DomainServiceProxy_OperationError, changeSetEntry.ValidationErrors); } }
public void Changeset_OriginalInvalidForInserts() { // can't specify an original for an insert operation TimestampEntityA curr = new TimestampEntityA { ID = 1, Version = new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 }, ValueA = "Foo", ValueB = "Bar" } ; TimestampEntityA orig = new TimestampEntityA { ID = 1, Version = new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 }, ValueA = "x", ValueB = "x" }; ChangeSetEntry entry = new ChangeSetEntry(1, curr, orig, DomainOperation.Insert); ChangeSet cs = null; ExceptionHelper.ExpectInvalidOperationException(delegate { cs = new ChangeSet(new ChangeSetEntry[] { entry }); }, string.Format(Resource.InvalidChangeSet, Resource.InvalidChangeSet_InsertsCantHaveOriginal)); // get original should throw for insert operations entry = new ChangeSetEntry(1, curr, null, DomainOperation.Insert); cs = new ChangeSet(new ChangeSetEntry[] { entry }); ExceptionHelper.ExpectInvalidOperationException(delegate { cs.GetOriginal(curr); }, string.Format(Resource.ChangeSet_OriginalNotValidForInsert)); }
public void Authorization_Custom_Authorization_On_CUD() { // Specifically, the City data is marked so that no one can delete a Zip code // from WA unless their user name is WAGuy MockUser notWaGuy = new MockUser("notWAGuy"); notWaGuy.IsAuthenticated = true; DomainServiceDescription serviceDescription = DomainServiceDescription.GetDescription(typeof(CityDomainService)); Zip zip = null; // First execute a query to get some zips DomainOperationEntry getZipsQuery = serviceDescription.GetQueryMethod("GetZips"); DomainServiceContext ctxt = new DomainServiceContext(new MockDataService(notWaGuy), DomainOperationType.Query); using (CityDomainService cities = new CityDomainService()) { // Now prepare for a query to find a Zip in WA ctxt = new DomainServiceContext(new MockDataService(notWaGuy), DomainOperationType.Query); cities.Initialize(ctxt); int count = -1; IEnumerable<ValidationResult> validationErrors = null; IEnumerable result = cities.Query(new QueryDescription(getZipsQuery), out validationErrors, out count); zip = result.OfType<Zip>().FirstOrDefault(z => z.StateName == "WA"); Assert.IsNotNull(zip, "Could not find a zip code in WA"); } // Prepare a submit to delete this zip from a user who is not authorized using (CityDomainService cities = new CityDomainService()) { // Now prepare for a query to find a Zip in WA ctxt = new DomainServiceContext(new MockDataService(notWaGuy), DomainOperationType.Submit); cities.Initialize(ctxt); // Prepare an attempt to delete this with a user whose name is not WAGuy // This should fail due to a custom auth attribute List<ChangeSetEntry> entries = new List<ChangeSetEntry>(); ChangeSetEntry entry = new ChangeSetEntry(1, zip, zip, DomainOperation.Delete); entries.Add(entry); UnauthorizedAccessException exception = null; try { ChangeSetProcessor.Process(cities, entries); } catch (UnauthorizedAccessException ex) { exception = ex; } Assert.IsNotNull(exception, "Expected failure attempting to delete a zip from WA with inappropriate user name"); Assert.AreEqual("Only one user can delete zip codes from that state, and it isn't you.", exception.Message); } // Now do that again but with a user who is WAGuy -- it should succeed using (CityDomainService cities = new CityDomainService()) { MockUser waGuy = new MockUser("WAGuy"); waGuy.IsAuthenticated = true; // Now try a submit where the user *is* Mathew to validate we succeed ctxt = new DomainServiceContext(new MockDataService(waGuy), DomainOperationType.Submit); cities.Initialize(ctxt); List<ChangeSetEntry> entries = new List<ChangeSetEntry>(); ChangeSetEntry entry = new ChangeSetEntry(1, zip, zip, DomainOperation.Delete); entries.Add(entry); Exception exception = null; try { ChangeSetProcessor.Process(cities, entries); } catch (UnauthorizedAccessException ex) { exception = ex; } Assert.IsNull(exception, "Expected success attempting to delete a zip from WA with inappropriate user name"); } }
public void Constructor_Initialization() { ChangeSet changeSet; IEnumerable<ChangeSetEntry> ops = this.GenerateEntityOperations(false); changeSet = new ChangeSet(ops); Assert.AreEqual(ops.Count(), ops.Intersect(changeSet.ChangeSetEntries).Count(), "Expected ChangeSetEntries count to match what was provided to the constructor"); ChangeSetEntry changeSetEntry = new ChangeSetEntry(0, new E(), new E() { E_ID2 = 5 }, DomainOperation.Update); Assert.IsTrue(changeSetEntry.HasMemberChanges); }
/// <summary> /// Execute a full roundtrip Submit request for the specified changeset, going through /// the full serialization pipeline. /// </summary> private ChangeSetEntry[] ExecuteSubmit(string url, string controllerName, ChangeSetEntry[] changeSet) { HttpResponseMessage response = this.ExecuteSelfHostRequest(url, controllerName, changeSet); ChangeSetEntry[] resultChangeSet = GetChangesetResponse(response); return changeSet; }
public void Authorization_Custom_Authorization_On_Custom_Update() { // Specifically, the City data is marked so that no one can delete a Zip code // from WA unless their user name is WAGuy MockUser notWaGuy = new MockUser("notWAGuy"); notWaGuy.IsAuthenticated = true; DomainServiceDescription serviceDescription = DomainServiceDescription.GetDescription(typeof(CityDomainService)); City city = null; // Execute a query to get a City from WA using (CityDomainService cities = new CityDomainService()) { DomainOperationEntry getCitiesQuery = serviceDescription.GetQueryMethod("GetCities"); // Now prepare for a query to find a Zip in WA DomainServiceContext ctxt = new DomainServiceContext(new MockDataService(notWaGuy), DomainOperationType.Query); cities.Initialize(ctxt); int count = -1; IEnumerable<ValidationResult> validationErrors = null; IEnumerable result = cities.Query(new QueryDescription(getCitiesQuery), out validationErrors, out count); city = result.OfType<City>().FirstOrDefault(z => z.StateName == "WA"); Assert.IsNotNull(city, "Could not find a city in WA"); } using (CityDomainService cities = new CityDomainService()) { // Now prepare for a submit to invoke AssignCityZoneIfAuthorized as a named update method DomainServiceContext ctxt = new DomainServiceContext(new MockDataService(notWaGuy), DomainOperationType.Submit); cities.Initialize(ctxt); // Prepare an attempt to delete this with a user whose name is not WAGuy // This should fail due to a custom auth attribute List<ChangeSetEntry> entries = new List<ChangeSetEntry>(); ChangeSetEntry entry = new ChangeSetEntry(); entry.DomainOperationEntry = serviceDescription.GetCustomMethod(typeof(City), "AssignCityZoneIfAuthorized"); entry.EntityActions = new EntityActionCollection { { "AssignCityZoneIfAuthorized", new object[] { "SomeZone" } } }; entry.Operation = DomainOperation.Update; entry.Entity = city; entries.Add(entry); UnauthorizedAccessException exception = null; try { ChangeSetProcessor.Process(cities, entries); } catch (UnauthorizedAccessException ex) { exception = ex; } Assert.IsNotNull(exception, "Expected failure attempting to perform custom method on WA with inappropriate user name"); Assert.AreEqual("Only one user is authorized to execute operation 'AssignCityZoneIfAuthorized', and it isn't you.", exception.Message); } // Now do that again but with a user who is WAGuy -- it should succeed using (CityDomainService cities = new CityDomainService()) { MockUser waGuy = new MockUser("WAGuy"); waGuy.IsAuthenticated = true; // Now prepare for a submit to invoke AssignCityZoneIfAuthorized as a named update method DomainServiceContext ctxt = new DomainServiceContext(new MockDataService(waGuy), DomainOperationType.Submit); cities.Initialize(ctxt); // Prepare an attempt to delete this with a user whose name is not WAGuy // This should fail due to a custom auth attribute // Prepare a submit to call the AssignCityZoneIfAuthorized with an unauthorized user List<ChangeSetEntry> entries = new List<ChangeSetEntry>(); ChangeSetEntry entry = new ChangeSetEntry(); entry.DomainOperationEntry = serviceDescription.GetCustomMethod(typeof(City), "AssignCityZoneIfAuthorized"); entry.EntityActions = new EntityActionCollection { { "AssignCityZoneIfAuthorized", new object[] { "SomeZone" } } }; entry.Operation = DomainOperation.Update; entry.Entity = city; entries.Add(entry); Exception exception = null; try { ChangeSetProcessor.Process(cities, entries); } catch (UnauthorizedAccessException ex) { exception = ex; } Assert.IsNull(exception, "Expected success attempting to delete a zip from WA with inappropriate user name"); } }
public void Submit_ResolveActions_UnsupportedAction() { Product product = new Product { ProductID = 1, ProductName = "Choco Wafers" }; ChangeSetEntry[] changeSet = new ChangeSetEntry[] { new ChangeSetEntry { Id = 1, Entity = product, Operation = ChangeOperation.Delete } }; HttpConfiguration configuration = new HttpConfiguration(); HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(configuration, "NorthwindEFTestController", typeof(NorthwindEFTestController)); DataControllerDescription description = DataControllerDescription.GetDescription(controllerDescriptor); Assert.Throws<InvalidOperationException>( () => DataController.ResolveActions(description, changeSet), String.Format(Resource.DataController_InvalidAction, "Delete", "Product")); }
public void Changeset_GetAssociatedChanges_Singleton() { DomainServiceDescription dsd = DomainServiceDescription.GetDescription(typeof(CompositionScenarios_Explicit)); // verify singleton change of None GreatGrandChild unmodifiedGgc = new GreatGrandChild(); GrandChild currGrandChild = new GrandChild { Child = unmodifiedGgc }; GrandChild origGrandChild = new GrandChild { Child = unmodifiedGgc }; ChangeSetEntry gcOperation = new ChangeSetEntry(1, currGrandChild, origGrandChild, DomainOperation.Update); gcOperation.Associations = new Dictionary<string, int[]> { { "Child", new int[] { 2 } } }; ChangeSetEntry ggcOperation = new ChangeSetEntry(2, unmodifiedGgc, null, DomainOperation.None); ChangeSet cs = new ChangeSet(new ChangeSetEntry[] { gcOperation, ggcOperation }); GreatGrandChild ggcChange = cs.GetAssociatedChanges(currGrandChild, p => p.Child, ChangeOperation.None).Cast<GreatGrandChild>().SingleOrDefault(); Assert.AreSame(unmodifiedGgc, ggcChange); // verify singleton insert GreatGrandChild newGgc = new GreatGrandChild(); currGrandChild = new GrandChild { Child = newGgc }; origGrandChild = new GrandChild { Child = null }; gcOperation = new ChangeSetEntry(1, currGrandChild, origGrandChild, DomainOperation.Update); gcOperation.Associations = new Dictionary<string, int[]> { { "Child", new int[] { 2 } } }; ggcOperation = new ChangeSetEntry(2, newGgc, null, DomainOperation.Insert); cs = new ChangeSet(new ChangeSetEntry[] { gcOperation, ggcOperation }); ggcChange = cs.GetAssociatedChanges(currGrandChild, p => p.Child, ChangeOperation.Insert).Cast<GreatGrandChild>().SingleOrDefault(); Assert.AreSame(newGgc, ggcChange); Assert.AreEqual(ChangeOperation.Insert, cs.GetChangeOperation(newGgc)); // verify singleton update GreatGrandChild modifiedGgc = new GreatGrandChild(); currGrandChild = new GrandChild { Child = modifiedGgc }; origGrandChild = new GrandChild { Child = modifiedGgc }; gcOperation = new ChangeSetEntry(1, currGrandChild, origGrandChild, DomainOperation.Update); gcOperation.Associations = new Dictionary<string, int[]> { { "Child", new int[] { 2 } } }; ggcOperation = new ChangeSetEntry(2, modifiedGgc, unmodifiedGgc, DomainOperation.Update); cs = new ChangeSet(new ChangeSetEntry[] { gcOperation, ggcOperation }); ggcChange = cs.GetAssociatedChanges(currGrandChild, p => p.Child, ChangeOperation.Update).Cast<GreatGrandChild>().SingleOrDefault(); Assert.AreSame(modifiedGgc, ggcChange); Assert.AreSame(unmodifiedGgc, cs.GetOriginal(modifiedGgc)); Assert.AreEqual(ChangeOperation.Update, cs.GetChangeOperation(modifiedGgc)); // verify singleton delete GreatGrandChild deletedGgc = new GreatGrandChild(); currGrandChild = new GrandChild { Child = null }; origGrandChild = new GrandChild { Child = deletedGgc }; gcOperation = new ChangeSetEntry(1, currGrandChild, null, DomainOperation.Update); gcOperation.OriginalAssociations = new Dictionary<string, int[]>() { { "Child", new int[] { 2 } } }; ggcOperation = new ChangeSetEntry(2, deletedGgc, null, DomainOperation.Delete); gcOperation.OriginalAssociations = new Dictionary<string, int[]>() { { "Child", new int[] { 2 } } }; cs = new ChangeSet(new ChangeSetEntry[] { gcOperation, ggcOperation }); ggcChange = cs.GetAssociatedChanges(currGrandChild, p => p.Child, ChangeOperation.Delete).Cast<GreatGrandChild>().SingleOrDefault(); Assert.AreSame(deletedGgc, ggcChange); Assert.AreSame(null, cs.GetOriginal(deletedGgc)); Assert.AreEqual(ChangeOperation.Delete, cs.GetChangeOperation(deletedGgc)); }
public void Changeset_GetAssociatedChanges_Enumerable() { DomainServiceDescription dsd = DomainServiceDescription.GetDescription(typeof(CompositionScenarios_Explicit)); #region build up a compositional hierarchy int id = 1; Parent currParent = new Parent(); List<ChangeSetEntry> changeSetEntries = new List<ChangeSetEntry>(); ChangeSetEntry parentUpdateOperation = new ChangeSetEntry(id++, currParent, null, DomainOperation.Update); List<int> currentAssociatedChildren = new List<int>(); changeSetEntries.Add(parentUpdateOperation); // add 3 unmodified children List<Child> unmodifiedChildren = new List<Child>() { new Child(), new Child(), new Child() }; foreach (Child c in unmodifiedChildren) { ChangeSetEntry operation = new ChangeSetEntry(id++, c, c, DomainOperation.None); changeSetEntries.Add(operation); currParent.Children.Add(c); currentAssociatedChildren.Add(operation.Id); } // add 2 child edits List<Child> originalEditedChildren = new List<Child> { new Child(), new Child() }; List<Child> currentEditedChildren = new List<Child> { new Child(), new Child() }; for (int i = 0; i < originalEditedChildren.Count; i++) { Child currChild = currentEditedChildren[i]; Child origChild = originalEditedChildren[i]; ChangeSetEntry operation = new ChangeSetEntry(id++, currChild, origChild, DomainOperation.Update); changeSetEntries.Add(operation); currParent.Children.Add(currChild); currentAssociatedChildren.Add(operation.Id); } // add a 2 new children children List<Child> newChildren = new List<Child> { new Child(), new Child() }; foreach (Child c in newChildren) { ChangeSetEntry operation = new ChangeSetEntry(id++, c, null, DomainOperation.Insert); changeSetEntries.Add(operation); currParent.Children.Add(c); currentAssociatedChildren.Add(operation.Id); } // add 2 removes by adding operations for the deleted children // and setting up the original association List<Child> removedChildren = new List<Child> { new Child(), new Child() }; List<int> deletedChildren = new List<int>(); foreach (Child c in removedChildren) { int deletedId = id++; changeSetEntries.Add(new ChangeSetEntry(deletedId, c, c, DomainOperation.Delete)); deletedChildren.Add(deletedId); } parentUpdateOperation.OriginalAssociations = new Dictionary<string, int[]>() { { "Children", deletedChildren.ToArray() } }; parentUpdateOperation.Associations = new Dictionary<string, int[]>() { { "Children", currentAssociatedChildren.ToArray() } }; #endregion // verify unmodified children ChangeSet cs = new ChangeSet(changeSetEntries); IEnumerable<Child> childChanges = cs.GetAssociatedChanges(currParent, p => p.Children, ChangeOperation.None).Cast<Child>(); Assert.AreEqual(3, childChanges.Count()); foreach (Child c in childChanges) { Assert.IsTrue(unmodifiedChildren.Contains(c)); } // verify inserted children childChanges = cs.GetAssociatedChanges(currParent, p => p.Children, ChangeOperation.Insert).Cast<Child>(); Assert.AreEqual(2, childChanges.Count()); foreach (Child c in childChanges) { Assert.IsTrue(newChildren.Contains(c)); } // verify deleted children childChanges = cs.GetAssociatedChanges(currParent, p => p.Children, ChangeOperation.Delete).Cast<Child>(); Assert.AreEqual(2, childChanges.Count()); foreach (Child c in childChanges) { Assert.IsTrue(removedChildren.Contains(c)); } // verify modified children childChanges = cs.GetAssociatedChanges(currParent, p => p.Children, ChangeOperation.Update).Cast<Child>(); Assert.AreEqual(2, childChanges.Count()); foreach (Child c in childChanges) { Assert.IsTrue(currentEditedChildren.Contains(c)); } // verify overload that returns all - should return all unmodified // in addition to all changed IEnumerable<Child> allChildren = cs.GetAssociatedChanges(currParent, p => p.Children).Cast<Child>(); Assert.AreEqual(9, allChildren.Count()); foreach (Child c in allChildren) { ChangeOperation operationType = cs.GetChangeOperation(c); switch (operationType) { case ChangeOperation.None: Assert.IsTrue(unmodifiedChildren.Contains(c)); break; case ChangeOperation.Insert: Assert.IsTrue(newChildren.Contains(c)); break; case ChangeOperation.Update: Assert.IsTrue(currentEditedChildren.Contains(c)); break; case ChangeOperation.Delete: Assert.IsTrue(removedChildren.Contains(c)); break; }; } // verify calls that return empty cs = new ChangeSet(new ChangeSetEntry[] { new ChangeSetEntry(1, currParent, null, DomainOperation.None) }); IEnumerable<Child> children = cs.GetAssociatedChanges(currParent, p => p.Children).Cast<Child>(); Assert.AreEqual(0, children.Count()); children = cs.GetAssociatedChanges(currParent, p => p.Children).Cast<Child>(); Assert.AreEqual(0, children.Count()); // test null collection properties currParent = new Parent { Children = null }; cs = new ChangeSet(new ChangeSetEntry[] { new ChangeSetEntry(1, currParent, new Parent { Children = null }, DomainOperation.None) }); Assert.AreEqual(0, cs.GetAssociatedChanges(currParent, p => p.Children).Cast<Child>().Count()); Assert.AreEqual(0, cs.GetAssociatedChanges(currParent, p => p.Children, ChangeOperation.None).Cast<Child>().Count()); }