Is there a method to call in Entity Framework to see the data that has changed (in memory) and would be written to disk when SaveChanges is called?
I'd like to display something to the user indicating that there is an unsaved change. Table level is ok, but I'd prefer to know if a particular field/column has an unsaved change.
For example, add this method to your context:
IEnumerable<(string Key, string Entity, EntityState state,
IEnumerable<(string Property, object OriginalValue, object CurrentValue)> Properties)> GetChanges()
{
var states = new[] { EntityState.Added, EntityState.Modified, EntityState.Deleted };
return this.ChangeTracker.Entries().Where(c => states.Contains(c.State))
.Select(entry =>
(
string.Join(",", entry.Metadata.FindPrimaryKey()
.Properties.Select(p => p.PropertyInfo.GetValue(entry.Entity))),
entry.Metadata.ClrType.Name,
entry.State,
entry.Properties
.Where(p => p.IsModified == (p.EntityEntry.State == EntityState.Modified))
.Select(prop =>
(
prop.Metadata.PropertyInfo.Name,
prop.OriginalValue,
prop.CurrentValue
)
)));
}
It returns added, modified or deleted entity objects, listing their class names (not table names) and their properties, with original and current values. The key values are also included to be able to make a distinction between objects of the same type.
Of modified entities only the changed properties are listed.
Simply enumerate the ChangeTracker.Entries to examine all the tracked entities, and their EntityEntry.State.
Related
I don't understand why it is recommended everywhere to use AddOrUpdate in the Seed method?
We develop application for half a year already and the AddOrUpdates overwrites user changes every time we update the server. E.g. if we call in the Seed:
context.Styles.AddOrUpdate(new Style { Id = 1, Color = "red" });
And user changes the Style to "green" then on next server update we overwrite it to "red" again and we get very annoyed user.
It looks that if we change AddOrUpdate to Add we will be guaranteed from overwriting user data. If we still need some special case we can put it to separate migration. Unlike the general Configuration.Seed method particular migrations don't run twice over the same database version.
I assume that Style's primary key is Id. The overload of AddOrUpdate that you use only checks if there is a record having Id == 1. If so, it updates it. That's all.
What's going wrong here is that the primary key is a surrogate key, i.e. it's there for querying convenience, but it's got no business meaning. Usually, with migrations you want to look for the natural keys of entities though. That's how the user identifies data. S/he wants a green style, not a style identified by 1.
So I think you should use this overload of AddOrUpdate:
context.Styles.AddOrUpdate( s => s.Color,
new Style { Id = 1, Color = "red" });
Now when there is no red style anymore, a new one is inserted, overriding the Id value (assuming that it's generated by the database).
From your later comments I understand that you want to Add data when they're new, but not update them when they exist (compared by primary key). For this you could use a slightly adapted version of an AddWhenNew method I described here. For your case I would do it like so:
public T void MarkAsAddedWhenNew<T>(this DbContext context,
Expression<Func<T, object>> identifierExpression, T item)
where T : class
{
context.Set<T>().AddOrUpdate(identifierExpression, item);
if (context.Entry(item).State != System.Data.Entity.EntityState.Added)
{
var identifierFunction = identifierExpression.Compile();
item = context.Set<T>()
.Local
.Single(x => identifierFunction(item)
.Equals(identifierFunction(x)));
context.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
}
return item;
}
Re-fetching the item from the local collection is a nuisance, but necessary because of a bug in AddOrUpdate(). This bug also caused the error you got when setting the state of the original entry to Unchanged: it was a different instance than the attached one.
The way Add method acts is misleading. It Inserts data into database even if there is already a row with the same PrimaryKey as we do Add. It just creates new PrimaryKey ignoring our value silently. I should have tried it before asking the question, but anyway, I think I'm not the only one who confused by this. So, in my situation Add is even worse than AddOrUpdate.
The only solution I've come to is following:
public static void AddWhenNew<T>(this DbContext ctx, T item) where T : Entity
{
var old = ctx.Set<T>().Find(item.Id);
if (old == null)
ctx.Set<T>().AddOrUpdate(item);
/* Unfortunately this approach throws exception when I try to set state to Unchanged.
Something like:"The entity already exists in the context"
ctx.Set<T>().AddOrUpdate(item);
if (ctx.Entry(item).State != System.Data.Entity.EntityState.Added)
ctx.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
*/
}
I've seen sample code for reading an Entity Framework EdmProperty's StoreGeneratedPattern property (computed, identity, or none) in at least 2 places (here & here), but it doesn't work with my model. My context is an ObjectContext, the version is EF5; maybe this code broke with EF5? When I look at the properties for this property/column in the model, it shows identity.
Here is my code:
using ( var context = new MyApplicationEntities() )
{
var entityType = ( (EntityConnection)context.Connection )
.GetMetadataWorkspace() // can't call context.MetadataWorkspace - storage model will not be present
.GetType( "MyEntityTypeWithIdentityColumn", "MyApplicationModel.Store", DataSpace.SSpace ) as EntityType;
EdmMember identityColumn = entityType.Members["MyIdentityColumn"];
Facet item;
// All I get here for Facets is Nullable & DefaultValue
if ( identityColumn.TypeUsage.Facets.TryGetValue( "StoreGeneratedPattern", false, out item ) )
{
var value = ( (StoreGeneratedPattern)item.Value ) == StoreGeneratedPattern.Identity;
}
}
Pawel's question led me to examine the edmx and discover the answer. I said in my question that I could look at the model (UI) and see that the column in question was an identity column. In fact, it was not. In any case I expected the StoreGeneratedPattern property to exist since the enumeration contains all possible values (Identity, Computed, None).
My actual goal was to detect all entities whose primary key column/property was not identity.
I discovered through subsequent testing that the StoreGeneratedPattern property only exists for identity & computed; if the column is not store generated, it doesn't exist (StoreGeneratedPattern.None is never referenced). Thus, the sample code above is fine as long as it's modified to handle the absence of the property (which seems strange to me).
I am receiviing this error when I try to delete rows using entity framework. Really do not understand why!
The object cannot be deleted because it was not found in the
ObjectStateManager.
public void Delete(int ticketID)
{
Modules.Entity.gmEntities context = new Modules.Entity.gmEntities();
var ticketitem = context.xticketitem.Select(p => p.TicketID == ticketID);
ticketitem.ToList().ForEach(r => context.DeleteObject(r));
context.SaveChanges();
}
By the call of context.xticketitem.Select(p => p.TicketID == ticketID); you will get a list of booleans that do not exist in context.
I think you should do something like this:
var ticketitem = context.xticketitem.Where(p => p.TicketID == ticketID);
ticketItem.ToList().ForEach(r => context.xticketitem.DeleteObject(r));
context.SaveChanges();
EDIT:
I've moved .ToList() on the next line to make differences between our snippets more evident. Let's try revise it step by step:
When you call var ticketitem = context.xticketitem.Select(p => p.TicketID == ticketID);
You are creating query that will go by all xticketitems and return whether each item's TicketID equals ticketID variable passed as an argument to your Delete method.
Result of this query is IEnumerable<bool>.
My code returns IEnumerable<xticketitem>. It's main difference.
When you call context.DeleteObject(r) your r variable is bool. and you are calling DeleteObject method on context. That mathod accepts parameter of type object (that's why you don't get error at compile time).
I'm calling DeleteObject on xticketitem ObjectSet that accepts strogly-typed parameter of xticketitem type.
Let's suppose I have the following code:
TEModule teModule = Context.TEModules.Where(module => module.EnumValue.Equals(text.ModuleName)).FirstOrDefault();
if (teModule == null)
{
teModule = new TEModule();
teModule.EnumValue = text.ModuleName;
Context.TEModules.AddObject(teModule);
//Context.SaveChanges();
TEModule aux = Context.TEModules.Where(module => module.EnumValue.Equals(teModule.ModuleName)).FirstOrDefault();
}
My problem is that if I keep the "SaveChanges" commented, then on the next query the aux object is always null, because Context.TEModules is empty, even when I call the "AddObject" method.
However, if I call SaveChanges after AddObject, then on the next query the aux object is not null. The problem is that I don't want to call SaveChanges so often, because this is not the only piece of code in which I add objects, and the performance goes down if I do so.
So question is: Do I have to call SaveChanges after every AddObject call, if later I need to know if the object already exists?
The purpose of linq-to-entities query is to be executed and the execution is performed in the database so if you didn't saved the entity its database representation doesn't exist.
If you need to find locally stored entities (not persisted yet) you must query ObjectStateManager instead.
var entity = Context.ObjectStateManager.GetObjectStateEntries(EntitiState.Added)
.Where(e => !e.IsRelationship)
.Select(e => e.Entity)
.OfType<TEModule>()
.FirstOrDefault(m => m.EnumValue.Equals(teModule.ModuleName));
I am trying to update a resource as follows:
public void Update(Resource resource) {
Resource _resource = _resourceRepository.First(r => r.Id == resource.Id);
_resource.Content = resource.Content;
_resource.Description = resource.Description;
_resource.Locked = resource.Locked;
_resource.Name = resource.Name;
_resource.Restrictions.ToList().ForEach(r => _resource.Restrictions.Remove(r));
foreach (Restriction restriction in resource.Restrictions)
_resource.Restrictions.Add(new Restriction { Property = _propertyRepository.First(p => p.Id == restriction.Property.Id), Value = restriction.Value });
} // Update
I have something similar, and working, to create a resource with only one difference: I do not remove the Restrictions.
I am getting the following error:
A relationship from the
'Restrictions_ResourceId_FK'
AssociationSet is in the 'Deleted'
state. Given multiplicity constraints,
a corresponding 'Restrictions' must
also in the 'Deleted' state.
What am I missing?
EF did exactly what you told him to do. Removing item from parent object navigation collection only removes relation between parent and child object. It means it only sets ResourceId in Restriction to null which is not allowed by your entity model.
If your Restriction can't exist without related resource you should model relation as Identifying. It means that Restriction primary key will also contain ResourceId column. When you then remove restriction from parent object collection, EF will delete restriction instead of setting ResourceId to null.
I was having similar problems since the opposite of Add() obviously seemed Remove().
You must use the DeleteObject() function instead to delete child items.
Thanks.