Asp.net mvc entity error when use of .SaveChanges() - c#

I wrote a function to handle views in mysql with entity data model. The column in the database is an int(10), but when I execute this block of code it gives me this error:
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
If I set the value to 5 manually and set hardcode to 5 into the code block, it doesn't fail on savechanges.
public static void AddPortfolioView(int id)
{
using (var db = new EyeMazingEnt())
{
var portfolio = (from a in db.portfolios where a.id.Equals(id) select a).SingleOrDefault();
if (portfolio != null)
{
portfolio.views = 0;
db.SaveChanges();
}
}
}

Related

EF5 can not handle Concurrency when Updating selective fields

I am using EF5 and Data First approach to Update entities.
I am using approach suggested by other questions to conditionally update only modified properties in the Entities.
Oki so here's the scenario My controller call Service with POCO objects and gets POCO objects from Service, The Service layer talks with Data layer which internally uses EF5 to retrieve entity from DB and Update them in DB.
The View data is loaded by controller from DTO object retrieved from Service layer.
User makes changes to View and Posts back JSON data to controller which gets mapped to DTO object in controller (courtesy MVC).
The controller makes call to Service layer with the DTO object (POCO) object.
The Service maps the POCO object to EF entity object and calls the Data layer's(i.e Repository) Update method passing in the EF entity.
In the Repository I fetch the existing entity from DB and call ApplyCurrentvaluesValues method, then I check if any properties are modified .
If properties are modified then I apply my custom logic to other entities which are not related to current entity and also Update the "UpdatedAdminId" & "UpdationDate" of current entity.
Post this I call "SaveChanges" method on Centext.
Every thing above I mentioned is working fine , except if I insert a break point in "SaveChanges" call and update some field modified by User to different value then "DbUpdateConcurrencyException" is not thrown by EF5.
i.e. I can get conditional Update & fire my custom logic when properties of my interest are modified to work perfectly.
But I am not getting error in case of the concurrency i.e the EF is not raising "DbUpdateConcurrencyException" in case a record is updated in between me fetching the record from DB , updating the record and saving it.
In real scenario there is a offline cron running which checks for newly created campaign and creates portfolio for them and marks the IsPortfolioCreated property below as true, in the mean time user can edit the campaign and the flag can be set to false even though the cron has created the portfolios.
To replicate the concurrency scenario I put a break point on SaveChanges and then Update the IsPortfolioCreated feild from MS-Sql enterprise manager for the same entity, but the "DbUpdateConcurrencyException" is not thrown even though the Data in Store has been updated.
Here's my code for reference,
Public bool EditGeneralSettings(CampaignDefinition campaignDefinition)
{
var success = false;
//campaignDefinition.UpdatedAdminId is updated in controller by retreiving it from RquestContext, so no its not comgin from client
var updatedAdminId = campaignDefinition.UpdatedAdminId;
var updationDate = DateTime.UtcNow;
CmsContext context = null;
GlobalMasterContext globalMasterContext = null;
try
{
context = new CmsContext(SaveTimeout);
var contextCampaign = context.CampaignDefinitions.Where(x => x.CampaignId == campaignDefinition.CampaignId).First();
//Always use this fields from Server, no matter what comes from client
campaignDefinition.CreationDate = contextCampaign.CreationDate;
campaignDefinition.UpdatedAdminId = contextCampaign.UpdatedAdminId;
campaignDefinition.UpdationDate = contextCampaign.UpdationDate;
campaignDefinition.AdminId = contextCampaign.AdminId;
campaignDefinition.AutoDecision = contextCampaign.AutoDecision;
campaignDefinition.CampaignCode = contextCampaign.CampaignCode;
campaignDefinition.IsPortfolioCreated = contextCampaign.IsPortfolioCreated;
var campaignNameChanged = contextCampaign.CampaignName != campaignDefinition.CampaignName;
// Will be used in the below if condition....
var originalSkeForwardingDomain = contextCampaign.skeForwardingDomain.ToLower();
var originalMgForwardingDomain = contextCampaign.mgForwardingDomain.ToLower();
//This also not firing concurreny exception....
var key = ((IObjectContextAdapter) context).ObjectContext.CreateEntityKey("CampaignDefinitions", campaignDefinition);
((IObjectContextAdapter)context).ObjectContext.AttachTo("CampaignDefinitions", contextCampaign);
var updated = ((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName, campaignDefinition);
ObjectStateEntry entry = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(updated);
var modifiedProperties = entry.GetModifiedProperties();
//Even tried this , works fine but no Concurrency exception
//var entry = context.Entry(contextCampaign);
//entry.CurrentValues.SetValues(campaignDefinition);
//var modifiedProperties = entry.CurrentValues.PropertyNames.Where(propertyName => entry.Property(propertyName).IsModified).ToList();
// If any fields modified then only set Updation fields
if (modifiedProperties.Count() > 0)
{
campaignDefinition.UpdatedAdminId = updatedAdminId;
campaignDefinition.UpdationDate = updationDate;
//entry.CurrentValues.SetValues(campaignDefinition);
updated = ((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName, campaignDefinition);
//Also perform some custom logic in other entities... Then call save changes
context.SaveChanges();
//If campaign name changed call a SP in different DB..
if (campaignNameChanged)
{
globalMasterContext = new GlobalMasterContext(SaveTimeout);
globalMasterContext.Rename_CMS_Campaign(campaignDefinition.CampaignId, updatedAdminId);
globalMasterContext.SaveChanges();
}
}
success = true;
}
catch (DbUpdateConcurrencyException ex)
{
//Code never enters here, if it does then I am planning to show the user the values from DB and ask him to retry
//In short Store Wins Strategy
//Code in this block is not complete so dont Stackies don't start commenting about this section and plague the question...
// Get the current entity values and the values in the database
var entry = ex.Entries.Single();
var currentValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
// Choose an initial set of resolved values. In this case we
// make the default be the values currently in the database.
var resolvedValues = databaseValues.Clone();
// Update the original values with the database values and
// the current values with whatever the user choose.
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValues);
}
catch (Exception ex)
{
if (ex.InnerException != null)
throw ex.InnerException;
throw;
}
finally
{
if (context != null) context.Dispose();
if (globalMasterContext != null) globalMasterContext.Dispose();
}
return success;
}
Entity framework it's not doing anything special about concurrency until you (as developer) configure it to check for concurrency problems.
You are trying to catch DbUpdateConcurrencyException, the documentation for this exception says: "Exception thrown by DbContext when it was expected that SaveChanges for an entity would result in a database update but in fact no rows in the database were affected. ", you can read it here
In a database first approach, you have to set the property 'Concurrency Mode' for column on 'Fixed' (the default is None). Look at this screenshot:
The column Version is a SQL SERVER TIMESTAMP type, a special type that is automatically updated every time the row changes, read about it here.
With this configuration, you can try with this simple test if all is working as expected:
try
{
using (var outerContext = new testEntities())
{
var outerCust1 = outerContext.Customer.FirstOrDefault(x => x.Id == 1);
outerCust1.Description += "modified by outer context";
using (var innerContext = new testEntities())
{
var innerCust1 = innerContext.Customer.FirstOrDefault(x => x.Id == 1);
innerCust1.Description += "modified by inner context";
innerContext.SaveChanges();
}
outerContext.SaveChanges();
}
}
catch (DbUpdateConcurrencyException ext)
{
Console.WriteLine(ext.Message);
}
In the example above the update from the inner context will be committed, the update from the outer context will thrown a DbUpdateConcurrencyException, because EF will try to update the entity using 2 columns as a filters: the Id AND the Version column.
Hope this helps!

EF6 InvalidOperationException on Update, but works after manual DB Change

I am trying to update an entity's FK. I am doing it like this:
myEntity.FK_ID= Guid.Parse("aff4b5d9-e197-4f0d-85ff-b04464d9e4f0"); //Entity with this Guid exists inside the DB
myEntity.ForeignKeyProperty= null;
Then I try to update the entity (new context) like this:
using (MyContext db = new MyContext ())
{
//db.Benutzer.Attach(entity); // <-- Same Exception
db.Entry(entity).State = EntityState.Modified;
}
This results in a InvalidOperationException:
A referential integrity constraint violation occurred: The property value(s) of 'MyTable.MyTable_ID' on one end of a relationship do not match the property value(s) of 'MyEntity.FK_ID' on the other end.
I really don't know why. I then tried the following:
I changed the value of FK_ID to the target value inside the Database. This worked (okay, EF chaching some stuff maybe...)
But then I rechanged the value to something else and it STILL works.
After the manual DB change it now works for this one entity EVERYTIME. If I try to update another entity I get the same Exception. Any ideas on why this is happening?!
Regards,
Marcel
EDIT:
Here's a more complete Code:
//In MVC-Controller
Benutzer dbBenutzer = Database.GetBenutzerByID(model.ID_Benutzer);
// ... Do some other changes to the entity
dbBenutzer.ChangeMitarbeiter = null;
dbBenutzer.MitarbeiterChange_ID = CurrentUser.Mitarbeiter_ID;
//...SaveChanges via
Database.Update<Benutzer>(dbBenutzer);
// In Repository
public static int Update<E>(E entity) where E : class
{
using (Context db = new MMVContext())
{
try
{
db.Entry(entity).State = EntityState.Modified; //EXCEPTION
return SaveRepositoryChanges<E>(db);
}
catch (Exception e)
{
Logging.Error(e.ToExtString());
return -1;
}
}
}

Attaching an entity of type failed because another entity of the same type already has the same primary key value

Error message: Attaching an entity of type failed because another entity of the same type already has the same primary key value.
Question: How do I attached an entity in a similar fashion as demonstrated in the AttachActivity method in the code below?
I have to assume the "another entity" part of the error message above refers to an object that exists in memory but is out of scope (??). I note this because the Local property of the DBSet for the entity type I am trying to attach returns zero.
I am reasonably confident the entities do not exist in the context because I step through the code and watch the context as it is created. The entities are added in the few lines immediately following creation of the dbcontext.
Am testing for attached entities as specified here:what is the most reasonable way to find out if entity is attached to dbContext or not?
When looking at locals in the locals window of visual studio I see no entities of type Activity (regardless of ID) except the one I am trying to attach.
The code executes in this order: Try -> ModifyProject -> AttachActivity
Code fails in the AttachActivity at the commented line.
Note the code between the debug comments which will throw if any entities have been added to the context.
private string AttachActivity(Activity activity)
{
string errorMsg = ValidateActivity(activity); // has no code yet. No. It does not query db.
if(String.IsNullOrEmpty(errorMsg))
{
// debug
var state = db.Entry(activity).State; // Detached
int activityCount = db.Activities.Local.Count;
int projectCount = db.Activities.Local.Count;
if (activityCount > 0 || projectCount > 0)
throw new Exception("objects exist in dbcontext");
// end debug
if (activity.ID == 0)
db.Activities.Add(activity);
else
{
db.Activities.Attach(activity); // throws here
db.Entry(activity).State = System.Data.Entity.EntityState.Modified;
}
}
return errorMsg;
}
public int ModifyProject(Presentation.PresProject presProject, out int id, out string errorMsg)
{
// snip
foreach (PresActivity presActivity in presProject.Activities)
{
Activity a = presActivity.ToActivity(); // returns new Activity object
errorMsg = ValidateActivity(a); // has no code yet. No. It does not query db.
if (String.IsNullOrEmpty(errorMsg))
{
a.Project = project;
project.Activities.Add(a);
AttachActivity(a);
}
else
break;
}
if (string.IsNullOrEmpty(errorMsg))
{
if (project.ID == 0)
db.Projects.Add(project);
else
db.AttachAsModfied(project);
saveCount = db.SaveChanges();
id = project.ID;
}
return saveCount;
}
This is the class that news up the dbContext:
public void Try(Action<IServices> work)
{
using(IServices client = GetClient()) // dbContext is newd up here
{
try
{
work(client); // ModifyProject is called here
HangUp(client, false);
}
catch (CommunicationException e)
{
HangUp(client, true);
}
catch (TimeoutException e)
{
HangUp(client, true);
}
catch (Exception e)
{
HangUp(client, true);
throw;
}
}
I am not asking: How do I use AsNoTracking What difference does .AsNoTracking() make?
One solution to avoid receiving this error is using Find method. before attaching entity, query DbContext for desired entity, if entity exists in memory you get local entity otherwise entity will be retrieved from database.
private void AttachActivity(Activity activity)
{
var activityInDb = db.Activities.Find(activity.Id);
// Activity does not exist in database and it's new one
if(activityInDb == null)
{
db.Activities.Add(activity);
return;
}
// Activity already exist in database and modify it
db.Entry(activityInDb).CurrentValues.SetValues(activity);
db.Entry(activityInDb ).State = EntityState.Modified;
}
Attaching an entity of type failed because another entity of the same type already has the same primary key value. This can happen when using the Attach method or setting the state of an entity to Unchanged or Modified if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the Add.
The solution is that
if you had to use GetAll()
public virtual IEnumerable<T> GetAll()
{
return dbSet.ToList();
}
Change To
public virtual IEnumerable<T> GetAll()
{
return dbSet.AsNoTracking().ToList();
}
I resolved this error by changing Update method like below.
if you are using generic repository and Entity
_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);
or normal(non-generic) repository and entity , then
_dbContext.Set<TaskEntity>().AddOrUpdate(entityToBeUpdatedWithId);
If you use AddOrUpdate() method, please make sure you have added
System.Data.Entity.Migrations namespace.

Update database via Linq to SQL not working

I'm developing a C# ASP.NET application, in which i'm retrieving some data from the database, throwing in a form, and when i click on Save, i want it to save my changes in the database.
I'm using Linq to SQL. The code below, at the end, call the method ClienteBusiness.SalvarAlteracoes(cliente), which by the way, only calls the ClienteData.SalvarAlteracoes(cliente) method.
protected void Salvar()
{
TB_CLIENTE_CLI cliente = new TB_CLIENTE_CLI();
int idEstado = 0;
int idCidade = 0;
if (!Int32.TryParse(ddlEstado.SelectedValue, out idEstado))
{
return;
}
if (!Int32.TryParse(Request.Form[ddlCidade.UniqueID], out idCidade))
{
return;
}
cliente.TXT_RAZAOSOCIAL_CLI = txtRazaoSocial.Text;
cliente.TXT_NOMEFANTASIA_CLI = txtNomeFantasia.Text;
cliente.TXT_CNPJ_CLI = txtCNPJ.Text;
cliente.TXT_CEP_CLI = txtCEP.Text;
/*e os demais campos*/
//Se a tela for de edição, altera o valor do ID para o cliente correspondente.
cliente.ID_CLIENTE_CLI = this.IdCliente;
ClienteBusiness.SalvarAlteracoes(cliente);
HTMLHelper.jsAlertAndRedirect(this, "Salvo com sucesso!", ResolveUrl("~/Pages/ClientePage.aspx"));
}
The method which save the changes is described below:
public static Int32 SalvarAlteracoes(TB_CLIENTE_CLI cliente)
{
using (PlanoTesteDataContext context = DataContext.ObterConexao())
{
if (cliente.ID_CLIENTE_CLI == 0)
{
context.TB_CLIENTE_CLIs.InsertOnSubmit(cliente);
}
else
{
context.TB_CLIENTE_CLIs.Attach(cliente, true);
}
context.SubmitChanges();
} return cliente.ID_CLIENTE_CLI;
}
On the line context.TB_CLIENTE_CLIs.Attach(cliente, true); i'm receiving a System.InvalidOperationException: An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy.
I've already checked the UpdateChecks and they are set to Never.
What can I do? Thanks and sorry for the bad english.
This should work:
else
{
context.Refresh(System.Data.Linq.RefreshMode.KeepCurrentValues, cliente);
context.TB_CLIENTE_CLIs.Attach(cliente);
}
This Refresh overload will keep the changes made by the user,it compares the modified entity with the original values from the database, detects the difference and marks the entity as modified and the call to SubmitChanges applies the update to the database.
You may very well run into trouble using Linq2SQL with disconnected entities. EF is a more suited solution to handle this.
However, please ensure you have set all properties on the entity on UpdateCheck to NEVER. I have tested this myself and it works. If this does work it will run an UPDATE statement on every column regardless of whether it has been updated or not. Could cause a problem if you use triggers on your tables. It might be a better idea to use a Timestamp instead to track the entities so concurrency issues between multiple users can be raised.
If you try to Attach an entity from a context where the ObjectTrackingEnabled is not set to False then you will have the following exception thrown:
An unhandled exception of type 'System.NotSupportedException' occurred in System.Data.Linq.dll
Additional information: An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported.
As an example please use the following for retrieving and reattaching an entity:
public TB_CLIENTE_CLI Get(int id)
{
using (PlanoTesteDataContext ctx = new PlanoTesteDataContext())
{
ctx.ObjectTrackingEnabled = false;
return ctx.TB_CLIENTE_CLI.SingleOrDefault(n => n.ID == id);
}
}
public void Save(TB_CLIENTE_CLI cliente)
{
using (PlanoTesteDataContext ctx = new PlanoTesteDataContext())
{
ctx.DeferredLoadingEnabled = false;
ctx.TB_CLIENTE_CLI.Attach(cliente, true);
ctx.SubmitChanges();
}
}
You will also need to set DeferredLoadingEnabled loading to False in the Save method so that you can save down changes on the entity subsequent times after the first initial save on modification.

Enity Framework updating with a child table causing errors

I have the following update function
public void UpdateBatchDefinition(BatchDefinition batchToUpdate)
{
if (batchToUpdate == null)
{
throw new ArgumentNullException("batchToUpdate");
}
BatchDefinition foundDefinition =
this.context.BatchDefinitions.SingleOrDefault(definition => definition.Id == batchToUpdate.Id);
if (foundDefinition != null)
{
if (!string.IsNullOrWhiteSpace(batchToUpdate.Name))
{
foundDefinition.Name = batchToUpdate.Name;
}
if (!string.IsNullOrWhiteSpace(batchToUpdate.Description))
{
foundDefinition.Description = batchToUpdate.Description;
}
if (!string.IsNullOrWhiteSpace(batchToUpdate.LoadType))
{
foundDefinition.LoadType = batchToUpdate.LoadType;
}
if (batchToUpdate.JobId != Guid.Empty)
{
foundDefinition.JobId = batchToUpdate.JobId;
}
foundDefinition.Tables = batchToUpdate.Tables;
this.context.SaveChanges();
}
}
the issue I am having Is when I am trying to update the Tables list. Tables is a List of Table and Table is a Entity of another table
Tables could be added to, removed from or left alone. I need to update that with what ever is being passed in
when I run this right now I get an 'EntityValidationErrors' error, though it wont tell me what the validation issue actually is.
on Inserting I got the same error but was able to fix it using the following
var underlyingContext = this.context as DbContext;
if (underlyingContext != null)
{
foreach (var table in batchDefinition.Tables)
{
// Need to mark the table entity as unchanged or
// else EF will treat it as a new table
underlyingContext.Entry(table).State = EntityState.Unchanged;
}
}
so I tried using that in this update function
var underlyingContext = this.context as DbContext;
if (underlyingContext != null)
{
foreach (var table in foundDefinition.Tables)
{
// Need to mark the table entity as unchanged or
//else EF will treat it as a new table
underlyingContext.Entry(table).State = EntityState.Unchanged;
}
}
foundDefinition.Tables = batchToUpdate.Tables;
and I get the following error instead:
AcceptChanges cannot continue because the object's key values conflict
with another object in the ObjectStateManager. Make sure that the key
values are unique before calling AcceptChanges.
Any thoughts one what I am missing here?
Change end of your update method like this:
foreach (var t in foundDefinition.Tables.ToList())
Context.Tables.Remove(t);
foundDefinition.Tables = batchToUpdate.Tables;
this.context.SaveChanges();
And about your last error, it is said that there are some duplicates in your context. So, EF can't save the context changes into the db (because there are duplicates in the context!)
In fact, I don't know the last error is from add or delete - you didn't mention clearly. So, I don't know the last two code samples are from your add method, or your update method...
However for update, the trick I mentioned here, must solve your problem for update...

Categories