I have defined some models like this (Entity Framework Code-First):
public class A
{
public int Id { get; set; }
public int Name { get; set; }
}
public class B
{
public int Id { get; set; }
public int Name { get; set; }
public virtual A ObjectA { get; set; }
}
// model update sample code
public void UpdateModel(int id, string name)
{
B objB = GetObjBByIdUsingLINQ(id); // this function gets the object using LINQ
if (objB != null) // <-- if you do a breakpoint here and inspect objB, objB.A != null
{
objB.Name = name;
dbContext.Entry(objB).State = EntityState.Modified;
dbContext.SaveChanges(); // <-- DbEntityValidationException here because objB.A == null
}
}
When I Load a model B from the database, and I only change the Name and I update it I get the following error: The ObjectA field is required.
I think this is because ObjectA is lazy loaded. However, when I add a breakpoint after I loaded B from the database, and then I view the contents of B in the variable explorer, A will be loaded, and updating B doesn't give an error.
Any ideas how to solve this problem?
What is happening is that when you stop in a breakpoint and inspect the value of the property ObjectA inside your objB, you are explicitly loading the property.
In the GetObjBByIdUsingLINQ(id) method you should use an Include to load your property, for example:
var objB = from b in dbContext.Bs.Include("ObjectA")
where b.Id == id
select b;
Or you can load the property explicitly, instead:
dbContext.Entry(objB).Reference("ObjectA").Load();
You should note that the first option will hit the database only once. In the second option, you will hit the database twice. That should be taken into account depending on your specific case.
You can read all about working with related entities in this blog post:
Using DbContext in EF 4.1 Part 6: Loading Related Entities.
Related
Having these two entities, I fetch them, map them to viwemodels/dtos before I pass them to the UI.
I also had to ignore reference loop handling, in my startup.cs file, to map them to DTO's correctly.
public class Matter
{
public int Id { get; set; }
public ICollection<MatterExposure> Exposures { get; set; }
// other properties
}
public class MatterExposure
{
public int Id { get; set; }
public Matter Matter { get; set; }
// other properties
}
When I save the form (which includes a table of 'MatterExposure's) in the UI I pass everything back to the controller to be saved. INFO - not saving child entities 'MatterExposure' yet in below controller call and it works fine!
[HttpPut("{id}")]
public async Task<IActionResult> UpdateData(string id, MatterForClaimDetailedDto generalMatterDto)
{
var user = await _userManager.GetUserAsync(HttpContext.User);
var matter = await _autoRepo.GetMatter(id);
// fill some matter data and add a child then save and it works fine
if (await _autoRepo.SaveAll())
return NoContent();
}
public class MatterForClaimDetailedDto
{
public int Id { get; set; }
public GeneralMatterDto MatterData { get; set; }
public ICollection<MatterExposure> Exposures { get; set; }
// other properties
}
Now I want to add the update of MatterExposure entities, as I could have made changes to them in the UI. So I try to use UpdateRange like this
[HttpPut("{id}")]
public async Task<IActionResult> UpdateData(string id, MatterForClaimDetailedDto generalMatterDto)
{
var user = await _userManager.GetUserAsync(HttpContext.User);
var matter = await _autoRepo.GetMatter(id);
matter.EditedDate = DateTime.Now;
matter.FirstName = generalMatterDto.FirstName;
matter.LastName = generalMatterDto.LastName;
_autoRepo.UpdateRange<List<MatterExposure>>(generalMatterDto.Exposures.ToList());
await _autoRepo.SaveAll()
}
public void UpdateRange<T>(T entity) where T : class
{
_autoContext.UpdateRange(entity);
}
But on calling UpdateRange I get this exception message:
"The entity type 'List MatterExposure' was not found. Ensure that the entity type has been added to the model."
In my context I have this:
public DbSet<MatterExposure> MatterExposure { get; set; }
I then tried below with no luck
public DbSet<List<MatterExposure>> MatterExposure { get; set; }
I thought I would try updating each individual 'MatterExposure' entity to see if that would change anything. So I tried removing the UpdateRange call and tried with individual 'MatterExposure' entities
foreach(var exposure in generalMatterDto.Exposures) {
_autoRepo.Update<MatterExposure>(exposure);
}
// in my repo I have this with different things I tried
public void Update<T>(T entity) where T : class
{
// _autoContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
//_autoContext.Entry(entity).State = EntityState.Detached;
_autoContext.Update(entity);
// _autoContext.ChangeTracker.
}
On the first loop through each 'MatterExposure' Update call to the repo I get this exception
"The instance of entity type 'MatterExposure' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values."
After the exception above I tried I put the loop at the top of the controller method to see if the other entity stuff was interfering.
// at top of controler method before the other entity actions are performed
foreach(var exposure in generalMatterDto.Exposures) {
_autoRepo.Update<MatterExposure>(exposure);
}
And moving the for loop to the top of the controller, runs through the 1st iteration but then fails on the second giving me the same error message again
"The instance of entity type 'MatterExposure' cannot be tracked because
another instance with the same key value for {'Id'} is already being tracked.
When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values."
QUESTION - am I not updating the child entities correctly or is it something else?
When I insert my objects, they recognize they are one-to-many and the foreign key is correctly placed in the many side table.
When I retrieve my objects, they do not recognize the one-to-many on the one side table so I cannot access the ICollection of the many side objects. Specifically a Null Reference Exception is thrown when trying to access the collection/
In the explanation below, Incident is the one side and Disturbance is the many side. An Incident is associated with many Disturbances, but a Disturbance is a part of only one Incident.
Disclaimer: due to some project constraints and some modules being built on top of other modules we are using Entity Framework in our DAL and have models cross cutting Business/Data. This may factor into the issue. I'm aware this isn't ideal, but this is where we are at and I haven't seen anything that explicitly says you cannot use EF like this.
I have an Incident defined like this:
public class Incident
{
public Incident()
{
}
public Incident(List<Disturbance> sortedDisturbances)
{
StartTime = sortedDisturbances[0].StartTime;
Disturbances = new List<Disturbance>(sortedDisturbances);
}
[Key]
public int IncidentID { get; set; }
public virtual ICollection<Disturbance> Disturbances { get; set; }
[Column(TypeName="datetime2")]
public DateTime? StartTime { get; set; }
}
I had to add a parameterless constructor to deal with errors resulting from Entity Framework trying to use a parameterless constructor in certain areas.
I have a Disturbance defined like this :
public class Disturbance : IComparable<Disturbance>
{
[Key]
public int DisturbanceID { get; set; }
[Column(TypeName = "datetime2")]
public DateTime StartTime { get; set; }
[Column(TypeName = "datetime2")]
public DateTime EndTime { get; set; }
public int CompareTo(Disturbance other)
{
if (this.StartTime < other.StartTime)
return 1;
if (this.StartTime > other.StartTime)
return -1;
return 0;
}
}
I haven't read anything that said implementing an interface would break anything in Entity Framework so I did it.
This is how I add an Incident:
Business Layer:
private void MakeIncident(List<Disturbance> DisturbancesToAggregate)
{
Incident incidentToInsert = new Incident(DisturbancesToAggregate);
_iDAL.InsertIncident(incidentToInsert);
}
Data Layer:
public void InsertIncident(Incident incidentToInsert)
{
using (var context = new InternalContext())
{
context.Incident.Add(incidentToInsert);
context.SaveChanges();
}
}
The problem is that when I access my Incidents:
public IEnumerable<DomainModel.Disturbance> GetProcessedDisturbances()
{
List<DomainModel.Disturbance> processedDisturbances = new List<DomainModel.Disturbance>();
using(var context = new InternalContext())
{
foreach(var i in context.Incident)
{
foreach(var d in i.Disturbances)
{
processedDisturbances.Add(d);
}
}
}
return processedDisturbances;
}
The i.Disturbances Collection causes a Null Reference Exception. Is there something I need to call to force the context to get the Disturbances? Am I doing something blatantly wrong?
My ideas (I don't like any of them and don't want to do any of them):
1. Explicitly put the IncidentID on the Disturbance table (not even sure if this would work)
2. Force a lookup table by adding an ICollection of Incidents to Disturbances (its not a many-to-many relationship and I think this would prevent me from being able to clear all Disturbances from an Incident)
3. Explicitly define the relationship when the model is created. (I don't like the idea of having to do this, plus I think EF is half way there because it is inserting correctly.
Its happening because of lazy loading in EF. We need to Eagerly loading the data. To know more about them, please refer the link below.
https://msdn.microsoft.com/en-in/data/jj574232.aspx
Given the following code, how does EF/DbContext knows about the change made to the customer object:
class Program
{
static void Main()
{
using(var shopContext = new ShopContext())
{
var customer = shopContext.Customers.Find(7);
customer.City = "Marion";
customer.State = "Indiana";
shopContext.SaveChanges();
}
}
}
public class ShopContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string State { get; set; }
}
Thank you
When you load the entity from the context it keeps an additional data structure - let's call it entry. The entry contains two set of values - original values and current values. When you execute the SaveChanges operation EF goes through your customer entities and updates current values in the entry so that they match with the real state of your entity - this operation is called detecting changes. During SQL command generation EF will compare current and original values and build an SQL update statement to modify changed values in the database. This operation is called snapshot change tracking - EF keeps a snap shot in the entry.
There is an alternative called dynamic change tracking which will modify the current value in the entry at the same time you assign the value to your entity's property. Dynamic change tracking has specific requirements (like all of your properties in the entity must be virtual) because it must wrap your class to a dynamic proxy at runtime. This used to be the preferred way but due to some performance issues in complex scenarios, snapshot change tracking is currently supposed to be used as default.
I have a situation where I have an object that is loaded back from a form to MVC controller via an action. We do not use FormCollection, but the one that use directly the class.
[HttpPost]
public ActionResult AjaxUpdate(Customer customer) { ...
The Customer object contain an object called customer which seem to be updated but when using SaveDatabase() on the context simply doesn't work.
To make it works I had to use in the action:
myDbContext.Customers.Attach(customer)
//...Code here that set to the customer.SubObject a real object from the database so I am sure that the SubObject contain an id which is valid and the datacontext is aware of it...
myDbContext.Entry(customer).State = EntityState.Modified;
Still, I had an exception concerning the "Store update, insert, or delete statement affected an unexpected number of rows (0)" that I were able to remove by using:
Database.ObjectContext().Refresh(RefreshMode.ClientWins,customer);
So, to warp up my question, why do I have to Attach + change the state + call Refresh. Isn't there a better way to update an object that contain object that are referenced in an other table. I am using Code first Entity Framework (Poco object). Also, I do not like to use Refresh since it's hidden from my Databasecontext.
I've made a console test project with EF 4.3.1. The code is my guess what you mean with the commented line and your comments below the question (but my guess is probably wrong because the program doesn't reproduce your error):
You can copy the code into program.cs and add a reference to EF 4.3.1:
using System.Data;
using System.Data.Entity;
namespace EFUpdateTest
{
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public int SubObjectId { get; set; }
public SubObject SubObject { get; set; }
}
public class SubObject
{
public int Id { get; set; }
public string Something { get; set; }
}
public class CustomerContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<SubObject> SubObjects { get; set; }
}
class Program
{
static void Main(string[] args)
{
int customerId = 0;
int subObject1Id = 0;
int subObject2Id = 0;
using (var ctx = new CustomerContext())
{
// Create customer with subobject
var customer = new Customer { Name = "John" };
var subObject = new SubObject { Something = "SubObject 1" };
customer.SubObject = subObject;
ctx.Customers.Add(customer);
// Create a second subobject, not related to any customer
var subObject2 = new SubObject { Something = "SubObject 2" };
ctx.SubObjects.Add(subObject2);
ctx.SaveChanges();
customerId = customer.Id;
subObject1Id = subObject.Id;
subObject2Id = subObject2.Id;
}
// New context, simulate detached scenario -> MVC action
using (var ctx = new CustomerContext())
{
// Changed customer name
var customer = new Customer { Id = customerId, Name = "Jim" };
ctx.Customers.Attach(customer);
// Changed reference to another subobject
var subObject2 = ctx.SubObjects.Find(subObject2Id);
customer.SubObject = subObject2;
ctx.Entry(customer).State = EntityState.Modified;
ctx.SaveChanges();
// No exception here.
}
}
}
}
This works without exception. The question is: What is different in your code which could cause the error?
Edit
To your comment that you don't have a foreign key property SubObjectId in the customer class: If I remove the property in the example program above I can reproduce the error.
The solution is to load the original subobject from the database before you change the relationship:
// Changed customer name
var customer = new Customer { Id = customerId, Name = "Jim" };
ctx.Customers.Attach(customer);
// Load original SubObject from database
ctx.Entry(customer).Reference(c => c.SubObject).Load();
// Changed reference to another subobject
var subObject2 = ctx.SubObjects.Find(subObject2Id);
customer.SubObject = subObject2;
ctx.Entry(customer).State = EntityState.Modified;
ctx.SaveChanges();
// No exception here.
Without a foreign key property you have an Independent Association which requires that the object including all references must represent the state in the database before you change it. If you don't set the reference of SubObject in customer EF assumes that the original state in the database is that customer does not refer to any subobject. The generated SQL for the UPDATE statement contains a WHERE clause like this:
WHERE [Customers].[Id] = 1 AND [Customers].[SubObject_Id] IS NULL
If the customer has a subobject in the DB [SubObject_Id] is not NULL, the condition is not fulfilled and the UPDATE does not happen (or happens for the "unexpected number of rows 0").
The problem does not occur if you have a foreign key property (Foreign Key Association): The WHERE clause in this case is only:
WHERE [Customers].[Id] = 1
So, it doesn't matter what's the original value of SubObject and of SubObjectId. You can leave the values null and the UPDATE works nonetheless.
Hence, the alternative solution to loading the original subobject is to introduce a foreign key property in Customer:
public int SubObjectId { get; set; }
Or, in case the relationship is not required:
public int? SubObjectId { get; set; }
I'm trying to update an object that I have previously saved with EntityFramework 4.1 (CodeFirst)
The class Job has the following properties ...
public class Job
{
[key]
public int Id { get; set; }
public string Title { get; set; }
public Project Project { get; set; }
public JobType JobType { get; set; }
public string Description { get; set; }
}
The initial create works fine, but the update only commits changes to the strings..
If I change the child objects eg the JobType Property from JobTypeA to JobTypeB - the change is not committed ...
I'm not looking to commit a change to JobType - only to Job.
using (var context = new JobContext())
{
context.Jobs.Attach(job);
context.Entry(job).State = EntityState.Modified;
context.SaveChanges();
}
Having a look at SQL Profiler - the Ids are not even being sent for the Update - however they are for the initial insert!
Setting the state to Modified only updates scalar and complex properties, not your navigation properties. This only goes through Entity Framework's change detection. It means that you need to load the original from the database:
using (var context = new JobContext())
{
var originalJob = context.Jobs.Include(j => j.JobType)
.Single(j => j.Id == job.Id);
// Update scalar/complex properties
context.Entry(originalJob).CurrentValues.SetValues(job);
// Update reference
originalJob.JobType = job.JobType;
context.SaveChanges();
}
You could probably also leverage some "tricks" in your case:
using (var context = new JobContext())
{
var jobType = job.JobType;
job.JobType = null;
context.JobTypes.Attach(jobType);
context.Jobs.Attach(job);
// change detection starts from here,
// EF "thinks" now, original is JobType==null
job.JobType = jobType;
// change detection will recognize this as a change
// and send an UPDATE to the DB
context.Entry(job).State = EntityState.Modified; // for scalar/complex props
context.SaveChanges();
}
It wouldn't work though if you want to set JobType to null.
This is a typical situation which is getting much simpler if you expose foreign keys as properties in your model: With a JobTypeId in your Job entity your code would work because the FK property is scalar and setting the state to Modified will also mark this property as modified.