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);
}
}
Built-in validation is invoked only when you are using DbContext APIs and not ObjectContext APIs.