EF Core Deleting Entities in Disconnected Environment - entity-framework-core

I'm having real difficulty with EF Core with a Web API project I'm working... to me EF Core is not intuitive at all. I'm in a disconnected environment and I'm trying to update Sudoku games. EF Core is spending more time deleting connections between users and their apps and roles than in updating the game. How do I disable delete statements in an update? There is no reason for deletes, I don't need them. How do I stop them?
The method is as follows, the game is loaded as a graph and my understanding is this code should change everything tracked to modified or added. To me it seems like EF Core is going out of it's way to delete things... this makes no sense. I never instructed it to delete anything:
async public Task<IRepositoryResponse> Update(TEntity entity)
{
var result = new RepositoryResponse();
try
{
dbSet.Update(entity);
context.ChangeTracker.TrackGraph(entity,
e => {
var dbEntry = (IEntityBase)e.Entry.Entity;
if (dbEntry.Id != 0)
{
e.Entry.State = EntityState.Modified;
}
else
{
e.Entry.State = EntityState.Added;
}
});
await context.SaveChangesAsync();
result.Success = true;
result.Object = entity;
return result;
}
catch (Exception exp)
{
result.Success = false;
result.Exception = exp;
return result;
}
}

Well, I found a work around but it is the equivalent to the fixing your bike with bubblegum and tape. It's ugly... but it works. Before I save the game I create a list of all associated apps and roles and then recreate and resave the values after await context.SaveChangesAsync();. The code is listed below:
async public Task<IRepositoryResponse> Update(TEntity entity)
{
var result = new RepositoryResponse();
try
{
entity.DateUpdated = DateTime.UtcNow;
context.Games.Update(entity);
context.ChangeTracker.TrackGraph(entity,
e => {
var dbEntry = (IEntityBase)e.Entry.Entity;
if (dbEntry.Id != 0)
{
e.Entry.State = EntityState.Modified;
}
else
{
e.Entry.State = EntityState.Added;
}
});
var apps = new List<App>();
var roles = new List<Role>();
foreach (var userApp in entity.User.Apps)
{
apps.Add(userApp.App);
}
foreach (var userRole in entity.User.Roles)
{
roles.Add(userRole.Role);
}
await context.SaveChangesAsync();
foreach (var app in apps)
{
userAppDbSet.Add(new UserApp(entity.UserId, app.Id));
}
foreach (var role in roles)
{
userRoleDbSet.Add(new UserRole(entity.UserId, role.Id));
}
await context.SaveChangesAsync();
result.Success = true;
result.Object = entity;
return result;
}
catch (Exception exp)
{
result.Success = false;
result.Exception = exp;
return result;
}
}
There has to be a better way of doing this? The full app can be found here, can someone tell me a better way of setting this up:
https://github.com/Joseph-Anthony-King/SudokuCollective

Related

Revert DbContext.Savechanges in case a second DbContext.Savechanges fail

I have the following code, which stores information in two different tables in the same method
public static async Task<Response> AddStockTransaction(StockTransactionsHeader header, List<StockTransactionsDetails> details)
{
using (DataContext dbContext = new DataContext())
{
try
{
dbContext.StockTransactionsHeader.Add(header);
await dbContext.SaveChangesAsync();
int hearderID = header.TransactionHeaderID;
foreach (var item in details)
{
item.TransactionHeaderID = hearderID;
}
dbContext.StockTransactionsDetails.AddRange(details);
await dbContext.SaveChangesAsync();
return new Response
{
IsSuccess = true
};
}
catch (Exception ex)
{
return new Response
{
IsSuccess = false,
Message = ex.Message
};
}
}
}
How can I do, in case there is an exception in the second SaveChanges () to revert the first one?
Once SaveChanges has been called, your datat is stored on your database. You should not call SaveChanges more than once in a call, unless you are willingly to persist the intermediate steps.
You can use a transaction scope to create managed transactions :
using (TransactionScope scope = CreateTransactionScope())
{
DoSomthing(context);
scope.Complete();
}
however, if the failure of the second part involves rolling back the first one, this means that both parts belong to the same transaction, therefore simply omitting the first SaveChanges would turn your code into a single transaction.
From my another awnser: You could use DbTransaction class.
private void TestTransaction()
{
var context = new MyContext(connectionString);
using (var transaction = context.Database.BeginTransaction())
{
try
{
// do your stuff
// commit changes
transaction.Commit();
}
catch
{
// 'undo' all changes
transaction.Rollback();
}
}
}

