I'm using EF5 and have entities in POCO classes. My first question is what is the best place to implement business rules and validation?
My first guess is to place it directly into POCO class into some Validate() function that gets called from DBContext when SaveChanges() is triggered.
This works well but some of the rules require validation across multiple entities like this
example for class Invoice:
if(this.Items.Where(i=>i.Price > 100).Count() > 0)
{
//mark invoice for review
this.IsForReview = true;
}
Now the unit tests would then test the Validation function (for each business rule) but will also have to populate the invoice class with Items (otherwise it would be always empty)
Another idea is to create an InvoiceValidation class with separate validation functions (or even class per each rule?) that are easier to unit test but it does increase the number of files/classes to maintain.
Any suggestions or links to existing solutions would be appreciated
The best way will depend on your dependencies.
Does the POCO / core assembly depend on EF ?
Do you inject Access to DB into your core Library assembly? etc.
I personally use repository/luw pattern, where the various repository objects inherit from a base repository object that is generic. the DAL depends on EF but the POCO classes in core dont.
The repository sub class has a a specific type and DOES the OTHER OBEJCT business checks.
IE business rules that require other entities to be checked , i implement in DAL.
The repository classes belong to the Data Access layer Project and DO have a dependency on EF and have the Context injected. Example below.
Checks specific to the instance I perform on the POCO.
Checks that require DB access I perform via an Interface implemented on a base Class respository class that is inturn overriden as requried. So now calls CheckEntity are triggered when adding or changing an object.
eg
... NOTE some code removed to keep example relevant...
public class RepositoryEntityBase<T> : IRepositoryEntityBase<T>, IRepositoryEF<T> where T : BaseObject
public virtual OperationStatus Add(T entity)
{
var opStatus = new OperationStatus(status: true, operation: OperationType.Add);
try
{
if (OnBeforeAdd != null) // registered listeners of added event?
{
var evtArg = PrepareEventArgs(entity, MasterEventType.Create);
OnBeforeAdd(this, evtArg);
}
opStatus = CheckBeforePersist(entity);
if (opStatus.Status)
{
Initialize(entity);
EntityDbSet.Add(entity);
if (OnAfterAdd != null) // registered listeners of added event?
{
var evtArg = PrepareEventArgs(entity, MasterEventType.Create);
OnAfterAdd(this, evtArg);
}
}
}
catch (Exception ex)
{
opStatus.SetFromException("Error Adding " + typeof(T), ex);
}
return opStatus;
}
//... then in a specific repository class
//... irepositorybase expects Check before persist.
public override OperationStatus CheckBeforePersist(MasterUser entity)
{
// base entity rule check first
var opStatus = new OperationStatus(true, OperationType.Check);
opStatus.ValidationResults = base.CheckEntity(entity);
if (opStatus.ValidationResults.Count > 0)
{
opStatus.Status = false;
opStatus.Message = "Validation Errors";
return opStatus;
}
//now check the local memory
var masterUser = Context.Set<MasterUser>().Local //in context
.Where(mu => mu.Id != entity.Id // not this record
&& mu.UserName == entity.UserName ) // same name
.FirstOrDefault();
if (masterUser != null)
{
opStatus.Status = false;
opStatus.Message = "Duplicate UserName :" + masterUser.UserName + " UserId:"+ masterUser.Id.ToString();
return opStatus;
}
masterUser = Context.Set<MasterUser>().Local //in context
.Where(mu => mu.Id != entity.Id // not this record
&& mu.Email == entity.Email) // same email
.FirstOrDefault();
if (masterUser != null)
{
opStatus.Status = false;
opStatus.Message = "Duplicate Email :" + masterUser.Email + " Username:" + masterUser.UserName;
return opStatus;
}
// now check DB
masterUser = Get(mu => mu.Id != entity.Id //not this record being checked
&& mu.UserName == entity.UserName); // has same username
if (masterUser != null)
{
opStatus.Status = false;
opStatus.Message = "Duplicate UserName :" + masterUser.UserName + " UserId:"+ masterUser.Id.ToString();
return opStatus;
}
masterUser = Get(mu => mu.Id != entity.Id // not this record
&& mu.Email == entity.Email); // but same email
if (masterUser != null)
{
opStatus.Status = false;
opStatus.Message = "Duplicate Email:" + masterUser.Email + " UserName:"+ masterUser.UserName;
return opStatus;
}
return opStatus;
}
}
I would suggest something like Fluent Validation (http://fluentvalidation.codeplex.com/) which allows you to take a collection of rules and put them together into a single context separate from the POCO class it's validating.
If anyone is interested this is the best example I found so far:
http://codeinsanity.com/archive/2008/12/02/a-framework-for-validation-and-business-rules.aspx
Related
I am one of the many struggling to "upgrade" from ASP.NET to ASP.NET Core.
In the ASP.NET project, I made database calls from my DAL like so:
var result = context.Database.SqlQuery<Object_VM>("EXEC [sp_Object_GetByKey] #Key",
new SqlParameter("#Key", Key))
.FirstOrDefault();
return result;
My viewmodel has additional fields that my object does not, such as aggregates of related tables. It seems unnecessary and counter intuitive to include such fields in a database / table structure. My stored procedure calculates all those things and returns the fields as should be displayed, but not stored.
I see that ASP.NET Core has removed this functionality. I am trying to continue to use stored procedures and load view models (and thus not have the entity in the database). I see options like the following, but as a result I get "2", the number of rows being returned (or another mysterious result?).
using(context)
{
string cmd = "EXEC [sp_Object_getAll]";
var result = context.Database.ExecuteSQLCommand(cmd);
}
But that won't work because context.Database.ExecuteSQLCommand is only for altering the database, not "selecting".
I've also seen the following as a solution, but the code will not compile for me, as "set" is really set<TEntity>, and there isn't a database entity for this viewmodel.
var result = context.Set().FromSql("EXEC [sp_Object_getAll]");
Any assistance much appreciated.
Solution:
(per Tseng's advice)
On the GitHub Entity Framework Issues page, there is a discussion about this problem. One user recommends creating your own class to handle this sort of requests, and another adds an additional method that makes it run smoother. I changed the methods slights to accept slightly different params.
Here is my adaptation (very little difference), for others that are also looking for a solution:
Method in DAL
public JsonResult GetObjectByID(int ID)
{
SqlParameter[] parms = new SqlParameter[] { new SqlParameter("#ID", ID) };
var result = RDFacadeExtensions.GetModelFromQuery<Object_List_VM>(context, "EXEC [sp_Object_GetList] #ID", parms);
return new JsonResult(result.ToList(), setting);
}
Additional Class
public static class RDFacadeExtensions
{
public static RelationalDataReader ExecuteSqlQuery(
this DatabaseFacade databaseFacade,
string sql,
SqlParameter[] parameters)
{
var concurrencyDetector = databaseFacade.GetService<IConcurrencyDetector>();
using (concurrencyDetector.EnterCriticalSection())
{
var rawSqlCommand = databaseFacade
.GetService<IRawSqlCommandBuilder>()
.Build(sql, parameters);
return rawSqlCommand
.RelationalCommand
.ExecuteReader(
databaseFacade.GetService<IRelationalConnection>(),
parameterValues: rawSqlCommand.ParameterValues);
}
}
public static IEnumerable<T> GetModelFromQuery<T>(
DbContext context,
string sql,
SqlParameter[] parameters)
where T : new()
{
DatabaseFacade databaseFacade = new DatabaseFacade(context);
using (DbDataReader dr = databaseFacade.ExecuteSqlQuery(sql, parameters).DbDataReader)
{
List<T> lst = new List<T>();
PropertyInfo[] props = typeof(T).GetProperties();
while (dr.Read())
{
T t = new T();
IEnumerable<string> actualNames = dr.GetColumnSchema().Select(o => o.ColumnName);
for (int i = 0; i < props.Length; ++i)
{
PropertyInfo pi = props[i];
if (!pi.CanWrite) continue;
System.ComponentModel.DataAnnotations.Schema.ColumnAttribute ca = pi.GetCustomAttribute(typeof(System.ComponentModel.DataAnnotations.Schema.ColumnAttribute)) as System.ComponentModel.DataAnnotations.Schema.ColumnAttribute;
string name = ca?.Name ?? pi.Name;
if (pi == null) continue;
if (!actualNames.Contains(name)) { continue; }
object value = dr[name];
Type pt = pi.DeclaringType;
bool nullable = pt.GetTypeInfo().IsGenericType && pt.GetGenericTypeDefinition() == typeof(Nullable<>);
if (value == DBNull.Value) { value = null; }
if (value == null && pt.GetTypeInfo().IsValueType && !nullable)
{ value = Activator.CreateInstance(pt); }
pi.SetValue(t, value);
}//for i
lst.Add(t);
}//while
return lst;
}//using dr
}
Insert is working well. The update is not working for the collections. And do not give any error. What am I doing wrong?
In the code there is a brief explanation of the rules.
public void EditaModelo(Modelo model)
{
try
{
// ProjectResponsible - is an unmapped property. It was used to separate ModeloFuncao between type 2 and type 1 (ProjectResponsible and ProductDevelopment)
// ModeloFuncao is a collection within a model where for each function there is a responsibility within the model.
// In other words, for each record in the function table shows a field on the screen. The same goes for ProductDevelopment.
// When you save the model, there will be incusões, deletions and changes of those responsible.
// The IsUpdate property tells whether the record already exists and will be changed.
var ModelosToAdd = model.ProjectResponsible.Where(x => !String.IsNullOrWhiteSpace(x.Usuario) && !x.IsUpdate).ToList();
List<ModeloFuncao> ModelosToRemove = model.ProjectResponsible.Where(x => String.IsNullOrWhiteSpace(x.Usuario) && x.IsUpdate).ToList();
List<ModeloFuncao> ModelosToUpdate = model.ProjectResponsible.Where(x => !String.IsNullOrWhiteSpace(x.Usuario) && x.IsUpdate).ToList();
ModelosToAdd.AddRange(model.ProductDevelopment.Where(x => !String.IsNullOrWhiteSpace(x.Usuario) && !x.IsUpdate).ToList());
ModelosToRemove.AddRange(model.ProductDevelopment.Where(x => String.IsNullOrWhiteSpace(x.Usuario) && x.IsUpdate).ToList());
ModelosToUpdate.AddRange(model.ProductDevelopment.Where(x => !String.IsNullOrWhiteSpace(x.Usuario) && x.IsUpdate).ToList());
if(ModelosToAdd.Count > 0) context.ModelosFuncoes.AddRange(ModelosToAdd); //Insert is Ok
if (ModelosToRemove.Count > 0) context.ModelosFuncoes.RemoveRange(ModelosToRemove); //Not tested
if (ModelosToUpdate.Count > 0) ModelosToUpdate.ForEach(x => context.ModelosFuncoes.Attach(x)); //Not working
// The ModeloPlanta is a collection in the model table and follow the rules as explained below in ModeloPlantaArea.
List<ModeloPlanta> plantasToUpdate = model.ModelosPlantas.ToList();
plantasToUpdate.ForEach(x => context.ModelosPlantas.Attach(x));//Not working
// The ModeloPlantaArea is a collection in the model table. Each model has a number of plants and each plant has a number of areas.
// Each plant has a responsibility and each area has a responsibility.
// The screen should display a field for each plant x Responsible and for each Area x Responsible
// When you save the model, there will be incusões, deletions and changes of those responsible.
List<ModeloPlantaArea> AreasToAdd = model.PlantasArea.Where(x => !String.IsNullOrWhiteSpace(x.UsuarioResponsavel) && !x.IsUpdate).ToList();
List<ModeloPlantaArea> AreasToUpdate = model.PlantasArea.Where(x => !String.IsNullOrWhiteSpace(x.UsuarioResponsavel) && x.IsUpdate).ToList();
List<ModeloPlantaArea> AreasToRemove = model.PlantasArea.Where(x => String.IsNullOrWhiteSpace(x.UsuarioResponsavel) && x.IsUpdate).ToList();
if (AreasToAdd.Count > 0) context.ModelosPlantasArea.AddRange(AreasToAdd);//Insert is Ok
if (AreasToUpdate.Count > 0) AreasToUpdate.ForEach(x => context.ModelosPlantasArea.Attach(x));//Not working
if (AreasToRemove.Count > 0) context.ModelosPlantasArea.RemoveRange(AreasToRemove);//Not tested
// When saving Model, need to save (add, delete, change) collections. And if a collection fails, the other must not be saved.
// So far this Inclusion OK. The change is not working.
this.Update(model);
}
catch (Exception ex)
{
throw ex;
}
}
Update method:
public void Update(Modelo item, IEnumerable<string> fieldsToUpdate = null)
{
base.context.Modelos.Attach(item);
base.UpdateSave(item, fieldsToUpdate);
}
UpdateSave Method:
protected void UpdateSave<TEntity>(TEntity item, IEnumerable<string> fieldsToUpdate) where TEntity : class
{
var entry = this.context.Entry<TEntity>(item);
if (fieldsToUpdate == null || fieldsToUpdate.Count() == 0)
entry.State = System.Data.Entity.EntityState.Modified;
else
{
this.changeFieldsModified<TEntity>(entry, fieldsToUpdate);
}
this.Save();
}
Save Method:
protected void Save(bool auditing = true)
{
try
{
this.context.SaveChanges(auditing);
}
catch (DbEntityValidationException exception)
{
throw new ValidationException(exception);
}
catch
{
throw;
}
}
SaveChanges Method:
public int SaveChanges(bool auditing)
{
var entriesAdded = new List<DbEntityEntry>();
// Salva o log dos itens alterados e excluídos.
if (auditing && base.ChangeTracker.HasChanges())
foreach (var entry in base.ChangeTracker.Entries())
switch (entry.State)
{
case EntityState.Added:
entriesAdded.Add(entry);
break;
case EntityState.Deleted:
this.saveEntryOperation(entry, EntityState.Deleted);
break;
case EntityState.Modified:
this.saveEntryOperation(entry, EntityState.Modified);
break;
}
// Realiza a persitência de dados
int count = base.SaveChanges();
// Salva o log dos itens adicionados.
if (auditing && entriesAdded.Count > 0)
foreach (var entry in entriesAdded)
this.saveEntryOperation(entry, EntityState.Added);
this.AuditionContext.SaveChanges();
return count;
}
Attach merely adds the entity into change tracking, but if you don't actually change it after that point, its state remains Unchanged. The fact that it was different from what's in the database already before it was attached is meaningless.
To signal that the entity needs to be updated, you need to set it to Modified:
db.Entry(entity).State = EntityState.Modified;
If the entity is not already attached, this will also have the effect of attaching it, so there's no need to call Attach as well.
[EDITED BY Ferreira]
This tip work fine. I made the changes as below.
if (ModelosToUpdate.Count > 0)
{
ModelosToUpdate.ForEach(x =>
{
context.ModelosFuncoes.Attach(x);
var entry = this.context.Entry<ModeloFuncao>(x);
entry.State = System.Data.Entity.EntityState.Modified
});
}
I have this method in my SurveyController class:
public ActionResult AddProperties(int id, int[] propertyids, int page = 1)
{
var survey = _uow.SurveyRepository.Find(id);
if (propertyids == null)
return GetPropertiesTable(survey, page);
var repo = _uow.PropertySurveyRepository;
propertyids.Select(propertyid => new PropertySurvey
{
//Setting the Property rather than the PropertyID
//prevents the error occurring later
//Property = _uow.PropertyRepository.Find(propertyid),
PropertyID = propertyid,
SurveyID = id
})
.ForEach(x => repo.InsertOrUpdate(x));
_uow.Save();
return GetPropertiesTable(survey, page);
}
The GetPropertiesTable redisplays Properties but PropertySurvey.Property is marked virtual and I have created the entity using the new operator, so a proxy to support lazy loading was never created and it is null when I access it. When we have access direct to the DbContext we can use the Create method to explicitly create the proxy. But I have a unit of work and repository pattern here. I guess I could expose the context.Create method via a repository.Create method and then I need to remember to use that instead of the new operator when I add an entity . But wouldn't it be better to encapsulate the problem in my InsertOrUpdate method? Is there some way to detect that the entity being added is not a proxy when it should be and substitute a proxy? This is my InsertOrUpdate method in my base repository class:
protected virtual void InsertOrUpdate(T e, int id)
{
if (id == default(int))
{
// New entity
context.Set<T>().Add(e);
}
else
{
// Existing entity
context.Entry(e).State = EntityState.Modified;
}
}
Based on the answer supplied by qujck. Here is how you can do it without having to employ automapper:
Edited to always check for proxy - not just during insert - as suggested in comments
Edited again to use a different way of checking whether a proxy was passed in to the method. The reason for changing the technique is that I ran into a problem when I introduced an entity that inherited from another. In that case an inherited entity can fail the entity.e.GetType().Equals(instance.GetType() check even if it is a proxy. I got the new technique from this answer
public virtual T InsertOrUpdate(T e)
{
DbSet<T> dbSet = Context.Set<T>();
DbEntityEntry<T> entry;
if (e.GetType().BaseType != null
&& e.GetType().Namespace == "System.Data.Entity.DynamicProxies")
{
//The entity being added is already a proxy type that supports lazy
//loading - just get the context entry
entry = Context.Entry(e);
}
else
{
//The entity being added has been created using the "new" operator.
//Generate a proxy type to support lazy loading and attach it
T instance = dbSet.Create();
instance.ID = e.ID;
entry = Context.Entry(instance);
dbSet.Attach(instance);
//and set it's values to those of the entity
entry.CurrentValues.SetValues(e);
e = instance;
}
entry.State = e.ID == default(int) ?
EntityState.Added :
EntityState.Modified;
return e;
}
public abstract class ModelBase
{
public int ID { get; set; }
}
I agree with you that this should be handled in one place and the best place to catch all looks to be your repository. You can compare the type of T with an instance created by the context and use something like Automapper to quickly transfer all of the values if the types do not match.
private bool mapCreated = false;
protected virtual void InsertOrUpdate(T e, int id)
{
T instance = context.Set<T>().Create();
if (e.GetType().Equals(instance.GetType()))
instance = e;
else
{
//this bit should really be managed somewhere else
if (!mapCreated)
{
Mapper.CreateMap(e.GetType(), instance.GetType());
mapCreated = true;
}
instance = Mapper.Map(e, instance);
}
if (id == default(int))
context.Set<T>().Add(instance);
else
context.Entry(instance).State = EntityState.Modified;
}
Say I have a class Person that has an "IsArchived" property. A Person can have a Collection - each of which can also be Archived.
Right now I have an UpdatePerson method in my repository that looks like this:
public Person UpdatePerson(Person person)
{
_db.Persons.Attach(person);
var entry = _db.Entry(person);
entry.Property(e => e.Name).IsModified = true;
entry.Property(e => e.Friends).IsModified = true;
SaveChanges();
return entry.Entity;
}
And I have a separate Repository method for Archive:
public void ArchivePerson(int personId)
{
var person = _db.Persons.FirstOrDefault(c => c.PersonId == personId);
if (person != null)
{
person.IsArchived = true;
foreach (var friend in person.Friends)
{
ArchivePerson(friend.PersonId);
}
SaveChanges();
}
}
This means that from my WebAPI, I have to dedicate a function to Archiving/Unarchiving. It would be cool if I could just call Update and set the Archived for the given entity, and apply this to all child entities where possible. Is there any way to accomplish this, or am I best off sticking to the ArchivePerson repository method?
I am implementing a REST style API that allows an object to be PATCH'ed. The intention of the PATCH operation is to allow one or more properties in a class to be updated without touching an of the other properties that may be set.
The are examples of partial updates on the ServiceStack OrmLite page, but it seems to need hardcoding to indicate which fields will be partially updated. In my scenario it is upto the depend application to decide which fields to send.
I also have to cope with the scenario that the object may not have been persisted yet.
To get around this I have implemented the following:
public object Patch(Myclass request)
{
HttpStatusCode SuccessCode;
try
{
var result = (MyClass)Get(request);
if (result != null)
{
request.PopulateWithNonDefaultValues(result);
dbFactory.Run(dbCmd => dbCmd.UpdateNonDefaults(request, r => r.myId == request.myId));
}
else
{
dbFactory.Run(dbCmd => dbCmd.Save(request));
}
SuccessCode = HttpStatusCode.Accepted;
}
catch (Exception e)
{
log.Error(e);
SuccessCode = HttpStatusCode.InternalServerError;
}
return new HttpResult()
{
StatusCode = SuccessCode
};
}
It works, but something doesn't feel right, I'm sure there must be a better way?
That looks ok although you're code will be a lot shorter if you just throw let it throw C# Exceptions when there's an error and if you're inheriting from ServiceStack's New API base Service class you can use the already available Db property, e.g:
public object Patch(Myclass request)
{
var result = (MyClass)Get(request);
if (result != null)
{
request.PopulateWithNonDefaultValues(result);
Db.UpdateNonDefaults(request, r => r.myId == request.myId);
}
else
{
Db.Save(request);
}
return new HttpResult
{
StatusCode = HttpStatusCode.Accepted
};
}