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).
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.
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;
*/
}
In the code below the value of item1 is null, while item2 is not null. Any ideas why Find method does not work properly in this case?
context= new EFModel.InputContext();
context.Items.Add(new Item{ Id = 1 });
var item1 = context.Items.Find(1);
var item2 = context.Items.Local.SingleOrDefault(i => i.Id == 1);
EDIT:
Thanks to #Maximc for pointing to the right direction. Apparently the issue was not the fact that the key was set to be auto generated and I assigned it. Somehow EF handles this situation properly by overwriting the aut generated Id with the user specified one. The problem was due to improper casting. My Id attribute is of type long so when I do var item1 = context.Items.Find((long)1); then it works.
Does the Id has the attribute [Key] ? I think this will be your issue.
EDIT:
After reading your comment, something you are saying here is false, you say Id is auto generated, (StoreGeneratedPattern="Identity") but then at the codeexample you use:
context.Items.Add(new Item{ Id = 1 });
Assiging a value to a Auto generated property will cause problems I think, anyway you shoudn t do that (Id = 1), just use:
context.Items.Add(new Item()); // the Id will be auto genereated since it has storeGeneratedPattern="Identity"
Then try to use Find again, also I think find will only work if there is only 1 item in the list, while if you force the Id (1) and there 2 Items with Id: 1 (Which I find strange, think there should be an error), then FirstOrDefault Will still work! (since it will just look for the first one and then return it. You should try, SingleOrDefault, here it will check if it found a item with that Id or there more then just 1, then it will return also null.
Hope you can solve it now :).
I have a table that has an Identity column and another column needs to have its value based on the computed identity column.
My EF code looks like this:
var context = new DBEntities();
var newTableRow = new TableRow();
newTableRow.Column1 = newTableRow.ComputedColumn;
context.TableRows.Add(newTableRow);
context.SaveChanges();
If ComputedColumn is an IDENTITY and Column1 is a nullable varchar(50) I would expect the value of Column1 to be the same as the ComputedColumn but it is null.
I have even tried this:
var context = new DBEntities();
var newTableRow = new TableRow();
context.TableRows.Add(newTableRow);
context.SaveChanges();
newTableRow.Column1 = newTableRow.ComputedColumn;
context.SaveChanges();
AND
var context = new DBEntities();
var newTableRow = new TableRow();
context.TableRows.Add(newTableRow);
context.SaveChanges();
var getTableRow = context.TableRows.Single(r => r.ComputedColumn == newTableRow.ComputedColumn);
getTableRow.Column1 = newTableRow.ComputedColumn.ToString();
context.SaveChanges();
Keep in mind that this is part of a larger transaction. What I don't want to do is complete the transaction then in a separate transaction do another update. I would like to keep everything in one transaction. This had been working in an insert proc before.
Thanks,
Brett
Your second an third example should work if ComputedColumn has DatabaseGeneratedOption.Identity. But they have separate transactions, which you don't want.
Your first example can't work. When a column has DatabaseGeneratedOption.Identity, EF reads the identity value back when executing SaveChanges. It is not aware of any special instruction you might have tried to convey with
newTableRow.Column1 = newTableRow.ComputedColumn;
As for EF, it's just an assignment with an empty value.
Maybe the best alternative is to add a computed column to your database table. You will kill two birds with one stone:
It is transactional, because the computed column just re-displays the identity column
You prevent redundancy and possible value conflicts.
I just reviewed the model and you're right there was a change I didn't notice. Column1 had been set as a computed value. Removing that value should fix the problem.
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.