folks,
I have a problem when I'm creating new object that has master. The problem actually is that EF is trying to create master object too, but I don't need it.
My two poco classes look like:
[DataContract(IsReference = true)]
public class Application
{
[DataMember]
public int ID { get; set; }
[DataMemeber]
public int ProjectManagerId {get; set; }
[DataMember]
public ProjectManager PM { get; set;}
}
[DataContract(IsReference = true)]
public class ProjectManager
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string FullName { get; set; }
}
When I'm creating new object on ASP.NET MVC page, I've got object of Application class, that has ProjectManagerId equals to 1 and PM field with ID = 0 and FullName for example 'Forest Gump'.
So, when I'm adding object, I have exception that application.PM.ID cannot be 0:
context.Applications.AddObject(application);
context.SaveChanges();
Is it possible to addobject my object without adding master?
I found workaround, but I don't like it: it is assigning PM field to null before adding object to context
application.PM = null;
EF has one core rule you must be aware of. It works with whole object graph and it doesn't allow combining detached and attached entities in the same object graph. It means that both Attach and AddObject method will be always executed on all entities in the object graph (it will traverse your navigation properties and execute the operation recursively).
Because of this essential behavior you must handle existing objects manually. You have three options:
Don't use navigation property. Your Application class has ProjectManagerId exposed. You need only to set this property to ID of the existing manger without populating ProjectManager navigation property to build a relation.
var application = new Application { ProjectManagerId = 1 };
context.Applications.AddObject(application);
context.SaveChanges();
Attach your parent, add child and only after that make connection between them:
// Create attached existing project manager
var projectManager = new ProjectManager { ID = 1 };
context.ProjectManagers.Attach(projectManager);
// Create a new added application
var application = new Applications();
context.Applications.AddObject(application);
// Now you are making relation between two entities tracked by the context
application.ProjectManager = projectManager;
context.SaveChanges();
The last option is simply fixing the state of existing entities. In this case you will set project manger to be unchanged while application will still remain in the added state:
// Add application and its related project manager
context.Applications.AddObject(application);
context.ObjectStateManager.ChangeObjectState(application.ProjectManager, EntityState.Unchanged);
context.SaveChanges();
Related
Following through Julie Lerman's Pluralsight course EF Core 6 Fundamentals I've created two classes in my own project (my own design, but identical to the course in terms of class structure/data hierarchy):
Class 1: Events - To hold information about an event being held (e.g. a training course), with a title and description (some fields removed for brevity):
public class EventItem
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EventItemId { get; set; }
[Required(AllowEmptyStrings = false)]
public string EventTitle { get; set; }
public string? EventDescription { get; set; }
[Required]
public List<EventCategory> EventCategories { get; set; } = new();
}
Class 2: Event categories - Each event can be linked to one or more pre-existing (seeded) categories (e.g. kids, adult).
public class EventCategory
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EventCategoryId { get; set; }
[Required]
public string EventCategoryName { get; set; }
public List<EventItem>? EventItems { get; set; }
}
In my Razor form to create the event, the user can select from multiple categories. Using EF Core I take the posted data (via a VM/DTO object) and construct the relevant parent/child entities. However upon saving to the database I get an exception as EF Core tries to re-create the categories when they already exist:
Cannot insert explicit value for identity column in table
'EventCategories' when IDENTITY_INSERT is set to OFF.
My code explicitly looks up the existing categories selected by the user, but the context tracker appears to still believe they need inserting, in addition to creating the many-to-many relationship.
I'd appreciate any input as to why this is happening please:
using (var dbcontext = DbFactory.CreateDbContext())
{
// Get selected categories from user's check box list
var selectedCategoryIds = _eventCagetories.Where(c => c.isSelected).Select(c => c.EventCategoryId).ToList();
// Create new Event
var newEventItem = new EventFinderDomain.Models.EventItem() {
EventTitle = _eventItemDTO.EventTitle,
EventDescription = _eventItemDTO.EventDescription,
EventUrl = _eventItemDTO.EventUrl,
TicketUrl = _eventItemDTO.TicketUrl
};
// Find categories from the database based on their ID value
var selectedEventCategories = dbcontext.EventCategories.Where(c => selectedCategoryIds.Contains(c.EventCategoryId)).ToList();
// Add the categories to the event
newEventItem.EventCategories!.AddRange(selectedEventCategories);
// Add the event to the change tracker
await dbcontext.EventItems.AddAsync(newEventItem); // <-- Created correctly with child list objects added
// Detect changes for debugging
dbcontext.ChangeTracker.DetectChanges();
var debugView = dbcontext.ChangeTracker.DebugView; // <-- Incorrectly shows newEventItem.Categories being added
// Save to database
await dbcontext.SaveChangesAsync(); // <-- Cannot insert explicit value for identity column
}
The Event entity appears to be correctly created in the debugger with its related child categories included:
The change tracker however incorrectly shows the selected categories being added again when they already exist:
After commenting out every line of code in the app and adding back in until it broke, it emerges the problem was elsewhere within Program.cs:
builder.Services.AddDbContextFactory<EventFinderContext>(
opt => opt.UseSqlServer(new SqlConnectionStringBuilder() {/*...*/}.ConnectionString)
.EnableSensitiveDataLogging()
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) // <-- THE CULPRIT
);
In the training video this method was described as a way of reducing overhead for disconnected apps. I had assumed that because of the disconnected nature of HTTP, this would be beneficial and that context would be re-established when creating the model's child data. This was incorrect on my part.
I should have used .AsNoTracking() only when retriving read-only data from my database. For example, loading in the child-data for a new model that wouldn't be modified directly, but used to create the many-to-many data (explicitly, for the category data option items only and not for the event data).
I have a problem creating a related entity in Entity Framework Core 2.0. I've just created the solution, consisting of an Asp.Net Core backend project, and a UWP project to act as client. Both solutions share model. The two models are:
public class UnitOfWork {
public int UnitOfWorkId { get; set; }
public string Name { get; set; }
public Human Human { get; set; }
}
public class Human {
public int HumanId { get; set; }
public string Name { get; set; }
public List<UnitOfWork> WorkDone { get; set; }
}
As you can see, model is very simple. One human has many units of work. By the way, the backend is connected to an Azure SQL database. I've seen the migration classes, and the database schema looks good to me.
The problem I have is when I want to create a unit of work referencing an existing human, using HTTP. The controller is fairly simple:
[HttpPost]
public UnitOfWork Post([FromBody] UnitOfWork unitOfWork) {
using (var db = new DatabaseContext()) {
db.UnitsOfWork.Add(unitOfWork);
var count = db.SaveChanges();
Console.WriteLine("{0} records saved to database", count);
}
return unitOfWork;
}
Again, nothing fancy here.
How can I create an unit of work, and assign it to an existing human? If I try it with an existing human, in this way
var humans = await Api.GetHumans();
var firstHuman = humans.First();
var unitOfWorkToCreate = new UnitOfWork() {
Name = TbInput.Text,
Human = firstHuman,
};
I get this error:
Cannot insert explicit value for identity column in table 'Humans' when IDENTITY_INSERT is set to OFF
I feel that setting IDENTITY_INSERT to ON will solve my problem, but this is not what I want to do. In the client, I'll select an existing human, write down a name for the unit of work, and create the latter. Is this the correct way to proceed?
EDIT: Following #Ivan Stoev answer, I've updated the UnitOfWork controller to attach unitofwork.Human. This led to
Newtonsoft.Json.JsonSerializationException: 'Unexpected end when deserializing array. Path 'human.workDone', line 1, position 86.'
Investigating - seen here - EFCore expects to create collections (like human.WorkDone) in the constructor, so I did it, and no more nulls deserializing. However, now I have a self-referencing loop:
Newtonsoft.Json.JsonSerializationException: Self referencing loop detected with type 'PlainWorkTracker.Models.UnitOfWork'. Path 'human.workDone'.
Any ideas? Thanks!
The operation in question is falling into Saving Disconnected Entities category.
Add methods marks all entities in the graph which are not currently tracked as new (Added) and then SaveChanges will try to insert them in the database.
You need a way to tell EF that unitOfWork.Human is an existing entity. The simplest way to achieve that is to Attach it (which will mark it as Unchanged, i.e. existing) to the context before calling Add:
db.Attach(unitOfWork.Human);
db.Add(unitOfWork);
// ...
Not sure about title of the question, anyways! I have my Model Class RandomStuff which has Environment as Virtual Property.
public class RandomStuff
{
//NOT SHOWN ON FORM HIDDEN JUST TO MAKE IT EASIER IN DB
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Newtonsoft.Json.JsonProperty]
public int JobId { get; set; }
[Required]
[ForeignKey("Environment")]
[Newtonsoft.Json.JsonProperty]
[DataMember(IsRequired = true)]
public int EnvironmentId { get; set; }
[Newtonsoft.Json.JsonProperty ]
public virtual Environment Environment { get; set; }
}
My Environment is as below:-
[Newtonsoft.Json.JsonObject(Newtonsoft.Json.MemberSerialization.OptIn)]
[DataContract]
public class Environment
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Newtonsoft.Json.JsonProperty]
public int Id { get; set; }
[Required]
[Newtonsoft.Json.JsonProperty]
[DataMember(IsRequired = true)]
public string Name { get; set; }
}
Now, in my controller I have :-
public HttpResponseMessage Save(RandomStuff stuff)
{
if (ModelState.IsValid)
{
//Some stuff here
db.RandomStuffs.Add(stuff);//When this excutes, it is
//inserting data in Environment table also.
db.SaveChanges();
}
}
Now, whenever i am adding stuff to my RandomStuff table, it is adding Environment also.
EDIT:
I'll try to explain with general example, which I took from ef-inserting-duplicate-parent-objects
public class Foo
{
public int FooId {get;set;}
public virtual ICollection<Bar> Bars {get;set;}
}
public class Bar
{
public int BarId {get;set;}
public strung BarName {get;set;}
}
Now, if above being my model structure. I am trying to add one Foo data to my DB.
db.Foo.Add(new Foo{
FooId=1,
Bars=new List<Bar>(){
new Bar{
BarId=2,
BarName="Something"
}
}
})//Something of this sort will be my data, which needs to be inserted
//Now this record "new Bar{BarId=2, BarName="Something"}" is already present in DB
Now, what is happening is, it is inserting data in Foo table with id=1 and it is also adding duplicate in Bars table with id 2.
Also if I use something like below, my requirement is fulfilled:
db.Entry(fooModelDataComingFromSomewhere.Bars).State = EntityState.Unchanged;
Your question still isn't quite clear, as you are now providing code snippets from another post that aren't even in the same structure as the code you are having an issue with. However, I'm still guessing that your issue is with duplicate inserts, so I'll try to provide a suggestion.
When you use the Add() Method on the Entity Framework DB Context, it marks the entire graph of connected objects as Added, even if the item already exists in the database. A common situation that you are probably seeing is that when you Add an object of type RandomStuff it is also adding a new object of type Environment, and though Environment.Name is the same, Environment.Id is Database assigned, and since Entity Framework has marked the entire graph as Added, it creates a new Environment object, with the same Name but a new Id. therefore, you have multiple Environment rows in your table with the same Name.
The solution is deceptively simple. Since you already have a Property on your RandomStuff object to hold the Foreign Key representing the Environment object, instead of adding a new Environment object to the graph, simply create your RandomStuff object and add the value of the EnvironmentId. In other words,
Instead of creating a new RandomStuff Object this way:
var RandomStuff randomStuff = new RandomStuff {
Environment = new Environment {
Name = "Existing Environment"
}
}
create it like this:
var RandomStuff randomStuff = new RandomStuff {
EnvironmentId = existingEnvironment.Id
}
I've got POCO domain entities that are persisted using Entity Framework 5. They are obtained from the DbContext using a repository pattern and are exposed to a RESTful MVC WebApi application through a UoW pattern. The POCO entities are proxies and are lazy loaded.
I am converting my entities to DTOs before sending them to the client. I am using Automapper to do this and it seems to be working fine with Automapper mapping the proxy POCOs to DTOs, keeping the navigation properties intact. I am using the following mapping for this:
Mapper.CreateMap<Client, ClientDto>();
Example of Domain/DTO objects:
[Serializable]
public class Client : IEntity
{
public int Id { get; set; }
[Required, MaxLength(100)]
public virtual string Name { get; set; }
public virtual ICollection<ClientLocation> ClientLocations { get; set; }
public virtual ICollection<ComplianceRequirement> DefaultComplianceRequirements { get; set; }
public virtual ICollection<Note> Notes { get; set; }
}
public class ClientDto : DtoBase
{
public int Id { get; set; }
[Required, MaxLength(100)]
public string Name { get; set; }
public ICollection<ClientLocation> ClientLocations { get; set; }
public ICollection<ComplianceRequirementDto> DefaultComplianceRequirements { get; set; }
public ICollection<Note> Notes { get; set; }
}
Now I am trying to update my context using DTOs sent back up from the wire. I am having specific trouble with getting the navigational properties/related entities working properly. The mapping for this I'm using is:
Mapper.CreateMap<ClientDto, Client>()
.ConstructUsing((Func<ClientDto, Client>)(c => clientUow.Get(c.Id)));
Above, clientUow.Get() refers to DbContext.Set.Find() so that I am getting the tracked proxy POCO object from EF (that contains all of the related entities also as proxies).
In my controller method I am doing the following:
var client = Mapper.Map<ClientDto, Client>(clientDto);
uow.Update(client);
client successfully is mapped, as a proxy POCO object, however it's related entities/navigational properties are replaced with a new (non-proxy) POCO entity with property values copied from the DTO.
Above, uow.Update() basically refers to a function that performs the persist logic which I have as:
_context.Entry<T>(entity).State = System.Data.EntityState.Modified;
_context.SaveChanges();
The above doesn't persist even persist the entity, let alone related ones. I've tried variations on the mappings and different ways to persist using detaching/states but always get "an object with the same key already exists in the ObjectStateManager" exceptions.
I've had a look at countless other threads and just can't get it all working with Automapper. I can grab a proxy object from the context and manually go through properties updating them from the DTO fine, however I am using Automapper to map domain -> DTO and it would be alot more elegant to use it to do the reverse, since my DTOs resemble my domain objects to a large extent.
Is there a textbook way to handle Automapper with EF, with Domain Objects/DTOs that have navigational properties that also need to be updated at the same time?
UPDATE:
var originalEntity = _entities.Find(entity.Id);
_context.Entry<T>(originalEntity).State = System.Data.EntityState.Detached;
_context.Entry<T>(entity).State = System.Data.EntityState.Modified;
The above persistence logic updates the 'root' EF proxy object in the context, however any related entities are not updated. I'm guessing that this is due to them not being mapped to EF proxy objects but rather plain domain objects. Help would be most appreciated!
UPDATE:
It seems that what I'm trying to achieve is not actually possible using the current version of EF(5) and that this is a core limitation of EF and not to do with Automapper:
Link
Link
I guess it's back to doing it manually. Hope this helps someone else who is wondering the same.
You have allready identified the problem:
The above persistence logic updates the 'root' EF proxy object in the
context, however any related entities are not updated
You are setting the modified state on the root node only. You must write code to iterate through all the objects and set the state to modified.
I implemented a pattern to handle this hierarchy model state with EF.
Every entity model class implements an interface like below, as do the view model classes:
public interface IObjectWithState
{
ObjectState ObjectState { get; set; }
}
The ObjectState enumeration is defined below:
public enum ObjectState
{
Unchanged = 0,
Added = 1,
Modified = 2,
Deleted = 3
}
For example when saving a deep hierarchy of objects using EF, I map the view model objects to their equivalent entity objects, including the ObjectState.
I then attach the root entity object to the context (and consequently all child objects):
dbContext.MyCustomEntities.Attach(rootEntityObj);
I then have an extension method on the DbContext that loops through all the items in the context's change tracker and update each entity's state (as you have done above).
public static int ApplyStateChanges(this DbContext context)
{
int count = 0;
foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.ObjectState);
if (stateInfo.ObjectState != ObjectState.Unchanged)
count++;
}
return count;
}
Then we can simply save the changes as normal:
dbContext.SaveChanges();
This way, all the hierarchy of child objects will be updated accordingly in the database.
What you want to do is get the Entity from the database first:
var centity = _context.Client.First(a=>a.Id = id)
Then you map over this and update (this is what you were looking for, it will only map things it finds in the inputDTO, and leave the other properties alone)
Mapper.Map<UpdateClientInput, Client>(inputDto, centity);
_context.update();
I'm trying to update an object in my database using entity framework 4.1 with code-first and SQL Server 2008. My object's class has a ID field of int type. The problem is that when I update my object and invoke SaveChanges on my DbContext so the database creates a copy of the datarow rather then update the one I already have. I don't want this, I just would like a clean update like in the old-fashioned SQL command UPDATE. How can I fix this?
Nation nation = (nationDB.Nations.Where(m => m.name == "France")).SingleOrDefault();
if (nation != null)
{
Nation modNation = nationDB.Nations.Find(nation.ID);
modNation.Level++;
}
nationDB.SaveChanges();
public class Nation
{
public int ID { get; set; }
public int name { get; set; }
public int level { get; set; }
}
To update it you must first read it from the database, change it, then save it.
If you create a new entity in code, even with an existing key, EF will treat it as a new entity.
I think that the ObjectStateManager has registered your object as being in the Added state. Try to set its status to Modified (by calling GetObjectStateEntry(Object) and checking/modifying the State property).