I get an error telling me that: "The EntityKey property can only be set when the current value of the property is null." when I try to save an object with related object.
Here's my code:
public partial class Cat{
public bool Save()
{
try
{
using (var context = new PhonebookEntities())
{
if (this.ParentCat != null)
{
if (this.ParentCat.CategoryID == 0)
context.AddToCats(this.ParentCat);
}
context.AddToCats(this);
context.SaveChanges();
}
return true;
}
catch (System.Exception)
{
return false;
}
}
And here I create a Cat object and connect it to a relatet parent Cat object and then call the save method:
var cat = new Cat()
{
CatName = "Test",
ParentCat = Cat.GetById(1)
};
cat.Save();
Let me guess - the Cat.GetById(1) looks like:
public static Cat GetById(int id)
{
using (var context = new PhonebookEntities())
{
return context.Cats.Single(c => c.Id == id);
}
}
You are using two different contexts - that is the source of the issue. The first context loads the Cat and fills it EntityKey but the second context doesn't know this instance so once you call AddToCats it will add both new Cat and ParentCat as well but it fails because new entity cannot have filled EntityKey (I know that it is not a new entity but for the new instance of the context it is!).
Add based operations always add all unknown entities in the object graph. The entity is known by the context only if the same context loaded the entity or if you called .Attach for that entity.
Because of that, this is also incorrect:
if (this.ParentCat != null)
{
if (this.ParentCat.CategoryID == 0)
context.AddToCats(this.ParentCat);
}
Unknown ParentCat will be added automatically together with the current Cat. If you call this it will also add the current Cat but the next call will try to add it again => you will probably get an exception.
This whole can be solved by two ways:
Load the ParentCat on the same context instance as you save Cat
Don't load the ParentCat and either use dummy class or try to set EntityKey
Dummy class approach:
var parentCat = new Cat() { Id = 1 };
context.Cats.Attach(parentCat); // Use correct entity set name
var cat = new Cat()
{
CatName = "Test",
ParentCat = parentCat
};
cat.Save();
EntityKey aproach (this is more like a guess):
var cat = new Cat()
{
CatName = "Test",
// I hope ParentCatReference will be initialized
ParentCatReference.EntityKey = new EntityKey("Cats", "Id", 1) // Use correct entity set name
};
cat.Save();
Related
I have a repository class with the following code to update entities that have changed.
However I get an error
"The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects."
However I cant see that the context has changed since it is a property of my repository.
Inside the repository I have
protected void InnerUpdate(params T[] items)
{
DbSet<T> dbSet = ((DbContext)this.context).Set<T>();
foreach (T item in items)
{
object id1 = item.GetProperty("Id");
T originalEntity = dbSet.Find(id1);
((DbContext)this.context).Entry(originalEntity).CurrentValues.SetValues(item);
var navProps = GetNavigationProperties(originalEntity);
foreach (var navProp in navProps)
{
//Set originalEntity prop value to modifiedEntity value
navProp.SetValue(originalEntity, navProp.GetValue(item));
}
}
}
this.context.SaveChanges(); // Error occurs here
}
public List<PropertyInfo> GetNavigationProperties(T entity )
{
var t = entity.GetType();
ObjectContext objectContex = ((IObjectContextAdapter)((DbContext)this.context)).ObjectContext;
var elementType = objectContex.CreateObjectSet<T>().EntitySet.ElementType;
var properties = new List<PropertyInfo>();
var entityType = entity.GetType();
foreach (var navigationProperty in elementType.NavigationProperties)
{
properties.Add(entityType.GetProperty(navigationProperty.Name));
}
return properties;
}
I wonder if the problem is due to calling CreateObjectSet ? Is there a different way to do this?
I am using EF6.1
I changed the code as follows and got it working
var navProps = GetNavigationProperties(originalEntity);
foreach (var navProp in navProps)
{
//Set originalEntity prop value to modifiedEntity value
var newval = (LoggedEntity) navProp.GetValue(item);
object entity = null;
if (newval != null)
{
var tp = navProp.PropertyType;
var entities = ((DbContext)this.context).Set(tp);
entity = entities.Find(newval.Id);
}
navProp.SetValue(originalEntity, entity);
}
I have this method in my SurveyController class:
public ActionResult AddProperties(int id, int[] propertyids, int page = 1)
{
var survey = _uow.SurveyRepository.Find(id);
if (propertyids == null)
return GetPropertiesTable(survey, page);
var repo = _uow.PropertySurveyRepository;
propertyids.Select(propertyid => new PropertySurvey
{
//Setting the Property rather than the PropertyID
//prevents the error occurring later
//Property = _uow.PropertyRepository.Find(propertyid),
PropertyID = propertyid,
SurveyID = id
})
.ForEach(x => repo.InsertOrUpdate(x));
_uow.Save();
return GetPropertiesTable(survey, page);
}
The GetPropertiesTable redisplays Properties but PropertySurvey.Property is marked virtual and I have created the entity using the new operator, so a proxy to support lazy loading was never created and it is null when I access it. When we have access direct to the DbContext we can use the Create method to explicitly create the proxy. But I have a unit of work and repository pattern here. I guess I could expose the context.Create method via a repository.Create method and then I need to remember to use that instead of the new operator when I add an entity . But wouldn't it be better to encapsulate the problem in my InsertOrUpdate method? Is there some way to detect that the entity being added is not a proxy when it should be and substitute a proxy? This is my InsertOrUpdate method in my base repository class:
protected virtual void InsertOrUpdate(T e, int id)
{
if (id == default(int))
{
// New entity
context.Set<T>().Add(e);
}
else
{
// Existing entity
context.Entry(e).State = EntityState.Modified;
}
}
Based on the answer supplied by qujck. Here is how you can do it without having to employ automapper:
Edited to always check for proxy - not just during insert - as suggested in comments
Edited again to use a different way of checking whether a proxy was passed in to the method. The reason for changing the technique is that I ran into a problem when I introduced an entity that inherited from another. In that case an inherited entity can fail the entity.e.GetType().Equals(instance.GetType() check even if it is a proxy. I got the new technique from this answer
public virtual T InsertOrUpdate(T e)
{
DbSet<T> dbSet = Context.Set<T>();
DbEntityEntry<T> entry;
if (e.GetType().BaseType != null
&& e.GetType().Namespace == "System.Data.Entity.DynamicProxies")
{
//The entity being added is already a proxy type that supports lazy
//loading - just get the context entry
entry = Context.Entry(e);
}
else
{
//The entity being added has been created using the "new" operator.
//Generate a proxy type to support lazy loading and attach it
T instance = dbSet.Create();
instance.ID = e.ID;
entry = Context.Entry(instance);
dbSet.Attach(instance);
//and set it's values to those of the entity
entry.CurrentValues.SetValues(e);
e = instance;
}
entry.State = e.ID == default(int) ?
EntityState.Added :
EntityState.Modified;
return e;
}
public abstract class ModelBase
{
public int ID { get; set; }
}
I agree with you that this should be handled in one place and the best place to catch all looks to be your repository. You can compare the type of T with an instance created by the context and use something like Automapper to quickly transfer all of the values if the types do not match.
private bool mapCreated = false;
protected virtual void InsertOrUpdate(T e, int id)
{
T instance = context.Set<T>().Create();
if (e.GetType().Equals(instance.GetType()))
instance = e;
else
{
//this bit should really be managed somewhere else
if (!mapCreated)
{
Mapper.CreateMap(e.GetType(), instance.GetType());
mapCreated = true;
}
instance = Mapper.Map(e, instance);
}
if (id == default(int))
context.Set<T>().Add(instance);
else
context.Entry(instance).State = EntityState.Modified;
}
I'm writing a DAL class using EF4.0, I've read
http://www.codeproject.com/Articles/43367/ADO-NET-Entity-Framework-as-Data-Access-Layer
and
http://msdn.microsoft.com/en-us/magazine/cc700340.aspx
But when I test their code, I meet some problem with the Update and Delete method.
The DAL class all code is below:
public class FriendlinkDA : IDisposable
{
private EdiBlogEntities context;
public FriendlinkDA()
{
context = new EdiBlogEntities();
}
public void Dispose()
{
context.Dispose();
}
public FriendLink GetFriendLink(Guid id)
{
return context.FriendLink.FirstOrDefault(f => f.Id == id);
}
public void Update(FriendLink model)
{
// Way 1: (throw exception)
//context.Attach(model);
//model.SetAllModified(context);
//context.SaveChanges();
// Way 2:
EntityKey key;
object originalItem;
key = context.CreateEntityKey("FriendLink", model);
if (context.TryGetObjectByKey(key, out originalItem))
{
context.ApplyCurrentValues(key.EntitySetName, model);
//context.ApplyPropertyChanges(key.EntitySetName, model);
}
context.SaveChanges();
}
public void Delete(FriendLink model)
{
// Way 1:
context.Attach(model);
context.DeleteObject(model);
context.SaveChanges();
// Way 2:
//var item = context.FriendLink.FirstOrDefault(f => f.Id == model.Id);
//context.DeleteObject(item);
//context.SaveChanges();
}
}
The extension method is:
public static void SetAllModified<T>(this T entity, ObjectContext context) where T : IEntityWithKey
{
var stateEntry = context.ObjectStateManager.GetObjectStateEntry(entity.EntityKey);
var propertyNameList = stateEntry.CurrentValues.DataRecordInfo.FieldMetadata.Select
(pn => pn.FieldType.Name);
foreach (var propName in propertyNameList)
stateEntry.SetModifiedProperty(propName);
}
In the application, I am use the DAL like this:
// Delete
using (var optFriendlink = new FriendlinkDA())
{
var test = optFriendlink.GetFriendLink(new Guid("81F58198-D396-41DE-A240-FC306C7343E8"));
optFriendlink.Delete(test);
}
// Update
using (var optFriendlink = new FriendlinkDA())
{
var testLink = optFriendlink.GetFriendLink(new Guid("62FD0ACF-40C3-4BAD-B438-38BB540A6080"));
testLink.Title = "ABC";
optFriendlink.Update(testLink);
}
Question 1:
In Delete(), both way 1 and way 2 can work. Which one is better?
Question 2:
In Update(), way 1 give me an exception: The object cannot be attached because it is already in the object context. An object can only be reattached when it is in an unchanged state.
on this statment: context.Attach(model);
but way 2 is fine.
why is this happening? I also attach the model in Delete(), why Delete() is working fine? how I can write the update correctly?
The exception says it all:
An object can only be reattached when it is in an unchanged state.
You change the object in the code snippet under // Update, so that's why it cannot be re-attached.
As to which method is better. Normally you would get an object from a context, dispose of the context, do something with the object and then use a new context to save the object. In that case using Attach is much more comfortable then getting an object by Id first.
I need to update all fields except property1 and property2 for the given entity object.
Having this code:
[HttpPost]
public ActionResult Add(object obj)
{
if (ModelState.IsValid)
{
context.Entry(obj).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
return View(obj);
}
How to change it to add an exception to obj.property1 and obj.property2 for not being updated with this code?
Let's assume that you have a collection of the properties to be excluded:
var excluded = new[] { "property1", "property2" };
With EF5 on .NET 4.5 you can do this:
var entry = context.Entry(obj);
entry.State = EntityState.Modified;
foreach (var name in excluded)
{
entry.Property(name).IsModified = false;
}
This uses a new feature of EF5 on .NET 4.5 which allows a property to be set as not modified even after it has been previously set to modified.
When using EF 4.3.1 or EF5 on .NET 4 you can do this instead:
var entry = context.Entry(obj);
foreach (var name in entry.CurrentValues.PropertyNames.Except(excluded))
{
entry.Property(name).IsModified = true;
}
You can't define such an exception. You can however mark single properties as modified:
context.Entry(obj).Property(o => o.Property3).IsModified = true;
context.Entry(obj).Property(o => o.Property4).IsModified = true;
// etc.
Note that setting IsModified to false is not supported once you have marked the state of the whole entity to Modified.
For your purpose I would actually prefer to load the entity from the database and then update it using normal change tracking:
var objInDB = context.Objects.Single(o => o.Id == obj.Id);
obj.Property1 = objInDB.Property1;
obj.Property2 = objInDB.Property2;
context.Entry(objInDB).CurrentValues.SetValues(obj);
context.SaveChanges();
Note that only changed properties will be saved by default by Automatic Detect changes.
See EF 6 and EF Core articles
This question was already nicely answered, but I wanted to provide an extension method for anyone who would like to use it.
This code was developed for EF 4.3.1
//You will need to import/use these namespaces
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
//Update an entity object's specified columns, comma separated
//This method assumes you already have a context open/initialized
public static void Update<T>(this DbContext context, T entityObject, params string[] properties) where T : class
{
context.Set<T>().Attach(entityObject);
var entry = context.Entry(entityObject);
foreach(string name in properties)
entry.Property(name).IsModified = true;
context.SaveChanges();
}
Usage Example
using (FooEntities context = new FooEntities())
{
FooEntity ef = new FooEntity();
//For argument's sake say this entity has 4 columns:
// FooID (PK), BarID (FK), Name, Age, CreatedBy, CreatedOn
//Mock changes
ef.FooID = 1;
ef.Name = "Billy";
ef.Age = 85;
context.Update<FooEntity>(ef, "Name", "Age"); //I only want to update Name and Age
}
This is an update that works for .net CORE and maybe can help someone who needs a generic solucion and wants to exclude some properties base on different conditions.
I'm using reflection to iterate through the properties and update base on its property value, in this case, as example, i'm excluding the null properties.
public virtual TEntity Update(TEntity entity)
{
dbSet.Attach(entity);
dbContext.Entry(entity).State = EntityState.Modified;
var entry = dbContext.Entry(entity);
Type type = typeof(TEntity);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.GetValue(entity, null) == null)
{
entry.Property(property.Name).IsModified = false;
}
}
dbContext.SaveChanges();
return entity;
}
The answers above (most of them) use DbContext. For those who is using ObjectContext these solutions arent accessible.
Here is solution for ObjectContext strictly (EF5 .NET 4.5):
ctx.AddObject("ENTITYNAME", item);
ctx.ObjectStateManager.ChangeObjectState(item, EntityState.Modified);
var entry = ctx.ObjectStateManager.GetObjectStateEntry(item);
entry.RejectPropertyChanges("PROPERTY_TO_EXCLUDE");
I'm using EF4. I'm adding a series of new entities from a list of DTOs, and I'm not saving changes until after all of them are added. I'm wanting to set the IDs of the DTOs to what the new entities' IDs are. How on earth do I do this? Does EF provide a mechanism for this?
With a single entity I would do this:
public void InsertMyDto(MyDto a_dto)
{
var newEntity = new MyEntity
{
Name = a_dto.Name,
Type = a_dto.Type.ToString(),
Price = a_dto.Price
};
_dataContext.MyEntities.AddObject(newEntity);
_dataContext.SaveChanges();
a_dto.ID = newEntity.ID;
}
This works fine, but what do I do in this case?
public void InsertMyDtos(IEnumerable<MyDto> a_dtos)
{
foreach (var myDto in a_dtos)
{
var newEntity = new MyEntity
{
Name = myDto.Name,
Type = myDto.Type.ToString(),
Price = myDto.Price
};
// Does some validation logic against the database that might fail.
_dataContext.MyEntities.AddObject(newEntity);
}
_dataContext.SaveChanges();
// ???
}
I want to save all at once, because I have validation work (not shown above) that is done against the database and fails before it gets to SaveChanges, and if it fails I want it to fail as a whole transaction (i.e. rollback).
I don't think that EF can help you here. It even can't help you for a single instance which forces you to write a_dto.ID = newEntity.ID. The counterpart of this code for multiple entites is to keep track of the pairs of dtos and new entities:
public void InsertMyDtos(IEnumerable<MyDto> a_dtos)
{
Dictionary<MyDto, MyEntity> dict = new Dictionary<MyDto, MyEntity>();
foreach (var myDto in a_dtos)
{
var newEntity = new MyEntity
{
Name = myDto.Name,
Type = myDto.Type.ToString(),
Price = myDto.Price
};
dict.Add(myDto, newEntity);
// Does some validation logic against the database that might fail.
_dataContext.MyEntities.AddObject(newEntity);
}
_dataContext.SaveChanges();
foreach (var item in dict)
item.Key.ID = item.Value.ID; // Key is MyDto, Value is MyEntity
}