I am using Entity Framework 7 and I need to be able to track changes. I am ultimately doing something like this during the on SaveChanges I am overriding it then at the end using base.SaveChanges():
foreach (var ent in this.ChangeTracker.Entries().Where(p => p.State == EntityState.Deleted || p.State == EntityState.Modified).ToList())
{
// For each changed record, get the audit record entries and add them
foreach (TableChange x in GetTableChangeRecordsForChange(ent, _ChangeUser))
{
this.TableChanges.Add(x);
val = true;
}
}
This ultimately calls out to get table change records:
private IEnumerable<TableChange> GetTableChangeRecordsForChange(EntityEntry dbEntry, string userId)
{
List<TableChange> result = new List<TableChange>();
foreach (var ent in this.ChangeTracker.Entries().Where(p => p.State == EntityState.Deleted || p.State == EntityState.Modified).ToList())
{
// For each changed record, get the audit record entries and add them
foreach (TableChange x in GetTableChangeRecordsForChange(ent, _ChangeUser))
{
this.TableChanges.Add(x);
val = true;
}
}
if (dbEntry.State == EntityState.Modified)
{
foreach (var property in dbEntry.Entity.GetType().GetTypeInfo().DeclaredProperties)
{
// For updates, we only want to capture the columns that actually changed
if (!object.Equals(dbEntry.Property(property.Name).OriginalValue, dbEntry.Property(property.Name).CurrentValue))
{
result.Add(new TableChange()
{
Action = "U",
ColumnName = property.Name,
CreatedBy = userId,
CreatedOn = DateTime.Now,
OldValue = dbEntry.Property(property.Name).OriginalValue.ToString(),
NewValue = dbEntry.Property(property.Name).CurrentValue.ToString(),
TableName = dbEntry.Entity.GetType().GetTypeInfo().Name,
TableID = dbEntry.Property("ID").CurrentValue.ToString()
});
}
}
}
}
Now the issue that I am facing is both OriginalValue & CurrentValue and the new value that was entered. It is not tracking what the original value was.
I do have:
this.ChangeTracker.AutoDetectChangesEnabled = true;
Yet I am still not getting it to provide me the original and current value correctly. Any help is greatly appreciated.
So I figured out my own issue... the issue I was facing was I was attaching the record that needed updating. I ultimately updated the record by creating my Update Function:
public T ADMSUpdate<T>(T entity, T originalEntity)
{
object propertyValue = null;
PropertyInfo[] properties = originalEntity.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
propertyValue = null;
if (null != property.GetSetMethod())
{
PropertyInfo entityProperty = entity.GetType().GetProperty(property.Name);
propertyValue = entity.GetType().GetProperty(property.Name).GetValue(entity, null);
if (null != propertyValue)
{
property.SetValue(originalEntity, propertyValue, null);
}
}
}
return entity;
}
Related
I am trying to get properties from a field on an editable form that was NOT created with iText.
I have a checkbox group (same name, call it AbcField with YES having an export value of yes and NO having an export value of no.
Yet, if I do the following:
if (fields.TryGetValue(kvp.Key, out PdfFormField formField))
{
if (formField is PdfButtonFormField)
{
var buttonField = formField as PdfButtonFormField;
buttonField.SetRadiosInUnison(false);
buttonField.SetValue(kvp.Value, true);
}
I still end up with this:
So, how do I only check ONE option?
EDIT: I found a solution, but it seems like a hack. So I was hoping for a better answer, but this does work:
if (formField is PdfButtonFormField)
{
var buttonField = formField as PdfButtonFormField;
var options = fields.Where(x => x.Key.Contains($"{kvp.Key}."));
buttonField.SetRadiosInUnison(false);
if (options != null && options.Any())
{
foreach (var option in options)
{
var optionField = option.Value;
if (optionField != null)
{
optionField.SetCheckType(PdfFormField.TYPE_CROSS);
var ap = optionField.GetPdfObject()?.GetAsDictionary(PdfName.AP);
if (ap != null)
{
var d = ap.GetAsDictionary(PdfName.D);
var n = ap.GetAsDictionary(PdfName.N);
var entrySet = d != null ? d.EntrySet().Select(x => x.Key).ToList() :
n?.EntrySet().Select(x => x.Key).ToList();
if (entrySet != null)
{
foreach (var entry in entrySet)
{
var entryText = entry.ToString();
if (entryText.Equals($"/{kvp.Value}", StringComparison.InvariantCultureIgnoreCase)
{
optionField.SetValue(kvp.Value);
}
}
}
}
}
}
}
What I am trying to create is when a user select an item, that item will disappear from the list of items. Some items can be submitted once and once it is submitted, the user not be able to submit the same item again. Submitted items will be logged to the database.
The issue I am having is figuring out what is wrong with my logic here as it is breaking and what can I do to improve this?
using (var db = new myDatabase())
{
var itemLists = db.GetAllItem().ToList();
var userSubmittedItems = db.GetAllUserItem("LoginID").ToList();
if (userSubmittedItems.Count > 0)
{
foreach (var submittedItems in userSubmittedItems)
{
foreach (var item in itemLists)
{
int itemID = item.ItemID;
int userItemID = userSubmittedItems.UserItemID;
if (itemID == userItemID && item.OneTime == true)
{
itemLists.Remove(item);
}
}
}
}
you're only removing the items in your collection itemLists, you are not performing anything in the database it self... for that you should, and imagining that your Entity for the Items is called ItemEntity do this:
using (var db = new myDatabase())
{
var itemLists = db.GetAllItem().ToList();
var userSubmittedItems = db.GetAllUserItem("LoginID").ToList();
if (userSubmittedItems.Count > 0)
{
foreach (var submittedItems in userSubmittedItems)
{
foreach (var item in itemLists)
{
int itemID = item.ItemID;
int userItemID = userSubmittedItems.UserItemID;
if (itemID == userItemID && item.OneTime == true)
{
itemLists.Remove(item);
db.ItemEntity.Remove(item); // mark for delete
}
}
}
db.SaveChanges(); // all marked items, if any, will now
// be committed in a db call
}
more on removing records with EF: Delete a single record from Entity Framework?
I am fairly new to EF and seem to have become a bit stuck with the following issue..
I have the following situation.
Two applications using the same database, and have both loaded the context at the same time displaying the same information.
Application 1 then goes and deletes a entity and updates the database with savechanges.
Application 2 then goes and saves back the same modified entity.
We are using an updategraph to recurse through the tree and save all the entities to the database context.
However, when we come to saving the deleted entity, this doesn't exist in the db context obtained from the database but we want to add this back to the database.
I have tried doing an ADD, but this gives us a multiplicity error as it is trying to add the full tree back to the context!
Any ideas please?
The following is a code snippet from the updategraph extension method. (http://github.com/refactorthis/GraphDiff)
private static void RecursiveGraphUpdate<T>(DbContext context, T dataStoreEntity, T updatingEntity, UpdateMember member) where T:class
{
if (member.IsCollection)
{
// We are dealing with a collection
var updateValues = (IEnumerable)member.Accessor.GetValue(updatingEntity, null);
var dbCollection = (IEnumerable)member.Accessor.GetValue(dataStoreEntity, null);
var keyFields = context.GetKeysFor(updateValues.GetType().GetGenericArguments()[0]);
var dbHash = MapCollectionToDictionary(keyFields, dbCollection);
// Iterate through the elements from the updated graph and try to match them against the db graph.
List<object> additions = new List<object>();
foreach (object updateItem in updateValues)
{
var key = CreateHash(keyFields, updateItem);
// try to find in db collection
object dbItem;
var itemID = updateItem.GetType().GetProperty("ID");
if (dbHash.TryGetValue(key, out dbItem))
{
var property = updateItem.GetType().GetProperty("Deleted");
bool ysn = property.GetValue(updateItem, null).ToString() == "True" ? true : false;
if (ysn)
{
context.Entry(dbItem).CurrentValues.SetValues(updateItem);
continue;
}
// If we own the collection
if (member.IsOwned)
{
context.Entry(dbItem).CurrentValues.SetValues(updateItem); // match means we are updating
foreach (var childMember in member.Members)
RecursiveGraphUpdate(context, dbHash[key], updateItem, childMember);
}
dbHash.Remove(key); // remove to leave only db removals in the collection
}
else
additions.Add(updateItem);
}
// Removal of dbItem's left in the collection
foreach (var dbItem in dbHash.Values)
{
var property = dbItem.GetType().GetProperty("Deleted");
bool ysn = property.GetValue(dbItem, null).ToString() == "True" ? true : false;
if (ysn)
{
// Own the collection so remove it completely.
if (member.IsOwned)
context.Set(dbItem.GetType()).Remove(dbItem);
dbCollection.GetType().GetMethod("Remove").Invoke(dbCollection, new[] { dbItem });
}
}
// Add elements marked for addition
foreach (object newItem in additions)
{
if (!member.IsOwned)
context.Set(newItem.GetType()).Attach(newItem);
// Otherwise we will add to object
dbCollection.GetType().GetMethod("Add").Invoke(dbCollection, new[] { newItem });
}
}
else // not collection
{
var dbvalue = member.Accessor.GetValue(dataStoreEntity, null);
var newvalue = member.Accessor.GetValue(updatingEntity, null);
if (dbvalue == null && newvalue == null) // No value
return;
// If we own the collection then we need to update the entities otherwise simple relationship update
if (!member.IsOwned)
{
if (dbvalue != null && newvalue != null)
{
var keyFields = context.GetKeysFor(newvalue.GetType());
var newKey = CreateHash(keyFields, newvalue);
var updateKey = CreateHash(keyFields, dbvalue);
if (newKey == updateKey)
return; // do nothing if the same key
}
if (context.Entry(newvalue).State == EntityState.Detached)
context.Set(newvalue.GetType()).Attach(newvalue);
member.Accessor.SetValue(dataStoreEntity, newvalue, null);
context.Entry(newvalue).Reload();
// TODO would like to do this: context.Entry(newvalue).State = EntityState.Unchanged;
// However it seems even though we are in an unchanged state EF will still update the database if the original values are different.
}
else
{
if (dbvalue != null && newvalue != null)
{
// Check if the same key, if so then update values on the entity
var keyFields = context.GetKeysFor(newvalue.GetType());
var newKey = CreateHash(keyFields, newvalue);
var updateKey = CreateHash(keyFields, dbvalue);
// perform update if the same
if (updateKey == newKey)
{
context.Entry(dbvalue).CurrentValues.SetValues(newvalue);
context.Entry(dbvalue).State = EntityState.Modified;
}
else
member.Accessor.SetValue(dataStoreEntity, newvalue, null);
}
else
member.Accessor.SetValue(dataStoreEntity, newvalue, null);
// TODO
foreach (var childMember in member.Members)
RecursiveGraphUpdate(context, dbvalue, newvalue, childMember);
}
}
}
I have some data that comes through and I generate a dataTable based on the type in a Dictionary list of string objects. The problem I'm having is that the code checks the Dictionary FirstOrDefault to build the datatable, but if any of those values are null things blow up quickly.
Is there a way I can take the following code and iterate through the rest of the dictionary values IF a particular key.Value is Null within those first set of Values to try and find a valid Type?
I tried simply checking and setting the type to string, but IF that column has any real values in it that fails (for instance dateTime when its set to string). I'd like to loop through all the key.Key type Values in question to try and discover if any of the cells have a type and if they are all null then the string would work.
public DataTable(IEnumerable<Dictionary<string, object>> source)
{
if (source != null)
{
var firstItem = source.FirstOrDefault();
if (firstItem != null)
{
//foreach (var item in source)
//{
// var u = item.Values;
// var t = item.Keys;
//}
foreach (var key in firstItem)
{
if (key.Value == null) //check if value is null and try and find the next value for that key.Key that isn't
{
foreach (var item in source)
{
var kk = key.Key;
var ik = item.Keys;
}
//...some logic to try and find if the key.Key Value in question has any legitimate value other than null to set the DataType to
//set value to some type here to avoid things blowing up.
Columns.Add(new DataColumn() { ColumnName = key.Key, DataType = typeof(string) });
}
else
{
Columns.Add(new DataColumn() { ColumnName = key.Key, DataType = key.Value.GetType() });
}
}
foreach (var item in source)
{
var row = new DataRow();
foreach (var key in item)
{
row[key.Key] = key.Value;
}
Rows.Add(row);
}
}
}
}
So, if you start by getting only your keys, and then pull the first non-null value for each key, it should simplify your logic (though it may be a bit more process intensive):
if ( source != null )
{
var keys = ( from d in source
from k in d.Keys
select k ).Distinct();
foreach ( var key in keys)
{
//...some logic to try and find if the key.Key Value in question has any legitimate value other than null to set the DataType to
var thisKey = key;
var valueNotNull = source.FirstOrDefault( dictionary => dictionary[thisKey] != null );
var colType = valueNotNull != null ? valueNotNull[thisKey].GetType() : typeof( string );
dt.Columns.Add( new DataColumn()
{
ColumnName = thisKey,
DataType = colType
} );
}
Its not clear what you are trying but about the datacolumn beeing null and finding the next that has the same key replace your foreach:
foreach (var item in source)
{
var kk = key.Key;
var ik = item.Keys;
}
With this:
var notnul = source.FirstOrDefault(x => x.ContainsKey(key.Key) && x[key.Key] != null);
And since we know its a datacolumn:
Columns.Add((DataColumn)notnul.Values.First());
and this var row = new DataRow(); will get you in trouble,consider doing this:
foreach (var item in source)
{
//var row = new DataRow();
foreach (var key in item)
{
if (key.Value is DataRow)
{
DataRow row = (DataRow)key.Value;
Rows.Add(row);
}
}
}
I'm using a timestamp column to check the concurrency in my entity. The exception is correctly thrown when data is not the same in 2 different contexts.
When such an exception occurs when saving, I call the following method to handle it:
public static void HandleOptimisticConcurrencyException(ObjectContext context, OptimisticConcurrencyException ex)
{
string msg = #"The data has changed while you were editing it.
If you save, your changes will override the previous ones.
If you don't, your changes will be lost.
Do you want to save your changes ?";
var ret = System.Windows.MessageBox.Show(msg, "Concurrency error...", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (ret == MessageBoxResult.Yes)
{
if (ex.StateEntries != null)
{
foreach (var item in ex.StateEntries)
{
context.Refresh(RefreshMode.ClientWins, item.Entity);
}
}
}
else
{
if (ex.StateEntries != null)
{
foreach (var item in ex.StateEntries)
{
context.Refresh(RefreshMode.StoreWins, item.Entity);
}
}
}
context.SaveChanges();
}
I checked, and the refresh is executed on each faulted entity.
However, the second SaveChanges() always rethrows an OptimisticConcurrencyException.
Am I doing something wrong ?
Thanks in advance
Edit
I've noticed that the problem arises because of a method call before the first SaveChanges()
try
{
this.UpdateFlags();
this.repository.Context.SaveChanges();
}
catch (OptimisticConcurrencyException ex)
{
ExceptionHelpers.HandleOptimisticConcurrencyException(this.repository.Context, ex);
}
If I comment out the UpdateFlags() call, I have no problem.
Here is the code for this method:
private void UpdateFlags()
{
DateTime now = DateTime.Now;
int masterId = (int)this.navigationContext.Item;
var master = this.repository.Context.Masters.Where(e => e.Id == masterId).FirstOrDefault();
foreach (var project in master.Projects)
{
// update flags for each project.
if (project.Dashboard == null)
{
project.Dashboard = new Dashboard();
}
var flags = project.Dashboard;
flags.ModifiedOn = now;
// Update DP flags
var dpFlag = (int)project.Tasks.Where(e => e.TaskDP != null)
.Select(e => this.CalculateCompletionStatus(e, now))
.DefaultIfEmpty(CompletionStatusType.Ok)
.Max();
flags.DP = dpFlag;
// Update TRS flags
var trsFlag = (int)project.Tasks.Where(e => e.TaskTRSs != null)
.Select(e => this.CalculateCompletionStatus(e, now))
.DefaultIfEmpty(CompletionStatusType.Ok)
.Max();
flags.TRS = trsFlag;
// Update REV flags
var revFlag = (int)project.Tasks.Where(e => e.TaskREV != null)
.Select(e => this.CalculateCompletionStatus(e, now))
.DefaultIfEmpty(CompletionStatusType.Ok)
.Max();
flags.REV = revFlag;
// Update DTP flags
var dtpFlag = (int)project.Tasks.Where(e => e.TaskDTP != null)
.Select(e => this.CalculateCompletionStatus(e, now))
.DefaultIfEmpty(CompletionStatusType.Ok)
.Max();
flags.DTP = dtpFlag;
// Update DEL flags
var delFlag = (int)project.Tasks.Where(e => e.TaskDEL != null)
.Select(e => this.CalculateCompletionStatus(e, now))
.DefaultIfEmpty(CompletionStatusType.Ok)
.Max();
flags.DEL = delFlag;
// Update FIN Flag
var finFlag = (int)project.SalesTasks.Select(e => this.CalculateCompletionStatus(e, now))
.DefaultIfEmpty(CompletionStatusType.Ok)
.Max();
flags.FIN = finFlag;
// Update Project flag
if (flags.REV == (int)CompletionStatusType.Client && project.DTPBeforeReview.HasValue && project.DTPBeforeReview.Value == false)
{
// Corner case : Review is late because of an external person (= grey) and DTP Before REV is not set
// => all moments after REV are not taken in account.
var projFlag = new List<int> { dpFlag, trsFlag, revFlag }.Max();
flags.ProjectStatus = projFlag;
}
else
{
var projFlag = new List<int> { dpFlag, trsFlag, revFlag, dtpFlag, delFlag, finFlag }.Max();
flags.ProjectStatus = projFlag;
}
}
}
However I don't see where the problem is, as this is made before the first SaveChanges()
Ok I think I found how to solve that.
The problem doesn't come from the first object who causes the first exception, but deeper in this object.
To solve that, I updated my method like this:
public static void HandleOptimisticConcurrencyException(ObjectContext context, OptimisticConcurrencyException ex)
{
string msg = #"The data has changed while you were editing it.
If you save, your changes will override the previous ones.
If you don't, your changes will be lost.
Do you want to save your changes ?";
var ret = System.Windows.MessageBox.Show(msg, "Concurrency error...", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (ret == MessageBoxResult.Yes)
{
if (ex.StateEntries != null)
{
foreach (var item in ex.StateEntries)
{
context.Refresh(RefreshMode.ClientWins, item.Entity);
}
}
}
else
{
if (ex.StateEntries != null)
{
foreach (var item in ex.StateEntries)
{
context.Refresh(RefreshMode.StoreWins, item.Entity);
}
}
}
do
{
try
{
context.SaveChanges();
break;
}
catch (OptimisticConcurrencyException ex2)
{
if (ret == MessageBoxResult.Yes)
{
foreach (var item in ex2.StateEntries)
{
context.Refresh(RefreshMode.ClientWins, item.Entity);
}
}
else
{
foreach (var item in ex2.StateEntries)
{
context.Refresh(RefreshMode.StoreWins, item.Entity);
}
}
}
}
while (true);
}
I'm open to any other better suggestion though...