How to ignore a DbUpdateConcurrencyException when deleting an entity

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.

EF : related entities are set as INSERT instead of being only referenced to the added entity

I tried to find an answer through the related questions I got but I didn't see the same situation I have now. I a beginner with this framework.
The thing is that in the DB the TopicFeedbackType is always referred but TopicNavigatedUrl and Product are always inserted event if I attach the entity. What am I doing wrong? I attach the found entity to the correct entity set name. The entity gets attached. I noticed that before the p_TopicQuickFb have one of its property updated by an attached entity, its EntityKey is null, but when the currentNavUrl(for example) is set, the EntityKey of p_TopicQuickFb is not null anymore. Its value is "EntitySet=TopicQuickFeedbacks" but there is no id.
I am really lost in there.
public void AddTopicQuickFeedback(TopicQuickFeedback p_TopicQuickFb, string p_SessionID)
{
TopicFeedbackType currentType = this.GetTopicFeedbackType(p_TopicQuickFb.TopicFeedbackType.FeedbackType);
bool currentTypeAttached = false;
TopicNavigatedUrl currentNavUrl = this.GetTopicNavigatedUrl(p_TopicQuickFb.TopicNavigatedUrl.Url);
bool currentNavUrlAttached = false;
Product currentProduct = this.GetProduct(p_TopicQuickFb.Product.Name, p_TopicQuickFb.Product.MajorVersion, p_TopicQuickFb.Product.MinorVersion);
bool currentProductAttached = false;
using (COHFeedbackEntities context = GetObjectContext())
{
TopicFeedback tf = GetTopicFeedback(p_SessionID, context);
if (tf != null)
{
if (currentType != null)
{
p_TopicQuickFb.TopicFeedbackType = null;
context.AttachToOrGet<TopicFeedbackType>("TopicFeedbackTypes", ref currentType);
currentTypeAttached = true;
p_TopicQuickFb.TopicFeedbackType = currentType;
}
if (currentNavUrl != null)
{
p_TopicQuickFb.TopicNavigatedUrl = null;
context.AttachToOrGet<TopicNavigatedUrl>("TopicNavigatedUrls", ref currentNavUrl);
currentNavUrlAttached = true;
p_TopicQuickFb.TopicNavigatedUrl = currentNavUrl;
}
if (currentProduct != null)
{
p_TopicQuickFb.Product = null;
context.AttachToOrGet<Product>("Products", ref currentProduct);
currentProductAttached = true;
p_TopicQuickFb.Product = currentProduct;
}
tf.TopicQuickFeedbacks.Add(p_TopicQuickFb);
context.SaveChanges();
context.Detach(tf);
if (currentNavUrlAttached)
{
context.TopicNavigatedUrls.Detach(currentNavUrl);
}
if (currentProductAttached)
{
context.Products.Detach(currentProduct);
}
if (currentTypeAttached)
{
context.TopicFeedbackTypes.Detach(currentType);
}
}
}
}
I found the method in this post : Is is possible to check if an object is already attached to a data context in Entity Framework?
public static void AttachToOrGet<T>(this System.Data.Objects.ObjectContext context, string entitySetName, ref T entity)
where T : IEntityWithKey
{
System.Data.Objects.ObjectStateEntry entry;
// Track whether we need to perform an attach
bool attach = false;
if (
context.ObjectStateManager.TryGetObjectStateEntry
(
context.CreateEntityKey(entitySetName, entity),
out entry
)
)
{
// Re-attach if necessary
attach = entry.State == EntityState.Detached;
// Get the discovered entity to the ref
entity = (T)entry.Entity;
}
else
{
// Attach for the first time
attach = true;
}
if (attach)
{
context.AttachTo(entitySetName, entity);
}
}
Test method:
User user = new User(true, false, false);
string commentStr = "This is my comment";
Product product = new Product("ProductName", 7, 0);
TopicFeedbackComment commFeedback = new TopicFeedbackComment(commentStr, new TopicNavigatedUrl("http://testurl.com/test0"), product);
TopicFeedback feedback = new TopicFeedback(sessionID, user, FeedbackState.New);
provider.AddTopicFeedback(feedback);
TopicFeedback addedFeedback = provider.RetrieveTopicFeedback(sessionID);
provider.AddTopicFeedbackComment(commFeedback, sessionID);
Running this again and again do just INSERT to the
Can't post images so I can provide schema it if necessary.
My answer is in my last comment. I found it by myself.
If someone would like to comment why it's working this way it would be nice! :)

