I'm using .NET 3.5 SP1 in ASP.NET MVC application.
While using ObjectContext with Http Request lifetime, and trying to attach an entity ALREADY present in context, we get error:
"An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
For Example, the code :
Category newCategory = new Category {CategoryId = CategoryIdSelected};
ctx.AttachTo("CategorySet", newCategory);
will give error if 'Category' with CategoryId = CategoryIdSelected exists in ObjectContext.
Modified code to check for existing entity:
Category newCategory = new Category {CategoryId = CategoryIdSelected};
ObjectStateEntry stateEntry = null;
if( ctx.ObjectStateManager.TryGetObjectStateEntry(newCategory, out stateEntry)){
//EntityObject already attached in context, get it
newCategory = (EntityObject)stateEntry.Entity;
}else{
ctx.AttachTo("CategorySet", newCategory);
}
The modified code is still giving same error:
"[System.InvalidOperationException] = {"An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
Please advise ?
Thank You
QUESTION ADDENDUM:
More problems attaching Entities when using ObjectContext having lifetime of Http Request.
For Example, if we have 'AppUser','Category' and Department entities.
public class AppUser : System.Data.Objects.DataClasses.EntityObject{
public int Uid {get; set;}
public string UserName {get; set;}
public string Password {get; set;}
public Department Dept {get; set;}
public Category catg {get; set;}
...........
}
AppUser has relationship with Department and Category Entities.
Now when trying to attach 'user':
user = new AppUser{Uid=1,catg = new Category {categoryId=10}, Dept = new Department{departmentId=101}, ...}
var key = ctx.CreateEntityKey("AppUserSet", user);
if (ctx.ObjectStateManager.TryGetObjectStateEntry(key, out stateEntry)) {
will work ONLY if, in context :
there is NO Category with categoryId=10, and
there is NO Department with departmentId=101
One option, is to ensure context does not have attached entities by always retrieving using NOMERGE NoTracking option. BUT I found following problems with MergeOption.NoTracking:
Second call would still result in db hit
You don't get EntityKeys on EntityRefs. So EntityKey of XXXReference is null,which means NO FK Stub. Please see.
How to get EntityKey of Reference w/o loading both ends (both entities)?
Even though Entity are Detached, they have a reference to the DataContext (via entity._realtionships._context). Please see.
Please advise.
Thank You.
Your code is using two different contexts. You check one, and then attach to the other:
if( csContext. //...){
///
}else{
ctx. // ...
}
The entity appears to be in ctx, but not in csContext. My advice is to use only one context at a time whenever possible; it's much less confusing.
Update
OK, you've changed the code in your question.
My guess is that your stub object doesn't have an EntityKey, so TryGetObjectStateEntry is returning false. Try:
if( ctx.ObjectStateManager.TryGetObjectStateEntry(
new EntityKey("MyEntities.CategorySet", "CategoryId", CategoryIdSelected),
out stateEntry)){
Obviously, replace "MyEntities" with the actual model name.
Related
I have my entity defined like this:
public class Entity : BaseModel // Has the already ID defined
{
private int? companyId;
public Company? Company { get; set; }
public int? CompanyId {
get => this.companyId == 0 ? null : this.companyId; // I tried this for debugging purposes to force this value to "null" -> made no difference
set => this.companyId = value;
}
}
public class Company : BaseModel // Has the already ID defined
{
public IEnumerable<Entity> Entities { get; set; } = new List<Entity>();
}
Anyway, if I set the CompanyId to null, my DB throws an exception with the message: "FOREIGN KEY constraint failed". If the CompanyId is set to, e.g. 123, the relationship is resolved accordingly.
I mean, it makes sense, that EF cannot find null in my DB, but how do I want to set an optional value otherwise? I am using code first annotations only, hence my OnModelCreating of my context is completely empty.
How are you loading the entities in the first place? Are you loading an Entity by ID and trying to dis-associate it from a company, or have you loaded a company with it's entities and trying to remove one association?
Normally when working with relations where you have navigation properties, you want to de-associate them (or delete them) via the navigation properties, not the FK properties. For instance if loading a company and wanting to de-associate one of the entities you should eager-load the entities then remove the desired one from the collection:
var company = _context.Companies.Include(c => c.Entitites).Single(c => c.Id == companyId);
var entityToRemove = company.Entities.SingleOrDefault(e => e.Id == entityId);
if(entityToRemove != null)
company.Entities.Remove(entityToRemove);
_context.SaveChanges();
Provided that the relationship between Company and Entity is set up properly as an optional HasMany then provided these proxies are loaded, EF should work out to set the entityToRemove's FK to null.
If you want to do it from the Entity side:
var entityToRemove = _context.Entities.Include(e => e.Company).Single(e => e.Id == entityId);
entityToRemove.Company = null;
_context.SaveChanges();
That too should de-associate the entities. If these don't work then it's possible that your mapping is set up for a required relationship, though I am pulling this from memory so I might need to fire up an example to verify. :) You also should be checking for any code that might set that CompanyId to 0 when attempting to remove one, whether that might be happening due to some mapping or deserialization. Weird behaviour like that can occur when entities are passed around in a detached state or deserialized into controller methods. (which should be avoided)
Update: Code like this can be very dangerous and lead to unexpected problems like what you are encountering:
public virtual async Task<bool> Update(TModel entity)
{
Context.Update(entity);
await Context.SaveChangesAsync();
return true;
}
Update() is typically used for detached entities, and it will automatically treat all values in the entity as Modified. If model was already an entity tracked by the Context (and the context is set up for change tracking) then it is pretty much unnecessary. However, something in the calling chain or wherever has constructed the model (i.e. Entity) has set the nullable FK to 0 instead of #null. This could have been deserialized from a Form etc. in a view and sent to a Controller as an integer value based on a default for a removed selection. Ideally entity classes should not be used for this form of data transfer from view to controller or the like, instead using a POCO view model or DTO. To correct the behaviour as your code currently is, you could try the following:
public async Task<bool> UpdateEntity(Entity entity)
{
var dbEntity = Context.Set<Entity>().Include(x => x.Customer).Single(x => x.Id == entityId);
if (!Object.ReferenceEquals(entity, dbEntity))
{ // entity is a detached representation so copy values across to dbEntity.
// TODO: copy values from entity to dbEntity
if(!entity.CustomerId.HasValue || entity.CustomerId.Value == 0)
dbEntity.Customer = null;
}
await Context.SaveChangesAsync();
return true;
}
In this case we load the entity from the DbContext. If this method was called with an entity tracked by the DbContext, the dbEntity would be the same reference as entity. In this case with change tracking the Customer/CustomerId reference should have been removed. We don't need to set entity state or call Update. SaveChanges should persist the change. If instead the entity was a detached copy deserialized, (likely the case based on that 0 value) the reference would be different. In this case, the allowed values in the modified entity should be copied across to dbEntity, then we can inspect the CustomerId in that detached entity for #null or 0, and if so, remove the Customer reference from dbEntity before saving.
The caveats here are:
This won't work as a pure Generic implementation. To update an "Entity" class we need knowledge of these relationships like Customer so this data service, repository, or what-have-you implementation needs to be concrete and non-generic. It can extend a Generic base class for common functionality but we cannot rely on a purely Generic solution. (Generic methods work where implementation is identical across supported classes.)
This also means removing that attempt at trying to handle Zero in the Entity class. It should just be:
public class Entity : BaseModel
{
public Company? Company { get; set; }
[ForeignKey("Company")]
public int? CompanyId { get; set; }
// ...
}
Marking Foreign Keys explicitly is a good practice to avoid surprises when you eventually find yourself needing to break conventions that EF accommodates in simple scenarios.
Challenge in EF6:
how to check Id of resulting row in the database after running this (esentially adding an entity record):
repository.Add(myEntity1);
...and use that id to add the second entity which has property X = to the id of the first entity?
use that id to add the second entity which has property X = to the id of the first entity?
repository.Add(myEntity2);
Right now there is no linkage between entity 1 and entity 2 because i don;t know how to save the id (automatically generated by ef) after first add
... and preserve it for adding it as a fk in the second entity?
Thanks a lot
You could try this following after your call to SaveChanges:
myEntity2.X = myEntity1.Id;
Then call SaveChanges again. This doesn't really utilise the power of Entity Framework, however, which is in managing relationships between entities. If your class was defined something like this:
public class MyEntity
{
[Key]
public int Id { get; set; }
[ForeignKey(nameof(RelatedEntity))]
public int RelatedEntityId { get; set; }
public MyEntity RelatedEntity { get; set; }
}
You could add your entities something like the following, and the Id/foreign key matching would be handled for you after calling SaveChanges:
myEntity1.RelatedEntity = myEntity2;
This is a fairly general solution, so if you'd like something more specific then you will need to include more details in your question.
You can read more about configuring Entity Framework relationships here.
So with entity framework I'm trying to update two existing entities.
There I've the main object something like:
public class MainObject
{
public string Name { get; set; }
public virtual SmallObject Part { get; set;}
}
public class SmallObject
{
public string Name { get; set; }
}
In the repository I first check if the SmallObject already exists in the database by:
MainObject.Part = (from s in repoSmallObject.GetAll()
where s.name == MainObject.Part.Name
select s).FirstOrDefault();
Then finally I call the update method in my GenericRepository
repoMainObject.Update(MainObject)
which is defined as a generic repository method:
dbSet.Attach(entity)
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
But the relationship doesn't get updated. Why is that? Both objects are attached to context not?
*Edit: The two repo's are injected with the same Context.
And strangely enough the Add method works and also updates the relationship.
When you set
context.Entry(entity).State = EntityState.Modified;
you need at least to set the state after and before updates (i.e. context.Entry(mainObject).CurrentValues and OriginalValues) so EF can build the right UPDATE query (with right WHERE clause).
It works if you set
context.Entry(entity).State = EntityState.Added;
because EF needs just to generate an INSERT query.
I don't know exactly why you need it but usually I prefer to attach the object to the DbSet and modify the properties so EF handles various states.
dbSet.Attach(MainObject)
MainObject.Part = (from s in repoSmallObject.GetAll()
where s.name == MainObject.Part.Name
select s).FirstOrDefault();
(In your case does not work because MainObject.Part.Name does not change)
The attached object should have the same values of the database otherwise you have a concurrency exception.
BTW, why you don't read the old object (MainObject) from the DB than work on it???
I have a code first model with a mapping table so that I can map MenuItem to an IdentityRole, enabling the production of a menu based on the logged in users role assignment.
public class MenuItem
{
public int Id { get; set; }
public string Text { get; set; }
}
public class MenuRoleMap
{
[Key]
public int Id { get; set; }
public virtual MenuItem MenuItem { get; set; }
public virtual IdentityRole Role { get; set; }
}
The IdentityRole and rest of Identity is auto wired via IdentityDbContext which I've inherited through my ApplicationDbContext like this, then the context should be consistent.
public class ApplicationDbContext : IdentityDbContext<User>
All of the tables look right, they have the expected columns and foreign keys, here is the MenuRoleMap table
I have an valid existing instance of MenuItem and IdentityRole which I use to try and add a new entity item to this table
foreach (IdentityRole role in selectedRoles)
{
MenuRoleMap mrm = new MenuRoleMap();
mrm.MenuItem = menuItem;
mrm.Role = role;
db.MenuRoleMaps.Add(mrm);
}
db.SaveChanges(); /// <<<=== HERE ERROR BECAUSE THE role IS ALREADY IN DB
Which throws this error
A first chance exception of type 'System.Data.Entity.Validation.DbEntityValidationException' occurred in EntityFramework.dll
Role: Role SystemsAdministrator already exists.
Which of course, it does exist, I know that, it's already in the database. Surely the EF should not be trying to add a new entity item for the foreign key entity if it already exists?
It doesn't do it for the MenuItem, only the IdentityRole.
I thought the problem was proxy creation since the IdentityRole was a proxy object, so I turned that off
this.Configuration.ProxyCreationEnabled = false;
but I still get the same error.
My question is, how do you add an entity where the foreign key is an IdentityRole?
Thank you stackers.
ANSWER TO MY OWN QUESTION
After assistance from those below I discovered while investigating the various solutions that the problem wasn't one of context per se but the validity of an object. The object looked right, what I hadn't realised is that it wasn't the object from the context, it was a facsimile. By trying to add this facsimile to the model, the context quite rightly says it already exists, you can't add it again. By trying to override the state of the item I created a different kind of error.
The resolution was simply to reload the object from the context and then add that to the parent item like so
foreach (IdentityRole role in selectedRoles)
{
// Here I'm getting the role from the context using the ID I have from the facsimile
IdentityRole roleToUse = db.Roles.Where(x => x.Id == role.Id).FirstOrDefault();
// carry on as normal
MenuRoleMap mrm = new MenuRoleMap();
mrm.MenuItem = menuItem;
mrm.Role = roleToUse; // note I'm using the retrieved 'roleToUse'
db.MenuRoleMaps.Add(mrm);
}
db.SaveChanges();
Hey presto it all works.
It looks like you've retrieved the entity from another context and then assigned it to an entity which is then added to a different context. It would then try to insert the Role entity too.
Are you returning the Role from another method where the lifetime of the context is scoped to that method?
You may find the following link useful in regards to updating the state of objects:
Entity states and SaveChanges
The problem is that when you use db.Set<MyEntity>.Add you will mark all entities that are attached to the entity that is added as being added too. You have to explicitly mark them as being unchanged:
foreach (IdentityRole role in selectedRoles)
{
MenuRoleMap mrm = new MenuRoleMap();
mrm.MenuItem = menuItem;
mrm.Role = role;
db.MenuRoleMaps.Add(mrm);
db.Entry(role).State=EntityState.Unchanged;
}
db.SaveChanges();
My answer is you can't or at least should not.
Authentication (Roles), and Business (Menu) are different concerns of the application.
For me you have to bring in the ApplicationDb, the part of IdentityDb that you need and organize the synchronisation.
To illustrate my saying: Imaging you use Google or LiveID as authentification provider: can you imagine navigation properties from you ApplicationDd to Google or Microsoft Dbs ?
Clearly not.
So create a AppRole replicating the Role of the authentication database and use this table from your application database to build your menus.
In pseudo code this looks like:
List<Int32> l = IdentityContext.GetRolesForUser(currentUserId);
foreach (AppRole role in AppContext.Roles.Where(r => l.Contains(r.Id)))
{
MenuRoleMap mrm = new MenuRoleMap();
mrm.MenuItem = menuItem;
mrm.Role = role;
appContext.MenuRoleMaps.Add(mrm);
}
appContext.SaveChanges();
Another solution would be to use the same context for Application and Identity.
Inheritance of context seems fine, but I never tested it.
I have a simple controller method (via ajax) that inserts a new entity into the db and then queries the database for all of the same type of records and returns a list of those records(json).
What I can't figure out is that when I insert the first record and query for my list of records, any child entities are not lazy loaded. However, when I add a second or any number of subsequent records my list includes all child entities lazy loaded as expected. Here is some code:
public class Person
{
public int Id {get;set;}
public string Name {get;set;}
public int StateId {get;set;}
public virtual State State {get;set;}
}
public class PersonController : Controller
{
public ActionResult CreatePerson(PersonModel model)
{
var person = model.ToPersonEntity();
_personService.InsertPerson(person);
var people = _personService.GetAllPeople();//on first person inserted this list does NOT have State loaded
var personAddresssList = people.Select(x => x.ToPersonAddressFormat());
return Json(personAddressList, JsonRequestBehavior.AllowGet);
}
}
There isn't really any more relevant code as this is a pretty simple operation. I fixed the problem by using .Include(x => x.State) in my linq query but I've never had to do this as long as my properties were marked 'Virtual'.
The only thing I can think of is that EF still has my original person as a tracked entity and that when I pull up the list of persons and the only person is the one I just inserted, it uses the cached entity which would not have any child properties attached to it yet. If this is true then when I load a list of more than one person, some funky black magic in EF says "I see 2 items in the list I won't use the cached person I just inserted".
Any ideas?