/// <summary> /// Handles a DELETE request to delete an entity. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task object that contains the deletion result.</returns> public async Task <IHttpActionResult> Delete(CancellationToken cancellationToken) { ODataPath path = this.GetPath(); IEdmEntitySet entitySet = path.NavigationSource as IEdmEntitySet; if (entitySet == null) { throw new NotImplementedException(Resources.DeleteOnlySupportedOnEntitySet); } DataModificationEntry deleteEntry = new DataModificationEntry( entitySet.Name, path.EdmType.FullTypeName(), RestierQueryBuilder.GetPathKeyValues(path), this.GetOriginalValues(), null); RestierChangeSetProperty changeSetProperty = this.Request.GetChangeSet(); if (changeSetProperty == null) { ChangeSet changeSet = new ChangeSet(); changeSet.Entries.Add(deleteEntry); SubmitResult result = await Api.SubmitAsync(changeSet, cancellationToken); } else { changeSetProperty.ChangeSet.Entries.Add(deleteEntry); await changeSetProperty.OnChangeSetCompleted(); } return(this.StatusCode(HttpStatusCode.NoContent)); }
public async Task TestEntityFilterReturnsTask() { TestEntityFilterReturnsTaskApi api = new TestEntityFilterReturnsTaskApi(); DataModificationEntry <Customer> createCustomer = new DataModificationEntry <Customer>( "Customers", "Customer", null, null, new Dictionary <string, object>() { { "CustomerID", "NEW01" }, { "CompanyName", "New Cust" }, }); await api.SubmitAsync(new ChangeSet(new ChangeSetEntry[] { createCustomer })); NorthwindContext ctx = new NorthwindContext(); #if EF7 Customer newCustomer = await ctx.Customers.FirstOrDefaultAsync(e => e.CustomerID == "NEW01"); #else Customer newCustomer = await ctx.Customers.FindAsync("NEW01"); #endif // The "OnInserting" should have been appended by the OnInsertingCustomers filter Assert.Equal("New CustOnInserting", newCustomer.CompanyName); ctx.Customers.Remove(newCustomer); await ctx.SaveChangesAsync(); }
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)); } }
private static async Task <object> FindEntity(SubmitContext context, DataModificationEntry entry, CancellationToken cancellationToken) { IQueryable query = Domain.Source(context.DomainContext, entry.EntitySetName); query = entry.ApplyTo(query); QueryResult result = await Domain.QueryAsync(context.DomainContext, new QueryRequest(query), cancellationToken); object entity = result.Results.SingleOrDefault(); if (entity == null) { // TODO GitHubIssue#38 : Handle the case when entity is resolved // there are 2 cases where the entity is not found: // 1) it doesn't exist // 2) concurrency checks have failed // we should account for both - I can see 3 options: // a. always return "PreConditionFailed" result - this is the canonical behavior of WebAPI OData (see http://blogs.msdn.com/b/webdev/archive/2014/03/13/getting-started-with-asp-net-web-api-2-2-for-odata-v4-0.aspx) // - this makes sense because if someone deleted the record, then you still have a concurrency error // b. possibly doing a 2nd query with just the keys to see if the record still exists // c. only query with the keys, and then set the DbEntityEntry's OriginalValues to the ETag values, letting the save fail if there are concurrency errors //throw new EntityNotFoundException throw new InvalidOperationException(Resources.ResourceNotFound); } return(entity); }
private static void SetValues(EntityEntry dbEntry, DataModificationEntry entry) { foreach (KeyValuePair <string, object> propertyPair in entry.LocalValues) { PropertyEntry propertyEntry = dbEntry.Property(propertyPair.Key); object value = propertyPair.Value; if (value == null) { // If the property value is null, we set null in the entry too. propertyEntry.CurrentValue = null; continue; } Type type = TypeHelper.GetUnderlyingTypeOrSelf(propertyEntry.Metadata.ClrType); value = ConvertToEfValue(type, value); if (value != null && !type.IsInstanceOfType(value)) { var dic = value as IReadOnlyDictionary <string, object>; if (dic == null) { throw new NotSupportedException(string.Format( CultureInfo.InvariantCulture, Resources.UnsupportedPropertyType, propertyPair.Key)); } value = Activator.CreateInstance(type); SetValues(value, type, dic); } propertyEntry.CurrentValue = value; } }
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)); } }
/// <summary> Searches for the first entity. </summary> /// /// <exception cref="InvalidOperationException"> Thrown when the requested operation is /// invalid. </exception> /// /// <param name="context"> The submit context. </param> /// <param name="entry"> The entry. </param> /// <param name="cancellationToken"> A cancellation token. </param> /// /// <returns> The found entity. </returns> private static async Task <object> FindEntity(SubmitContext context, DataModificationEntry entry, CancellationToken cancellationToken) { IQueryable query = Api.Source(context.ApiContext, entry.EntitySetName); query = entry.ApplyTo(query); QueryResult result = await Api.QueryAsync( context.ApiContext, new QueryRequest(query), cancellationToken); object entity = result.Results.SingleOrDefault(); if (entity == null) { // TODO GitHubIssue#38 : Handle the case when entity is resolved // there are 2 cases where the entity is not found: // 1) it doesn't exist // 2) concurrency checks have failed // we should account for both - I can see 3 options: // a. always return "PreConditionFailed" result // - this is the canonical behavior of WebAPI OData, see the following post: // "Getting started with ASP.NET Web API 2.2 for OData v4.0" on http://blogs.msdn.com/b/webdev/. // - this makes sense because if someone deleted the record, then you still have a concurrency error // b. possibly doing a 2nd query with just the keys to see if the record still exists // c. only query with the keys, and then set the DbEntityEntry's OriginalValues to the ETag values, // letting the save fail if there are concurrency errors ////throw new EntityNotFoundException throw new InvalidOperationException("Could not find the specified resource."); } return(entity); }
public async Task ComplexTypeUpdate() { // Arrange var libraryApi = new LibraryApi(); var entry = new DataModificationEntry( "Readers", "Person", new Dictionary <string, object> { { "Id", new Guid("53162782-EA1B-4712-AF26-8AA1D2AC0461") } }, new Dictionary <string, object>(), new Dictionary <string, object> { { "Addr", new Dictionary <string, object> { { "Zip", "332" } } } }); var changeSet = new ChangeSet(new[] { entry }); var sc = new SubmitContext(libraryApi.Context, changeSet); // Act var changeSetPreparer = libraryApi.Context.Configuration.GetApiService <IChangeSetPreparer>(); await changeSetPreparer.PrepareAsync(sc, CancellationToken.None); var person = entry.Entity as Person; // Assert Assert.NotNull(person); Assert.Equal("332", person.Addr.Zip); }
private static async Task PrepareEntry <TEntity>( SubmitContext context, DbContext dbContext, DataModificationEntry entry, DbSet <TEntity> set, CancellationToken cancellationToken) where TEntity : class { Type entityType = typeof(TEntity); TEntity entity; if (entry.IsNew) { // TODO: See if Create method is in DbSet<> in future EF7 releases, as the one EF6 has. entity = (TEntity)Activator.CreateInstance(typeof(TEntity)); ChangeSetPreparer.SetValues(entity, entityType, entry.LocalValues); set.Add(entity); } else if (entry.IsDelete) { entity = (TEntity)await ChangeSetPreparer.FindEntity(context, entry, cancellationToken); set.Remove(entity); } else if (entry.IsUpdate) { entity = (TEntity)await ChangeSetPreparer.FindEntity(context, entry, cancellationToken); var dbEntry = dbContext.Update(entity); ChangeSetPreparer.SetValues(dbEntry, entry, entityType); } else { throw new NotSupportedException(Resources.DataModificationMustBeCUD); } entry.Entity = entity; }
/// <summary> Sets the values. </summary> /// /// <exception cref="NotSupportedException"> Thrown when the requested operation is not /// supported. </exception> /// /// <param name="dbEntry"> The database entry. </param> /// <param name="entry"> The entry. </param> /// <param name="entityType"> Type of the entity. </param> private static void SetValues(DbEntityEntry dbEntry, DataModificationEntry entry, Type entityType) { if (entry.IsFullReplaceUpdate) { // The algorithm for a "FullReplaceUpdate" is taken from ObjectContextServiceProvider.ResetResource // in WCF DS, and works as follows: // - Create a new, blank instance of the entity. // - Copy over the key values and set any updated values from the client on the new instance. // - Then apply all the properties of the new instance to the instance to be updated. // This will set any unspecified properties to their default value. object newInstance = Activator.CreateInstance(entityType); SetValues(newInstance, entityType, entry.EntityKey); SetValues(newInstance, entityType, entry.LocalValues); dbEntry.CurrentValues.SetValues(newInstance); } else { foreach (KeyValuePair <string, object> propertyPair in entry.LocalValues) { DbPropertyEntry propertyEntry = dbEntry.Property(propertyPair.Key); object value = propertyPair.Value; if (value == null) { // If the property value is null, we set null in the entry too. propertyEntry.CurrentValue = null; continue; } Type type = typeof(string); if (propertyEntry.EntityEntry != null && propertyEntry.EntityEntry.Entity != null) { type = propertyEntry.EntityEntry.Entity.GetType().GetProperty(propertyPair.Key).PropertyType; } else if (propertyEntry.CurrentValue != null) { type = propertyEntry.CurrentValue.GetType(); } if (propertyEntry is DbComplexPropertyEntry) { var dic = value as IReadOnlyDictionary <string, object>; if (dic == null) { throw new NotSupportedException(string.Format("Unsupported type for property: {0}.", propertyPair.Key)); } value = Activator.CreateInstance(type); SetValues(value, type, dic); } propertyEntry.CurrentValue = ConvertToEfValue(type, value); } } }
private static object CreateFullUpdateInstance(DataModificationEntry entry, Type entityType) { // The algorithm for a "FullReplaceUpdate" is taken from ObjectContextServiceProvider.ResetResource // in WCF DS, and works as follows: // - Create a new, blank instance of the entity. // - Copy over the key values and set any updated values from the client on the new instance. // - Then apply all the properties of the new instance to the instance to be updated. // This will set any unspecified properties to their default value. object newInstance = Activator.CreateInstance(entityType); ChangeSetPreparer.SetValues(newInstance, entityType, entry.EntityKey); ChangeSetPreparer.SetValues(newInstance, entityType, entry.LocalValues); return(newInstance); }
/// <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()); }
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)); } }
private static void SetValues(DbEntityEntry dbEntry, DataModificationEntry entry, Type entityType) { if (entry.IsFullReplaceUpdate) { // The algorithm for a "FullReplaceUpdate" is taken from ObjectContextServiceProvider.ResetResource // in WCF DS, and works as follows: // - Create a new, blank instance of the entity. // - Copy over the key values and set any updated values from the client on the new instance. // - Then apply all the properties of the new instance to the instance to be updated. // This will set any unspecified properties to their default value. object newInstance = Activator.CreateInstance(entityType); SetValues(newInstance, entityType, entry.EntityKey); SetValues(newInstance, entityType, entry.LocalValues); dbEntry.CurrentValues.SetValues(newInstance); } else { foreach (KeyValuePair <string, object> propertyPair in entry.LocalValues) { DbPropertyEntry propertyEntry = dbEntry.Property(propertyPair.Key); object value = propertyPair.Value; if (propertyEntry is DbComplexPropertyEntry) { var dic = value as IReadOnlyDictionary <string, object>; if (dic == null) { // TODO GitHubIssue#103 : Choose property error message for unknown type throw new NotSupportedException("Unsupported type for property:" + propertyPair.Key); } var type = propertyEntry.CurrentValue.GetType(); value = Activator.CreateInstance(type); SetValues(value, type, dic); } propertyEntry.CurrentValue = ConvertToEfDateTimeIfValueIsEdmDate(value); } } }
/// <summary> /// Handles a POST request to create an entity. /// </summary> /// <param name="edmEntityObject">The entity object to create.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task object that contains the creation result.</returns> public async Task <IHttpActionResult> Post(EdmEntityObject edmEntityObject, CancellationToken cancellationToken) { if (!this.ModelState.IsValid) { return(BadRequest(this.ModelState)); } ODataPath path = this.GetPath(); IEdmEntitySet entitySet = path.NavigationSource as IEdmEntitySet; if (entitySet == null) { throw new NotImplementedException(Resources.InsertOnlySupportedOnEntitySet); } DataModificationEntry postEntry = new DataModificationEntry( entitySet.Name, path.EdmType.FullTypeName(), null, null, edmEntityObject.CreatePropertyDictionary()); RestierChangeSetProperty changeSetProperty = this.Request.GetChangeSet(); if (changeSetProperty == null) { ChangeSet changeSet = new ChangeSet(); changeSet.Entries.Add(postEntry); SubmitResult result = await Api.SubmitAsync(changeSet, cancellationToken); } else { changeSetProperty.ChangeSet.Entries.Add(postEntry); await changeSetProperty.OnChangeSetCompleted(); } return(this.CreateCreatedODataResult(postEntry.Entity)); }
private async Task <IHttpActionResult> Update( EdmEntityObject edmEntityObject, bool isFullReplaceUpdate, CancellationToken cancellationToken) { ODataPath path = this.GetPath(); IEdmEntitySet entitySet = path.NavigationSource as IEdmEntitySet; if (entitySet == null) { throw new NotImplementedException(Resources.UpdateOnlySupportedOnEntitySet); } DataModificationEntry updateEntry = new DataModificationEntry( entitySet.Name, path.EdmType.FullTypeName(), RestierQueryBuilder.GetPathKeyValues(path), this.GetOriginalValues(), edmEntityObject.CreatePropertyDictionary()); updateEntry.IsFullReplaceUpdate = isFullReplaceUpdate; RestierChangeSetProperty changeSetProperty = this.Request.GetChangeSet(); if (changeSetProperty == null) { ChangeSet changeSet = new ChangeSet(); changeSet.Entries.Add(updateEntry); SubmitResult result = await Api.SubmitAsync(changeSet, cancellationToken); } else { changeSetProperty.ChangeSet.Entries.Add(updateEntry); await changeSetProperty.OnChangeSetCompleted(); } return(this.CreateUpdatedODataResult(updateEntry.Entity)); }
private static void SetValues(EntityEntry dbEntry, DataModificationEntry entry, Type entityType) { //StateEntry stateEntry = ((IAccessor<InternalEntityEntry>) dbEntry.StateEntry; IEntityType edmType = dbEntry.Metadata; if (entry.IsFullReplaceUpdate) { // The algorithm for a "FullReplaceUpdate" is taken from WCF DS ObjectContextServiceProvider.ResetResource, and is as follows: // Create a new, blank instance of the entity. Copy over the key values, and set any updated values from the client on the new instance. // Then apply all the properties of the new instance to the instance to be updated. This will set any unspecified // properties to their default value. object newInstance = Activator.CreateInstance(entityType); ChangeSetPreparer.SetValues(newInstance, entityType, entry.EntityKey); ChangeSetPreparer.SetValues(newInstance, entityType, entry.LocalValues); foreach (var property in edmType.GetProperties()) { object val; if (!entry.LocalValues.TryGetValue(property.Name, out val)) { PropertyInfo propertyInfo = entityType.GetProperty(property.Name); val = propertyInfo.GetValue(newInstance); } //stateEntry[property] = val; dbEntry.Property(property.Name).CurrentValue = val; } } else { // For some properties like DateTimeOffset, the backing EF property could be of a different type like DateTime, so we can't just // copy every property pair in DataModificationEntry to EF StateEntry, instead we let the entity type to do the conversion, by // first setting the EDM property (in DataModificationEntry) to a entity instance, then getting the EF mapped property from the // entity instance and set to StateEntry. object instance = null; foreach (var property in edmType.GetProperties()) { object val; var edmPropName = (string)property["EdmPropertyName"]; if (edmPropName != null && entry.LocalValues.TryGetValue(edmPropName, out val)) { if (instance == null) { instance = Activator.CreateInstance(entityType); } PropertyInfo edmPropInfo = entityType.GetProperty(edmPropName); edmPropInfo.SetValue(instance, val); PropertyInfo propertyInfo = entityType.GetProperty(property.Name); val = propertyInfo.GetValue(instance); } else if (!entry.LocalValues.TryGetValue(property.Name, out val)) { continue; } //stateEntry[property] = val; dbEntry.Property(property.Name).CurrentValue = val; } } }