God day!
I have a tree of entities and at specific point of time i need to update only scalar properties of one entity. Classic update rise entire graph lookup, but relations not need to update.
The trouble in Category entity what one category have another categories in children. My method generate exceptions when saving changes about duplicate key. I think EF try to add children to database.
Static method of my data context listed below:
public static void Update<T>(T item) where T : KeyedObject
{
if (item == null)
throw new ArgumentNullException("Item to update is null");
item.ValidateIsNotNew();
using (DataContext db = new DataContext())
{
T original = GetOriginalWithException<T>(db, item);
DbEntityEntry entry = db.Entry(original);
entry.CurrentValues.SetValues(item);
entry.State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (Exception ex)
{
throw new DatabaseException(
"Cant update list item. See inner exception for details.",
ex);
}
}
}
I tries another method: attaching object. This method does not throw exception, but it rise entire graph update and take many resources. Code listed below:
public static void Update<T>(T item) where T : KeyedObject
{
if (item == null)
throw new ArgumentNullException("Item to update is null");
item.ValidateIsNotNew();
using (DataContext db = new DataContext())
{
db.Set<T>().Attach(item);
db.Entry(item).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (Exception ex)
{
throw new DatabaseException(
"Cant update list item. See inner exception for details.",
ex);
}
}
}
Related
Using Spring Data MongoDB with MongoRepository. I have this bean
#Bean
public Jackson2RepositoryPopulatorFactoryBean repositoryPopulator() {
Jackson2RepositoryPopulatorFactoryBean factory = new Jackson2RepositoryPopulatorFactoryBean();
try {
factory.setResources(resourceResolver.getResources("classpath:static/collections/*.json"));
} catch (IOException e) {
log.error("Could not load data", e);
}
return factory;
}
which just works fine with fongo (db is dropped at the end of a test run) but not with real mongo. If I leave the bean as it is and I switch to real mongo instance, then I get my data base populated but only the first run, if I re-run the project (+tests) then it fails because it's already populated (getting DuplicateKeyException).
How do I populate only on the case the repositories are empty?
Consider using data migration tools like Mongobee. This is basically Liquibase/Flyway for MongoDB.
#Bean
public Jackson2RepositoryPopulatorFactoryBean repositoryPopulator() throws Exception {
Jackson2RepositoryPopulatorFactoryBean factory = new Jackson2RepositoryPopulatorFactoryBean();
try {
Resource[] resources = resourceResolver.getResources("classpath:static/collections/*.json");
//resources to list so I can add only the necessary resources
List<Resource> resourcesToFill = new ArrayList<>();
for (Resource r : resources) {
String collection = r.getFilename().substring(0, r.getFilename().length() - 5);
if (!mongoTemplate().collectionExists(collection))
resourcesToFill.add(r);
}
//back to Array...
resources = new Resource[resourcesToFill.size()];
for(int i=0; i<resources.length; i++)
resources[i] = resourcesToFill.get(i);
factory.setResources(resources); // <-- the reason of this shitty code, why the hell use Array?
} catch (IOException e) {
log.error("Could not load data", e);
}
return factory;
}
Have a list of data need to be saved. Before the save had to delete the existing data and save the new data.
If any of the delete & save is failed that transaction need to roll back, rest of the delete & save transaction should continue.
public LabResResponse saveLabResult(List<LabResInvstResultDto> invstResults) {
LabResResponse labResResponse = new LabResResponse();
List<Long> relInvstid = new ArrayList<Long>();
try{
if(invstResults != null){
List<LabResInvstResult> labResInvstResults = mapper.mapAsList(invstResults, LabResInvstResult.class);
for(LabResInvstResult dto: labResInvstResults){
if(dto != null){
//delete all child records before save.
deleteResult(dto, relInvstid);
}
}
}
labResResponse.setRelInvstids(relInvstid);
}catch(Exception e){
e.printStackTrace();
}
return labResResponse;
}
Here new transaction will added for each delete & save
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = { Exception.class })
private void deleteResult(LabResInvstResult dto, List<Long> relInvstid) {
try{
labResultRepo.deleteById(dto.getId());
LabResInvstResult result = labResultRepo.save(dto);
}catch(Exception e){
e.printStackTrace();
}
}
On delete it throws an exception "Caused by: javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call"
I can solve this by adding a #Transactional for public LabResResponse saveLabResult(List invstResults) method.
But my intial usecase will not work this will roll back entire list of transaction.
Here are two problems.
The first problem is that you call the "real" deleteResult method of the class. When Spring sees #Transactional it creates a proxy object with transactional behavior. Unless you're using AspectJ it won't change the class itself but create a new one, proxy. So when you autowire this bean you will be able use proxy's method that runs transaction related logic. But in your case you're referencing to the method of the class, not proxy.
The second problem is that Spring (again if AspectJ is not used) can't proxy non-public methods.
Summary: make the deleteResult method public somehow and use proxied one. As a suggestion, use another component with deleteResult there.
You are catching exception out of for loop, while your requirement says you want to continue the loop for other objects in list.
Put your try/catch block with-in loop. It should work fine
public LabResResponse saveLabResult(List<LabResInvstResultDto> invstResults) {
LabResResponse labResResponse = new LabResResponse();
List<Long> relInvstid = new ArrayList<Long>();
try{
if(invstResults != null){
List<LabResInvstResult> labResInvstResults = mapper.mapAsList(invstResults, LabResInvstResult.class);
for(LabResInvstResult dto: labResInvstResults){
if(dto != null){
//delete all child records before save.
try {
deleteResult(dto, relInvstid);
} catch(Exception e){
e.printStackTrace();
}
}
}
}
labResResponse.setRelInvstids(relInvstid);
}catch(Exception e){
e.printStackTrace();
}
return labResResponse;
}
I have an app that reads a lot of data into memory and processes it in a batches.
What I want is for entity framework to ignore DbUpdateConcurrencyException when deleting an entity that has already been deleted.
The reason is that by the time an entity has been processed and marked for deletion, it may already have been deleted from the DB.
Obliviously deleting a row that has already been deleted isn't a problem and shouldn't cause an error, I just need a way to tell entity framework that :)
Example
Db.Entry(itemToRemove).State = EntityState.Deleted;
Db.SaveChanges();
Causes an error if itemToRemove has already been deleted.
Note: Db.Configuration.ValidateOnSaveEnabled = false; doesn't fix this as another thread suggested.
How about?
Db.Entry(itemToRemove).State = EntityState.Deleted;
bool saveFailed;
do
{
saveFailed = false;
try
{
Db.SaveChanges();
}
catch(DbUpdateConcurrencyException ex)
{
saveFailed = true;
var entry = ex.Entries.Single();
//The MSDN examples use Single so I think there will be only one
//but if you prefer - do it for all entries
//foreach(var entry in ex.Entries)
//{
if(entry.State == EntityState.Deleted)
//When EF deletes an item its state is set to Detached
//http://msdn.microsoft.com/en-us/data/jj592676.aspx
entry.State = EntityState.Detached;
else
entry.OriginalValues.SetValues(entry.GetDatabaseValues());
//throw; //You may prefer not to resolve when updating
//}
}
} while (saveFailed);
More here:
Resolving optimistic concurrency exceptions
I posted this question a long time ago but it has recently had some attention so I though I would add the solution I actually use.
//retry up to 5 times
for (var retries = 0; retries < 5; retries++)
{
try
{
Db.SaveChanges();
break;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entity in ex.Entries)
{
entity.State = EntityState.Detached;
}
}
}
Things I considered - I did NOT want to use ReloadAsync() or ObjectContext.Refresh as I wanted to ignore items deleted in another process WITHOUT any additional database overhead.
I added in the for loop as a simple protection against infinite loops - not something that should be able to happen, but I'm a belt and braces approach man and not a fan of while(true) if it can be avoided.
No need to a local variable like isDone or saveFailed - simply break if we saved successfully.
No need to cast ex.Entries to a list in order to enumerate it - just because you can write something on one line doesn't make it better.
You could handle the DbUpdateConcurrencyException and then call Refresh(RefreshMode,IEnumerable) with RefreshMode.StoreWins and your deleted entities as parameter.
try{
Db.Entry(itemToRemove).State = EntityState.Deleted;
Db.SaveChanges();
}
catch(DbUpdateConcurrencyException)
{
IObjectContextAdapter adapter = Db;
adapter.ObjectContext.Refresh(RefreshMode.StoreWins, context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Deleted));
Db.SaveChanges();
}
Based on the code from https://msdn.microsoft.com/en-US/data/jj592904 but where I added an infite loop counter (just in case, you never know, right?) and looping through all the entries in the exception's list.
var maxTriesCounter = 20;
bool saveFailed;
do
{
saveFailed = false;
maxTriesCounter--;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
foreach (var entry in ex.Entries)
{
entry.Reload();
}
}
} while (saveFailed && maxTriesCounter > 0);
Here is what I use. Detach all problem records after the save.
Db.Entry(itemToRemove).State = EntityState.Deleted;
while(true)
try {
Db.SaveChanges();
break;
} catch (DbUpdateConcurrencyException ex) {
ex.Entries.ToList().ForEach(x=>x.State=EntityState.Detached);
}
Or you could add a custom SaveChanges function to your DbContext class and use it instead whenever you need to ignore those errors.
public int SaveChanges_IgnoreConcurrencyExceptions () {
while(true)
try {
return this.SaveChanges();
} catch (DbUpdateConcurrencyException ex) {
ex.Entries.ToList().ForEach(x => x.State=EntityState.Detached);
}
}
This is my approach:
public async Task DeleteItem(int id)
{
bool isDone = false;
while (!isDone)
{
var item= await dbContext.Items.AsNoTracking().SingleOrDefaultAsync(x=> x.id== id);
if (item== null)
return;
dbContext.Items.Delete(item);
try
{
await dbContext.CommitAsync();
return;
}
catch (DbUpdateConcurrencyException ex)
{
}
}
}
This is another approach:
context.Delete(item);
bool saveFailed;
do
{
saveFailed = false;
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
var entity = ex.Entries.Single();
await entity.Single().ReloadAsync();
if (entity.State == EntityState.Unchanged)// entity is already updated
context.Delete(item);;
else if (entity.State == EntityState.Detached) // entity is already deleted
saveFailed =false;
}
} while (saveFailed);
ReloadAsync() method as of Microsoft docs :
Reloads the entity from the database overwriting any property values
with values from the database.
The entity will be in the Unchanged state after calling this method,
unless the entity does not exist in the database, in which case the
entity will be Detached. Finally, calling Reload on an Added entity
that does not exist in the database is a no-op. Note, however, that an
Added entity may not yet have had its permanent key value created.
I'm trying to get all emails (to, from, cc) from an email in a list and go through the list and check the contacts, if the Contact exists in CRM then a field on the email entity will be marked as true. When I check the to, from, and cc fields of the email it returns 0 parties, but there is no error there. Also at the end, when I'm calling service.Update(entity), it returns an error. An unexpected error occurred.
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider
.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider
.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = factory
.CreateOrganizationService(context.UserId);
try
{
Email entity;
if (context.MessageName == "Create")
{
if (context.PostEntityImages.Contains("PostImage")
&& context.PostEntityImages["PostImage"] is Entity)
entity = (Email)context.PostEntityImages["PostImage"].ToEntity<Email>();
else
throw new Exception("No PostEntityImages...");
}
else
throw new Exception("EmailPortalVisibilityPlugin Plugin invalid");
if(entity.LogicalName != "email")
throw new Exception("EmailPortalVisibilityPlugin invalid");
bool contactExists = false;
List<string> emails = new List<string>();
emails.AddRange(ParseAddressUsed(entity.To, trace));
emails.AddRange(ParseAddressUsed(entity.From, trace));
emails.AddRange(ParseAddressUsed(entity.Cc, trace));
foreach (String em in emails)
{
contactExists = LookupContact(em, service, trace);
if (contactExists)
break;
}
UpdateToggleState(entity, contactExists, service, trace);
}
catch (Exception ex)
{
throw new InvalidPluginExecutionException("Execute '" + ex.Message + "'");
}
}
public List<string> ParseAddressUsed(
IEnumerable<ActivityParty> entity, ITracingService trace)
{
try
{
List<string> addressStrings = new List<string>();
foreach (ActivityParty party in entity)
addressStrings.Add(party.PartyId.Id.ToString());
return addressStrings;
}
catch (FaultException<OrganizationServiceFault> exceptionServiceCall)
{
throw new Exception("ParseAddressUsed FaultException");
}
catch (Exception ex)
{
throw new Exception("ParseAddressUsed Exception");
}
}
public bool LookupContact(
String emailAddress, IOrganizationService service, ITracingService trace)
{
try
{
QueryByAttribute queryByAttribute = new QueryByAttribute("contact");
queryByAttribute.ColumnSet = new ColumnSet("contactId");
queryByAttribute.Attributes.Add("emailaddress1");
queryByAttribute.Values.Add(emailAddress);
EntityCollection retrieved = service.RetrieveMultiple(queryByAttribute);
return (retrieved.Entities.Count > 0);
}
catch (FaultException<OrganizationServiceFault> exceptionServiceCall)
{
throw new Exception("LookupContact Exception");
}
catch (Exception ex)
{
throw new Exception("LookupContact Exception");
}
}
public void UpdateToggleState(
Email entity, bool toggleState, IOrganizationService service, ITracingService trace)
{
try
{
Entity email = new Entity("email");
email.Id = entity.Id;
email.Attributes.Add("new_clientfacing", toggleState);
service.Update(email);
}
catch (FaultException<OrganizationServiceFault> exceptionServiceCall)
{
throw new Exception("UpdateToggleState Exception");
}
catch (Exception ex)
{
throw new Exception("UpdateToggleState Exception");
}
}
Try to set the first argument type of function ParseAddressUsed to EntityCollection instead of IEnumerable<ActivityParty>, and do the necessary changes.
And for the final update in function UpdateToggleState, there is no need to create a new email Entity (Entity email = new Entity("email");), when you already have the entity variable. You could just set the new_clientfacing attribute and update the entity, which is already retrieved.
In your method ParseAddressUsed you are adding the PartyId GUID to the string list and you use it in LookupContact in the emailaddress1 filter as a parameters, that is probably the reason why you are not retrieving any records.
Please try to change addressStrings.Add(party.PartyId.Id.ToString()) to addressStrings.Add(party.AddressUsed) instead and see if that works.
Cheers, dimamura
I am developing an MVC app.
When I try to updated record it showing error of DBEntityValidation exception,
( beacuse its trying to add record in DB. This is my code)
public JsonResult SavePassword(int EmpId, string Password)
{
try
{
Employee e1 = db.Employees.First(i => i.Id == EmpId);
db.Entry(e1).State = EntityState.Modified;
e1.Password = Password;
db.SaveChanges();
return Json(EmpId);
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"", ve.PropertyName, ve.ErrorMessage);
}
}
throw;
}
}
In exception, it shows that validation msgs, which I have checked while adding new record.
So , I think its trying to add in DB insted of updating.