Generic Repository with EF 5.0 is not working

I have a generic repository for my Entities, all my entities (generated by the Code generation Item) have a personalized partial that implements an IID interface, at this point all of my entites must have a Int32 Id property.
So, my problems is with the update, here is my code
public class RepositorioPersistencia<T> where T : class
{
public static bool Update(T entity)
{
try
{
using (var ctx = new FisioKinectEntities())
{
// here a get the Entity from the actual context
var currentEntity = ctx.Set<T>().Find(((BLL.Interfaces.IID)entity).Id);
var propertiesFromNewEntity = entity.GetType().GetProperties();
var propertiesFromCurrentEntity = currentEntity.GetType().GetProperties();
for (int i = 0; i < propertiesFromCurrentEntity.Length; i++)
{
//I'am trying to update my current entity with the values of the new entity
//but this code causes an exception
propertiesFromCurrentEntity[i].SetValue(currentEntity, propertiesFromNewEntity[i].GetValue(entity, null), null);
}
ctx.SaveChanges();
return true;
}
}
catch
{
return false;
}
}
}
Someone can help me? this driving me crazy.
You can use the EF API to update the values of an entity as follows.
public static bool Update(T entity)
{
try
{
using (var ctx = new FisioKinectEntities())
{
var currentEntity = ctx.Set<T>().Find(((BLL.Interfaces.IID)entity).Id);
var entry = ctx.Entry(currentEntity);
entry.CurrentValues.SetValues(entity);
ctx.SaveChanges();
return true;
}
}
catch
{
return false;
}
}

build index using lucene.net 2.9.2.2

I have to use lucene.net 2.9.2.2 with NHibernate 3.0. I have started to edit this old code:
public void BuildSearchIndex()
{
FSDirectory entityDirectory = null;
IndexWriter writer = null;
var entityType = typeof(MappedSequence);
var indexDirectory = new DirectoryInfo(GetIndexDirectory());
if (indexDirectory.Exists)
{
indexDirectory.Delete(true);
}
try
{
entityDirectory = FSDirectory.GetDirectory(Path.Combine(indexDirectory.FullName, entityType.Name), true);
writer = new IndexWriter(entityDirectory, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), true, IndexWriter.MaxFieldLength.UNLIMITED);
}
finally
{
if (entityDirectory != null)
{
entityDirectory.Close();
}
if (writer != null)
{
writer.Close();
}
}
IFullTextSession fullTextSession = Search.CreateFullTextSession(this.Session);
// Iterate through Suppliers and add them to Lucene's index
foreach (MappedSequence instance in Session.CreateCriteria(typeof(MappedSequence)).List<MappedSequence>())
{
fullTextSession.Index(instance);
}
}
private string GetIndexDirectory()
{
INHSConfigCollection nhsConfigCollection = CfgHelper.LoadConfiguration();
string property = nhsConfigCollection.DefaultConfiguration.Properties["hibernate.search.default.indexBase"];
var fi = new FileInfo(property);
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fi.Name);
}
to build the index. The line:
FSDirectory.GetDirectory(Path.Combine(indexDirectory.FullName, entityType.Name), true);
still uses obsolete code. Could anyone be so kind and point out the necessary change. Thanks.
Christian
PS
Try using FSDirectory.Open(path) instead.