Entity Framework - I'm having trouble editing a complex Entity - entity-framework

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

Related

EF Core 3 Has Value Generator

in the modelBUilder for an entity, I am try to have the created and modified dates be set on add and updates by a custom generator. The reason for going this path is because the DbContext that creates the models is being used a base class. This base class is being inherited by SQL Server & SQLite EFCore extensions. Because of this there should be database explicit functionality in the context. The GetDateUTC() and triggers that were originally implemented SQL Server.
modelBuilder.Entity<CommunicationSendRequest>(entity =>
{
...
entity.Property(p => p.CreatedAt).ValueGeneratedOnAdd().HasValueGenerator<CreatedAtTimeGenerator>();
entity.Property(p => p.ModifiedAt).ValueGeneratedOnUpdate().HasValueGenerator<ModifiedAtTimeGenerator>();
});
but what is happening on add and updates both properties always set to new values. Meaning on brand new inserts the modifiedat is set, and on updates the createdat date is set. Which removes the true created at date.
The question is are those have value generators setup correctly? Is there a way to accomplish this using the generators? In the generators I tried to also check the state an return the value only if the state was added or modified. But the state always equaled Detached.
public class CreatedAtTimeGenerator : ValueGenerator<DateTimeOffset>
{
public override DateTimeOffset Next(EntityEntry entry)
{
if (entry == null)
{
throw new ArgumentNullException(nameof(entry));
}
return DateTimeOffset.UtcNow;
}
public override bool GeneratesTemporaryValues { get; }
}
public class ModifiedAtTimeGenerator : ValueGenerator<DateTimeOffset>
{
public override DateTimeOffset Next(EntityEntry entry)
{
if (entry == null)
{
throw new ArgumentNullException(nameof(entry));
}
return DateTimeOffset.UtcNow;
}
public override bool GeneratesTemporaryValues { get; }
}
What I actually did was to go away from the ValueGenerate concept all together and handle it the creation of CreatedAt, ModifiedAt, and added after this post was created DeletedAt dates by overriding the SaveChanges() methods.
public override int SaveChanges()
{
var selectedEntityList = ChangeTracker.Entries()
.Where(x => (x.Entity is IEntityCreatedAt ||
x.Entity is IEntityModifiedAt ||
x.Entity is IEntityIsDeleted) &&
(x.State == EntityState.Added || x.State == EntityState.Modified)).ToList();
selectedEntityList.ForEach(entity =>
{
if (entity.State == EntityState.Added)
{
if (entity.Entity is IEntityCreatedAt)
((IEntityCreatedAt)entity.Entity).CreatedAt = DateTimeOffset.UtcNow;
}
if (entity.State == EntityState.Modified)
{
if (entity.Entity is IEntityModifiedAt)
((IEntityModifiedAt)entity.Entity).ModifiedAt = DateTimeOffset.UtcNow;
if (entity.Entity is IEntityIsDeleted)
if (((IEntityIsDeleted)entity.Entity).IsDeleted)
((IEntityIsDeleted)entity.Entity).DeletedAt = DateTimeOffset.UtcNow;
}
});
return base.SaveChanges();
}

Entity Framework Core - Save Changes for only Certain DBSet

I'm trying to overwrite the SaveChanges method to only save changes for a particular entity.
So I want to call DB.SaveChanges<MyEntity>();
However, I don't want to lose all the previous changes that may have occurred on the given context.
I'm working on something like below, but my objects values (current) aren't being set back to before.
What needs to change?
public async Task<int> SaveChanges<T>() where T : class
{
var original = (from et in this.ChangeTracker.Entries()
where !typeof(T).IsAssignableFrom(et.Entity.GetType()) && et.State != EntityState.Unchanged
group et by new { et.State, et.CurrentValues } into grp
select new
{
key = grp.Key.State,
values = grp.Key.CurrentValues,
data = grp
}).ToList();
foreach (var entry in this.ChangeTracker.Entries().Where(x => !typeof(T).IsAssignableFrom(x.Entity.GetType())))
{
entry.State = EntityState.Unchanged;
}
var rows = await base.SaveChangesAsync();
foreach (var state in original)
{
foreach (var entry in state.data)
{
entry.State = state.key;
entry.CurrentValues.SetValues(state.values);
}
}
return rows;
}

Get original type from DbEntityType

We have overriden the SaveChanges method because we want to set some final properties automatically upon saving and we have to set SETCONTEXT in each connection. Our current override looks as follows:
public override int SaveChanges()
{
// Use basic SaveChanges if SessionInfo is not initialized
if (SessionInfo.ContextInfo == null)
{
return base.SaveChanges();
}
// SessionInfo was initialized, so use custom logic now
// Set the SqlId according to sessioninfo for each entity to add
foreach (DbEntityEntry entry in ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added))
{
string sqlIdPropertyName =
entry.CurrentValues.PropertyNames.First(x=>x.EndsWith("SqlId");
entry.Property(sqlIdPropertyName).CurrentValue = SessionInfo.ServerSqlId;
}
// Set the IsDeleted boolean to true for each entity to delete
foreach (DbEntityEntry entry in ChangeTracker.Entries()
.Where(x => x.State == EntityState.Deleted))
{
entry.Property("IsDeleted").CurrentValue = true;
entry.State = EntityState.Modified;
}
// Begin custom transaction if SessionInfo was set
this.Database.Connection.Open();
SessionInfo.SetContextInfo(this);
int result = base.SaveChanges();
this.Database.Connection.Close();
return result;
}
As you can see, when we add a new record to the database, the save logic sets the SqlId for the object according to the SessionInfo. However, this now depends of PropertyNames.First(), which is a risk.
The PropertyName of the SqlId we want to set is always equal to the name of the POCO class type + SqlId, so for the class "Invoice" it would be "InvoiceSqlId".
How can we get the typename of the original POCO class from a DbEntityEntry?
Try this: entry.Entity.GetType().Name
EDIT - for when you may be using proxies
var entityType = entry.Entity.GetType();
string name;
if (entityType.BaseType != null &&
entityType.Namespace == "System.Data.Entity.DynamicProxies")
{
name = entityType.BaseType.Name;
}
else
{
name = entityType.Name
}

Is this a good pattern for PATCH

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

How to implement and test complex business rules for POCO classes

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