views:

569

answers:

3

Hello, I just started using LINQ to SQL classes, and really like how this helps me write readable code. In the documentation, typical examples state that to do custom validation, you create a partial class as so::

partial class Customer 
{
    partial void OnCustomerIDChanging(string value)
    {
        if (value=="BADVALUE") throw new NotImplementedException("CustomerID Invalid");
    }
}

And similarly for other fields... And then in the codebehind, i put something like this to display the error message and keep the user on same page so to correct the mistake.

    public void CustomerListView_OnItemInserted(object sender, ListViewInsertedEventArgs e)
{
    string errorString = "";
    if (e.Exception != null)
    {
      e.KeepInInsertMode = true;
      errorString += e.Exception.Message;
      e.ExceptionHandled = true;
    }
    else errorString += "Successfully inserted Customer Data" + "\n";
    errorMessage.Text = errorString;
}

Okay, that's easy, but then it stops validating the rest of the fields as soon as the first Exception is thrown!! Mean if the user made mode than one mistake, she/he/it will only be notified of the first error. Is there another way to check all the input and show the errors in each ? Any suggestions appreciated, thanks.

A: 

Not with LINQ. Presumably you would validate the input before giving it to LINQ.

What you're seeing is natural behaviour with exceptions.

Lasse V. Karlsen
Thanks for the suggestion. I think you mean do the validation using the Validation controls from ASP.NET? I prefer not to do it like this, I prefer to limit the asp.net part to simple textboxes, listview or gridview controls, etc .. but maybe instead of throwing an exception, i can trigger some even to store the error.
Slabo
A: 

I figured it out. Instead of throwing an exception at first failed validation, i store an error message in a class with static variable. to do this, i extend the DataContext class like this::

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

/// <summary>
/// Summary description for SalesClassesDataContext
/// </summary>
public partial class SalesClassesDataContext
{
    public class ErrorBox
    {
        private static List<string> Messages = new List<string>();
        public void addMessage(string message)
        {
            Messages.Add(message);
        }
        public List<string> getMessages() 
        {
            return Messages;
        }
    }
}

in the classes corresponding to each table, i would inherit the newly defined class like this::

public partial class Customer : SalesClassesDataContext.ErrorBox

only in the function OnValidate i would throw an exception in case the number of errors is not 0. Hence not attempting to insert, and keeping the user on same input page, without loosing the data they entered.

Slabo
Slabo, this is not a forum site, and as such, you should not post an answer to your own question unless it is actually an answer. If you have more to add, then please edit your original question.
Darien Ford
A: 

This looks like a job for the Enterprise Library Validation Application Block (VAB). VAB has been designed to return all errors. Besides this, it doesn't thrown an exception, so you can simply ask it to validate the type for you.

When you decide to use the VAB, I advise you to -not- use the OnXXXChanging and OnValidate methods of LINQ to SQL. It's best to override the SubmitChange(ConflictMode) method on the DataContext class to call into VAB's validation API. This keeps your validation logic out of your business entities, which keeps your entities clean.

Look at the following example:

public partial class NorthwindDataContext
{
    public ValidationResult[] Validate()
    {
        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();            
    }

    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 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; 
    }
}

Calling the Validate() method will return a collection of all errors, but rather than calling Validate(), I'd simply call SubmitChanges() when you're ready to persist. SubmitChanges() will now check for errors and throw an exception when one of the entities is invalid. Because the list of errors is sent to the ValidationException, you can iterate over the errors higher up the call stack, and present them to the user, as follows:

try
{
    db.SubmitChanges();
}
catch (ValidationException vex)
{
    ShowErrors(vex.ValidationErrors);
}

private static void ShowErrors(IEnumerable<ValidationResult> errors)
{
    foreach(var error in errors)
    {
        Console.WriteLine("{0}: {1}", error.Key, error.message);
    }
}

When you use this approach you make sure that your entities are always validated before saving them to the database

Here is a good article that explains how to integrate VAB with LINQ to SQL. You should definitely read it if you want to use VAB with LINQ to SQL.

Steven