Is there a better way to handle validation in LINQ to SQL?
Are there any ways, besides throwing exceptions, that one can go about using the partial开发者_StackOverflow validation methods in LINQ to SQL to cancel the insert of a record?
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!
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();
}
}
}
}
精彩评论