Delete Request Handler

The DeleteRequestHandler is the base class that handles Delete service requests. Like the other handlers, Sergen generates a small subclass for every entity; the base class contains all the default logic.

Generated Handler

For a Language entity, Sergen generates:

using MyRow = MyProject.Administration.LanguageRow;

namespace MyProject.Administration;

public interface ILanguageDeleteHandler : IDeleteHandler<MyRow> { }

public class LanguageDeleteHandler(IRequestContext context)
    : DeleteRequestHandler<MyRow>(context), ILanguageDeleteHandler
{
}

The class derives from the generic DeleteRequestHandler<TRow> and IDeleteRequestHandler.

The Service Endpoint

The endpoint exposes a single Delete action:

[Route("Services/Administration/Language/[action]")]
[ConnectionKey(typeof(MyRow)), ServiceAuthorize(typeof(MyRow))]
public class LanguageEndpoint : ServiceEndpoint
{
    [HttpPost, AuthorizeDelete(typeof(MyRow))]
    public DeleteResponse Delete(IUnitOfWork uow, DeleteRequest request,
        [FromServices] ILanguageDeleteHandler handler)
    {
        return handler.Delete(uow, request);
    }
}

[AuthorizeDelete(typeof(MyRow))] validates the delete permission declared on the row.

The Request and Response

The request is a DeleteRequest:

public class DeleteRequest : ServiceRequest
{
    public object EntityId { get; set; }
}

It only contains the ID of the record to delete. The response is a DeleteResponse:

public class DeleteResponse : ServiceResponse
{
    public bool WasAlreadyDeleted { get; set; }
}

WasAlreadyDeleted is true if the record was already deleted (soft deleted) before this call.

Hard Delete vs Soft Delete

The delete behavior depends on the row type:

  • Hard delete — if the row is a plain table row (no IsActive/IsDeleted field), the record is physically removed with a DELETE statement.
  • Soft delete — if the row implements IIsActiveDeletedRow (i.e. it has an IsActive field where -1 means deleted), the handler issues an UPDATE ... SET IsActive = -1 instead of deleting. The record stays in the table and can be restored later with the Undelete Request Handler.
  • IsDeleted soft delete — if the row implements IIsDeletedRow (an IsDeleted field), the handler sets IsDeleted = true.

IsActive is a common pattern in Serene/StartSharp templates: rows implementing IIsActiveRow use 1 for active and 0 for inactive, and -1 for deleted (the IIsActiveDeletedRow interface marks that -1 is the deleted value).

Lifecycle Methods

The main overridable methods are:

  • OnBeforeDelete() — called before the delete/soft-delete is executed.
  • OnAfterDelete() — called after the delete/soft-delete succeeds.
  • OnReturn() — called just before the response is returned.

OnBeforeDelete/OnAfterDelete also invoke any registered DeleteBehavior.OnBeforeDelete / OnAfterDelete.

The Movie tutorial manually deletes a movie's cast entries before deleting the movie, to avoid foreign-key errors:

public class MovieDeleteHandler : DeleteRequestHandler<MyRow>, IMovieDeleteHandler
{
    private readonly IServiceResolver<IMovieCastDeleteHandler> movieCastDelete;

    public MovieDeleteHandler(IRequestContext context,
        IServiceResolver<IMovieCastDeleteHandler> movieCastDelete) : base(context)
    {
        this.movieCastDelete = movieCastDelete;
    }

    protected override void OnBeforeDelete()
    {
        base.OnBeforeDelete();

        var mc = MovieCastRow.Fields;
        foreach (var detailID in Connection.Query<Int32>(
            new SqlQuery().From(mc)
                .Select(mc.MovieCastId)
                .Where(mc.MovieId == Row.MovieId.Value)))
        {
            movieCastDelete.Resolve().Delete(UnitOfWork, new() { EntityId = detailID });
        }
    }
}

Note that for master–detail relations you normally don't need to do this by hand — the MasterDetailRelation behavior handles related records automatically.

Behaviors

Behaviors implementing IDeleteBehavior run for every delete handler and are the recommended way to add cross-cutting delete logic.

See Also