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.
Related
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.
My scenario is similar to the following:
User a = ...; //has email pippo#gmail.com
em.remove(a);
...
User aUser = findUserByEmail("pippo#gmail.com");
where findUser queries the object using a named query marked as CheckCacheThenDatabase. Unfortunately, it returns the object (in removed status) and not null as expected.
So, why the L2 cache is not updated accordingly?
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).
How do I get both source and target navigation property names for a given RelatedEnd?
Assuming I need the target reference given a navigation property called 'prop'
// Get all related ends and find the reference for this FK
IEnumerable<IRelatedEnd> relEnds =
((IEntityWithRelationships)position).RelationshipManager.GetAllRelatedEnds();
EntityReference er =
relEnds.First(re => re.TargetRoleName == prop) as EntityReference;
You don't need an entity instance.
Because the line below gives the NavigatioProperty List.
var list=this.ObjectContext.EntitiesName.EntitySet.ElementType.NavigationProperties
And the below line returns the list of dependent properties. It can be more than one property.
list[0].GetDependentProperties()
https://stackoverflow.com/a/4988123/413032