views:

112

answers:

2

Are there any ways, besides throwing exceptions, that one can go about using the partial validation methods in LINQ to SQL to cancel the insert of a record?

+1  A: 

Ultimately this indicates that at you last line of defence (before any database constraints, at least) your data was invalid. If you want to do something other than scream loudly, then perhaps verify the data (via any of a multitude of approaches) before adding it to the insert list.

As an additional thought, you could try overriding SubmitChanges (on the data-context); obtain the change-set, verify the inserts and remove (delete-on-submit, which IIRC checks the insert list and drops them) any that you've decided were mistakes. Then call the base.SubmitChanges. But to me this is a bit backwards.

To illustrate, this only does a single insert (not two as requested), but I don't like this approach. At all. As long as we're clear ;-p

namespace ConsoleApplication1 {
    partial class DataClasses1DataContext { // extends the generated data-context
        public override void SubmitChanges(
                System.Data.Linq.ConflictMode failureMode) {
            var delta = GetChangeSet();
            foreach (var item in delta.Inserts.OfType<IEntityCheck>()) {
                if (!item.IsValid()) {
                    GetTable(item.GetType()).DeleteOnSubmit(item);
                }
            }
            base.SubmitChanges(failureMode);
        }
    }
    public interface IEntityCheck { // our custom basic validation interface
        bool IsValid();
    }
    partial class SomeTable : IEntityCheck { // extends the generated entity
        public bool IsValid() { return this.Val.StartsWith("d"); }
    }
    static class Program {
        static void Main() {
            using (var ctx = new DataClasses1DataContext()) {
                ctx.Log = Console.Out; // report what it does
                ctx.SomeTables.InsertOnSubmit(new SomeTable { Val = "abc" });
                ctx.SomeTables.InsertOnSubmit(new SomeTable { Val = "def" });
                ctx.SubmitChanges();
            }
        }
    }
}
Marc Gravell
I never said I wanted to perform two inserts :) I like this approach. I will give this a go and let you know how I went. Thanks :)
Sir Psycho
@Marc Gravell: While it answers his question, I think it's a bit scary to silently remove invalid entities before saving. I know Phycho asks about not using exceptions, but IMO it's better to throw an exception with a decent exception message.
Steven
Steven, It's not about being silent, It's about using exceptions for exceptional cases. Validation is not something unexpected and therefore doesn't need an exception to be thrown...IMO :-)
Sir Psycho
@Sir Psycho - I kind of agree with Steven here - you're way past the point that regular validation should occur. If something is wrong *at this point*, there's a problem and an exception is reasonable.
Marc Gravell
A: 

I can understand that you don't want to throw an exception directly after a property is set with an invalid value. This approach makes it difficult to communicate correctly to the user what actually is wrong. However, I think it's better to keep away from using those partial validation methods. IMO you want to throw an exception when your model is invalid, but only just before you're persisting your model to the database.

I advise you to use a validation framework and integrate it with your LINQ to SQL DataContext class. Here's an example of how to do this with The Enterprise Library Validation Application Block, but the concept will work for every validation framework you pick:

public partial class NorthwindDataContext
{
    public override void SubmitChanges(ConflictMode failureMode)
    {
        ValidationResult[] = this.Validate();

        if (invalidResults.Length > 0)
        {
            // You should define this exception type
            throw new ValidationException(invalidResults);
        }

        base.SubmitChanges(failureMode);
    }

    private ValidationResult[] Validate()
    {
        // here we use the Validation Application Block.
        return invalidResults = (
            from entity in this.GetChangedEntities()
            let type = entity.GetType()
            let validator = ValidationFactory.CreateValidator(type)
            let results = validator.Validate(entity)
            where !results.IsValid
            from result in results
            select result).ToArray();            
    }

    private IEnumerable<object> GetChangedEntities()
    {
        ChangeSet changes = this.GetChangeSet();

        return changes.Inserts.Concat(changes.Updates);
    }
}

[Serializable]
public class ValidationException : Exception
{
    public ValidationException(IEnumerable<ValidationResult> results)
        : base("There are validation errors.")
    {
        this.Results = new ReadOnlyCollection<ValidationResult>(
            results.ToArray());
    }

    public ReadOnlyCollection<ValidationResult> Results
    {
        get; private set; 
    }
}

There are several validation frameworks available, such as DataAnnotations and the Enterprise Library Validation Application Block (VAB). VAB is very suited for doing this. With LINQ to SQL your entities are generated, so you'll need to use the configuration based approach that VAB offers (don’t try decorating your entities with attributes). By overriding the SubmitChanges method you can make sure the validation gets triggered just before entities are persisted. My SO answers here and here contain useful information about using VAB.

I've written a few interesting articles about integrating VAB with LINQ to SQL here and here. The nice thing about LINQ to SQL (compared to Entity Framework 1.0) is that a lot of useful metadata is generated. When combining this with VAB you can use this metadata to validate your model, without having to hook up every validation manually. Especially validations as maximum string length and not null can be extracted from the model. Read here how to do this.

VAB to the rescue!

Steven
I will take a look at those articles. Thanks
Sir Psycho
@Sir Psycho: I've updated my answer. This hopefully better answers your question.
Steven