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?

Ian Nelson
  • 57,123
  • 20
  • 76
  • 103
Razor
  • 17,271
  • 25
  • 91
  • 138
  • If I understood the question right, I guess you could use TransactionScope : http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx and commit or dispose changes. – Jacob Mar 07 '10 at 10:56

2 Answers2

1

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
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 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 :) – Razor Mar 08 '10 at 11:45
  • @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 Mar 08 '10 at 13:33
  • 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 :-) – Razor Mar 09 '10 at 10:35
  • @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 Mar 09 '10 at 12:09
1

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
  • 166,672
  • 24
  • 332
  • 435
  • I will take a look at those articles. Thanks – Razor Mar 08 '10 at 11:42
  • @Sir Psycho: I've updated my answer. This hopefully better answers your question. – Steven Mar 08 '10 at 13:28
  • Do you consider any of your suggestions here out of date? – Merritt Mar 06 '12 at 23:08
  • I believe this method is still valid, however people are using L2S less and less. The method of automatically validating on commit is something that Entity Framework 4.1 does out of the box, but it (by default) uses DataAnnotations. Still not out of date is the using the VAB configuration to define the validation, but I never liked it much. I rather use a fluent configuration API for configuring the validation on object. Unfortunately VAB does not support such thing out of the box and you need to define such API yourself (but you can [start here](http://stackoverflow.com/a/8906203/264697)). – Steven Mar 07 '12 at 08:51