Reputation: 23
By default, EF uses EntityObject when generating the objects. I've modified it to use my own AbstractEntityObject class. In doing so, I've added IValidatableObject as I was under the impression when you called context.SaveChanges() it would automatically call Validate and throw the exceptions.
Here is what I have:
public abstract class AbstractEntityObject : EntityObject, IValidatableObject
{
private readonly DBTable table;
protected AbstractEntityObject(DBTable table)
{
this.table = table;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<OITValidationResult> results = new List<OITValidationResult>();
List<PropertyInfo> properties = new List<PropertyInfo>(GetType().GetProperties());
foreach (DBField field in table.GetFields())
{
foreach (PropertyInfo prop in properties)
if (StringUtilities.EqualsIgnoreCase(field.FieldName, prop.Name))
{
results.AddRange(field.Validate(prop.GetValue(this, null)));
results.AddRange(AdditionalValidation(field, prop));
properties.Remove(prop);
break;
}
}
return results;
}
public abstract List<OITValidationResult> AdditionalValidation(DBField field, PropertyInfo prop);
}
public abstract class AbstractTLMSEntityObject : AbstractEntityObject
{
protected AbstractTLMSEntityObject(DBTable table)
: base(table)
{
}
public override List<OITValidationResult> AdditionalValidation(DBField field, PropertyInfo prop)
{
List<OITValidationResult> results = new List<OITValidationResult>();
if (!EntityState.Equals(EntityState.Unchanged))
{
if (StringUtilities.EqualsIgnoreCase(field.FieldName, "userid"))
prop.SetValue(this, TLMSDB.User.UserName, null);
else if (StringUtilities.EqualsIgnoreCase(prop.Name, "dtmod"))
prop.SetValue(this, DateTime.Now, null);
}
OITValidationResult additionalResult = AdditionalValidation(field.Field);
if (additionalResult != null)
results.Add(additionalResult);
return results;
}
/* By default there is no additional validation, subclasses should override this if they need additional validation */
public virtual OITValidationResult AdditionalValidation(Enum field)
{
return null;
}
}
Then, I have the subclass, which is a partial class along with the one that EF creates:
public partial class CSDCrud
{
public enum Fields
{
RECEIPT_NUMBER,
GRANT_ID,
GRANT_FUND,
TOTAL_ACRES
}
public CSDCrud() : base(CSDCrudDAO.Instance.Table)
{
}
public static String GetDataPropertyName(Enum field)
{
return CSDCrudDAO.Instance.Table.GetField(field).FieldName;
}
}
CSDCrud inherits from AbstractTLMSEntityObject thanks to me changing that in the .tt file.
Now here is where it gets weird. I have set it up so the DBField (referenced in the parent class) does self validation of the data. In this case, I have set it up so if receipt_number is required and thus will fail and throw an exception...In fact, the following occurs...
List<OITValidationResult> results = (List<OITValidationResult>)crudObject.Validate(null);
try
{
CommonEntityManager.GetContext().CrudSet.AddObject(crudObject);
CommonEntityManager.GetContext().SaveChanges();
return true;
}
catch (Exception ex)
{
return false;
}
As expected, results contains 1 item which is the proper error...however the SaveChanges saves it out just fine, without throwing an exception...what am I missing?
edit: I should note, obviously I can use the SavingChanges event to add in my own handler, but I'm hoping to use the pre-existing infrastructure.
static void context_SavingChanges(object sender, EventArgs e)
{
foreach (ObjectStateEntry ose in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
{
List<ValidationResult> results = new List<ValidationResult>(((IValidatableObject)ose.Entity).Validate(null));
if (results.Count > 0)
throw new CustomException(results);
}
foreach (ObjectStateEntry ose in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added))
{
List<ValidationResult> results = new List<ValidationResult>(((IValidatableObject)ose.Entity).Validate(null));
if (results.Count > 0)
throw new CustomException(results);
}
}
Upvotes: 2
Views: 1030
Reputation: 25955
Built-in custom validations in your entity object are not invoked explicitly by objectContext class but programmer needs to take care of this explicitly. To do that explicitly from your end SavingChanges
event is the best place. This is how you can do it? I'm describing this in a quick code snippet that I prepared in a console application:
Course.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects.DataClasses;
using System.ComponentModel.DataAnnotations;
namespace EFCoding
{
public partial class Course : EntityObject, IValidatableObject
{
List<ValidationResult> validationErrors = new List<ValidationResult>();
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return validationErrors;
}
partial void OnNameChanging(global::System.String value)
{
validationErrors.Clear();
if (value.Length > 10)
{
ValidationResult vResult = new ValidationResult("Invalid Course Name", new string[] { "EmailID" });
validationErrors.Add(vResult);
}
}
partial void OnDisciplineChanging(global::System.String value)
{
validationErrors.Clear();
if (value.Length > 10)
{
ValidationResult vResult = new ValidationResult("Invalid Discipline", new string[] { "EmailID" });
validationErrors.Add(vResult);
}
}
#region Factory Method
/// <summary>
/// Create a new Course object.
/// </summary>
/// <param name="id">Initial value of the Id property.</param>
/// <param name="name">Initial value of the Name property.</param>
/// <param name="discipline">Initial value of the Discipline property.</param>
public static Course CreateCourse(global::System.Int32 id, global::System.String name, global::System.String discipline)
{
Course course = new Course();
course.Id = id;
course.Name = name;
course.Discipline = discipline;
return course;
}
#endregion
#region Primitive Properties
/// <summary>
/// No Metadata Documentation available.
/// </summary>
[EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
[DataMemberAttribute()]
public global::System.Int32 Id
{
get
{
return _Id;
}
set
{
if (_Id != value)
{
OnIdChanging(value);
ReportPropertyChanging("Id");
_Id = StructuralObject.SetValidValue(value);
ReportPropertyChanged("Id");
OnIdChanged();
}
}
}
private global::System.Int32 _Id;
partial void OnIdChanging(global::System.Int32 value);
partial void OnIdChanged();
/// <summary>
/// No Metadata Documentation available.
/// </summary>
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.String Name
{
get
{
return _Name;
}
set
{
OnNameChanging(value);
ReportPropertyChanging("Name");
_Name = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("Name");
OnNameChanged();
}
}
private global::System.String _Name;
partial void OnNameChanging(global::System.String value);
partial void OnNameChanged();
/// <summary>
/// No Metadata Documentation available.
/// </summary>
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.String Discipline
{
get
{
return _Discipline;
}
set
{
OnDisciplineChanging(value);
ReportPropertyChanging("Discipline");
_Discipline = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("Discipline");
OnDisciplineChanged();
}
}
private global::System.String _Discipline;
partial void OnDisciplineChanging(global::System.String value);
partial void OnDisciplineChanged();
#endregion
}
}
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.Data.Objects;
using System.Data;
namespace EFCoding
{
class Program
{
static void Main(string[] args)
{
using (var efContext = new EfTestEntities1())
{
efContext.SavingChanges += new EventHandler(efContext_SavingChanges);
var course = new Course { Id = 2, Name = "Very long course name which will result in exception", Discipline = "Very long discipline name which will result in error at run time" };
efContext.Courses.AddObject(course);
efContext.SaveChanges();
}
}
static void efContext_SavingChanges(object sender, EventArgs e)
{
ObjectContext objectContext = sender as ObjectContext;
IEnumerable<Object> entities = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified).Select(x => x.Entity);
foreach (var item in entities)
{
var current = item as Course;
if (current != null)
{
var validationsErrors = current.Validate(new ValidationContext(current,null,null));
if (validationsErrors.Count() > 0)
{
throw new InvalidOperationException("Model has validation errors");
}
}
}
}
}
}
Upvotes: 0
Reputation: 31620
Built-in validation is invoked only when you are using DbContext APIs and not ObjectContext APIs.
Upvotes: 3