1

I'm fairly new to the world of MVC and EF, and I've come quite a ways on my own, but one thing I haven't been able to find online is how people validate for "do not delete" conditions.

I'm using EF4.1 database-first POCO classes generated with the DbContext T4 template. In my partial class files I've already decorated all of my classes with the "IValidatableObject" interface that gets called on changes for my business rules that go beyond the standard MetaData attribute type of validations.

What I need now is a validation that works via the same mechanism (and is, therefore, transparent to the UI and the controller) for checking if deletions are OK. My thought was to create an interface like so:

public interface IDeletionValidation
{
    DbEntityValidationResult ValidateDeletion(DbEntityValidationResult validationResults);
}

...and then do this in an override to ValidateEntity in the DbContext...

public partial class MyEntityContext
{
   protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
    {
        DbEntityValidationResult val = base.ValidateEntity(entityEntry, items);
        if (entityEntry.State == EntityState.Deleted)
        {
            IDeletionValidation delValidationEntity = entityEntry.Entity as IDeletionValidation;
            if (delValidationEntity != null)
                val = delValidationEntity.ValidateDeletion(val);
        }
        return val;
    }

...and then I could implement the IDeletionValidation interface on those classes that need to have a validation done before they can be safely deleted.

An example (not working, see caveat in comments) of the ValidateDeletion code would be...

public partial class SalesOrder : IDeletionValidation, IValidatableObject  
{
    public DbEntityValidationResult ValidateDeletion(DbEntityValidationResult validations)
    {
        // A paid SalesOrder cannot be deleted, only voided
        // NOTE: this code won't work, it's coming from my head and note from the actual source, I forget
        // what class I'd need to add to the DbEntityValidationResult collection for this type of validation! 
        if (PaidAmount != 0) 
             validations.Add(new ValidationResult("A paid SalesOrder cannot be deleted, only voided"));
        return validations;
    }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        List<ValidationResult> validations = new List<ValidationResult>();

        // Verify that the exempt reason is filled in if the sales tax flag is blank
        if (!IsTaxable && string.IsNullOrEmpty(TaxExemptReason))
            validations.Add(new ValidationResult("The Tax Exempt Reason cannot be blank for non-taxable orders"));

        return validations;
    }

    ....
}

Am I on the right track? Is there a better way?

Thanks,
CList

EDIT --- Summary of the one-interface method proposed by Pawel (below)

I think the one-interface way presented below and my way above is a little bit of a chocolate vs. vanilla argument in terms of how you want to do it. Performance should be about the same for large numbers of updates / deletes, and you may want to have your delete validation be a separate interface that doesn't apply to all of your validated classes, but if you want all of your validations in one place here it is...

Mod your DBContext

protected override bool ShouldValidateEntity(DbEntityEntry entityEntry)
{
    return entityEntry.Sate == EntityState.Deleted || 
           base.ShouldValidateEntity(entityEntry);
}

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
    var myItems = new Dictionary<object, object>();
    myItems.Add("IsDelete", (entityEntry.State == EntityState.Deleted));

    // You could also pass the whole context to the validation routines if you need to, which might be helpful if the 
    // validations need to do additional lookups on other DbSets
    // myItems.Add("Context", this);

    return base.ValidateEntity(entityEntry, myItems);
}

Put the deletion-validation in your entity's Validate

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        List<ValidationResult> validations = new List<ValidationResult>();

        bool isDelete = validationContext.Items.ContainsKey("IsDelete")
                                ? (bool)validationContext.Items["IsDelete"] 
                                : false;
        if (isDelete)
        {
            if (PaidAmount != 0)
                validations.Add(new ValidationResult("You cannot delete a paid Sales Order Line", new string[] { "PaidAmount" }));
            return validations;
        }

        // Update / Add validations!!
        // Verify that the exempt reason is filled in if the sales tax flag is blank
        if (!IsTaxable && string.IsNullOrEmpty(TaxExemptReason))
            validations.Add(new ValidationResult("The Tax Exempt Reason cannot be blank for non-taxable orders"));

        return validations;
    }

...and in the interest of brevity and only putting all of the check-if-delete code in one place, you could even create an extension method on the ValidationContext class (if you're into that sort of thing) like so...

public static class MyExtensions
{
    public static bool IsDelete(this System.ComponentModel.DataAnnotations.ValidationContext validationContext)
    {
        return validationContext.Items.ContainsKey("IsDelete")
                                ? (bool)validationContext.Items["IsDelete"] 
                                : false;
    }
}

...which gives us this for our validation code...

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        List<ValidationResult> validations = new List<ValidationResult>();

        if (validationContext.IsDelete())
        {
            ....
4

1 回答 1

1

我不太确定为什么您需要一个单独的界面来仅用于已删除的实体。您可以使用传递给 base.ValidateEntity() 方法的项目字典将实体状态(或 EntityEntry 对象或上下文)传递给 IValidatableObject.Validate() 方法。查看此博客文章http://blogs.msdn.com/b/adonet/archive/2011/05/27/ef-4-1-validation.aspx中的“自定义验证示例:Uniqness”部分。这样,您可以只使用一个接口 - IValidatableObject 来完成所有操作。除此之外 - 默认情况下,EF 仅验证已添加和已修改的实体。如果要验证处于 Deleted 状态的实体,您需要使用以下内容覆盖 DbContext.ShouldValidateEntity() 方法:

protected override bool ShouldValidateEntity(DbEntityEntry entityEntry)
{
    return entityEntry.Sate == EntityState.Deleted || 
               base.ShouldValidateEntity(entityEntry);
}
于 2012-09-14T18:14:57.050 回答