Updating child properties in Entity Framework TPH - c#

I'm new to inheritance with EF in C#/MVC, and I'm trying to update a subclass's navigation property but can't figure out how to do so. The subclass inherits from the parent class, obviously, and it has its own EF mapping specified. I have to update the DB using the parent type, otherwise nothing is pushed to the DB (no error is thrown though). Using the parent class, however, doesn't expose the child properties so I can't update them to even push to the DB.
What is the proper way to handle subclass-specific properties in EF?
This is more of a general question than one specific to my issue, but here's the code that doesn't work.
// get the existing task using the model posted from view,
// convert it to child class (GetTaskById returns parent class)
PETask task = _projectService.GetTaskById(model.Id).ConvertToET();
var proj = _projectService.GetById(model.ProjectId);
// error checking
if (ModelState.IsValid)
{
// use AutoMapper to convert the posted data to entity, passing task as
// a parameter to preserve proxies
task = model.ToEntity(task);
if (model.SOId.HasValue)
{
SalesOrder so = _salesOrderService.GetByOrderId(model.SOId.Value);
task.SalesOrderId = so.Id;
task.SalesOrder = so;
}
_projectService.Update(task);
// all information is correct, including a proxy for the SalesOrder property
// and the actual SalesOrderId, but the DB is not updated. no error is thrown.
}

Related

Entity Framework not changing EnityState on Update

I have a working Update method , which is simply changing a property value and calls SaveChanges() on db context:
public void Update(int id, string name)
{
var entity = context.Entities.Single(x => x.Id == id);
entity.Name = name;
context.SaveChanges();
}
this way changes do indeed get applied , however the EnityState remains "Unchanged". Any thoughts as to why? I am trying to avoid having to tell EF what's happening explicitly e.g. using context.Entry(entity).State = EntityState.Modified;
the problem is I am using the state in the overriden SaveChanges method:
public override int SaveChanges()
{
var context = ((IObjectContextAdapter)this).ObjectContext;
var objectStateEntries =
context.ObjectStateManager
.GetObjectStateEntries(EntityState.Added | EntityState.Modified);
...
return base.SaveChanges();
}
..when debugging, i can see that the state of my entity is Unchanged.
If you haven't disabled the change tracking of EF or proxy creation, then you shouldn't have problem with that update. EF by default tracks automatically changes when you entities meet the requirements you can find in this msdn page. If you meet those requirements and check later the type of you entity once is returned by Single extension method you will see that is a proxy class, not your real class. So, first check if you're meeting all those requirements that EF needs to track your changes automatically, you'll be fine with that code.
For either of these proxies to be created:
A custom data class must be declared with public access.
A custom data class must not be sealed
A custom data class must not be abstract .
A custom data class must have a public or protected constructor that
does not have parameters. Use a protected constructor without
parameters if you want the CreateObject method to be used to create a
proxy for the POCO entity. Calling the CreateObject method does not
guarantee the creation of the proxy: the POCO class must follow the
other requirements that are described in this topic.
The class cannot implement the IEntityWithChangeTracker or
IEntityWithRelationships interfaces because the proxy classes
implement these interfaces.
The ProxyCreationEnabled option must be set to true.
For change tracking proxies:
Each property that is mapped to a property of an entity type in the
data model must have non-sealed,
public, and virtual get and set
accessors.
A navigation property that represents the "many" end of a
relationship must return a type that implements ICollection, where T
is the type of the object at the other end of the relationship.
If you want the proxy type to be created along with your object, use
the CreateObject method on the ObjectContext when creating a new
object, instead of the new operator.

EF resolve navigation property after manual attach

I'm using Entity Framework. I've attached a POCO object representing an entity in the DB to my dbcontext using:
var entity = new MyEntity() { ID = 1, AnotherItemID = 10 };
context.Set<T>().Attach(entity);
So far so good. I can access the set and work with the entity I've added. It's added in the Unchanged state. However, it is only a POCO and not a Proxy. Therefore, when I try to access a navigation property, e.g. myEntity.AnotherItem, I just get a null back.
Does anyone know if there is a way to have EF resolve navigation properties for POCO classes attached in this way? Or of a way to cast the POCO to a proxy class?
Thanks
Update
There are two ways to solve this (of course there may be others too!). One is the Explicit Loading option in the answer below. The other way, which allows lazy loading to work, is to use the DBSet Create method rather than the POCO new keyword when creating entities to be attached. More info about that here:
EF4.3 Code-First, MVC, Lazy Loading After Attaching in POST Action
You can use Explicity Loading:
//When you want to load a reference navigation property
context.Entry(entity).Reference(p => p.AnotherItem).Load();
//When you want to load a collection navigation property
context.Entry(post).Collection(p => p.Items).Load();

Entity Framework SaveChanges two behaviours depending on how I add to DbContext

I have overridden my db.SaveChanges() so I can call my FluentValidation validators before it actually attempts to save it.
I have a validator for each entity marked with IValidatableEntity and if the entity matches it will call it and pass the objectStateEntry in.
public virtual IEnumerable<string> SaveChanges(User user)
{
List<string> validationErrors = new List<string>();
if (this.Configuration.ValidateOnSaveEnabled)
{
foreach (var entry in ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager
.GetObjectStateEntries(System.Data.Entity.EntityState.Added | System.Data.Entity.EntityState.Deleted | System.Data.Entity.EntityState.Modified | System.Data.Entity.EntityState.Unchanged)
.Where(entry => (entry.Entity is IValidatableEntity)))
{
validationErrors.AddRange(((IValidatableEntity)entry.Entity).Validate(entry));
}
}
if (!validationErrors.Any())
{ .....
The problem I have is that I get two different behaviours depending on how I add the object to the dbContext. I Guess because it only marks the aggregate root as being modified and only gives it an entry?
// Example A - Calls the Organisation Validator Only
organisation.Client.Add(client);
// Example B - Calls the Client Validator - which is correct
db.Client.Add(client);
Is there anyway to get EF automatically detect child properties have changed (Add / Modified) and call them? It kind of breaks my validation model if it doesn't, I was banking on updating the aggregate root and having EF call the necessary child validations as they should have unique entries.
Do I have to chain validators inside my Fluent Validations to catch these? I Didn't want a case of where my fluent Validator will have to check potentially hundreds of child entities. (some contain db lookups etc).
Thanks
Try to call DetectChanges at the beginning of your overridden SaveChanges method (it must be before you call GetObjectStateEntries):
this.ChangeTracker.DetectChanges();
The difference between the two lines to add a Client is that organisation.Client.Add(client) does not call any EF code directly (it's just adding an item to a collection in a POCO) while db.Client.Add(client) does and the DbSet<T>.Add method will call change detection automatically to update the entity states.
In the first case if you don't call any EF method before SaveChanges the base.SaveChanges will detect the changes as the very last place to ensure that all entity states are correct and all changes are saved. But base.SaveChanges is too late for the code in your overridden SaveChanges because it is after your evaluation of GetObjectStateEntries. At that point the entity state of the added client could still be Detached (i.e. not existing in the state manager) instead of Added. In order to fix this you have to call DetectChanges manually early enough to retrieve the final entity states in GetObjectStateEntries.
I assume organisation is a simple POCO, so the following code:
organisation.Client.Add(client);
just adds another POCO in an ICollection of POCOs. EF has no way to detect you are adding an entity to the context.
In the other hand, the following code:
db.Client.Add(client);
adds a POCO directly in an implementation of ICollection (DbCollectionEntry) that is related to Entity Framework and is in charge of what's called change tracking (among other things). This is possible thanks to dynamic proxy types generated at runtime (see https://stackoverflow.com/a/14321968/870604).
So you'll have to detect changes manually (see #Slauma answer). Another option would be to use a proxy object instead of your organisation POCO. This would be possible by calling:
var newOrganisation = dbContext.Set<Organisation>().Create();
The above code of course works for a new organisation instance.

Linq to entities lazy loading

I have the following class generated by entity framework:
public partial class Branch
{
public short Id { get; set; }
public short CompanyId { get; set; }
public string Code { get; set; }
public string Title { get; set; }
public virtual Company Ts_Companies { get; set; }
}
I have the following method which takes all of the branches out of the database:
public Branch[] LoadBranches(int companyId, int page, int limit, string search, string sort, string sortOrder)
{
using (var dbContext = new TimeShedulerEntities())
{
var _branches = (from ct in dbContext.Branches
where ct.Title.Contains(search) || ct.Code.Contains(search)
select ct).OrderBy(c => c.Title).Skip((page - 1) * limit).Take(limit);
return _branches.ToArray();
}
}
In my model designer I see that the Lazy Loading is set to true, but when I iterate over the branches, the property Ts_Companies is null. Also I get the following exception:
An exception of type 'System.ObjectDisposedException' occurred in
EntityFramework.dll but was not handled in user code
Additional information: The ObjectContext instance has been disposed
and can no longer be used for operations that require a connection.
Am I forgetting something?
You created and disposed of the context during your function since it was inside the using statement. Each entity happens to know from which context it was created so that lazy loading is possible.
When you accessed the Ts_Companies property, the entity realized that it had not yet loaded that property since it is probably a navigation property and attempted to ask its ObjectContext (TimeShedulerEntities) to load that property. However, the context had been disposed and so that it what caused that exception.
You need to modify your query as follows to 'pre-load' the Ts_Companies:
var _branches = (from ct in dbContext.Branches.Include("Ts_Companies")
where ct.Title.Contains(search) || ct.Code.Contains(search)
select ct).OrderBy(c => c.Title).Skip((page - 1) * limit).Take(limit);
It will take possibly quite a bit longer to load depending on the size of the Ts_Companies object and how many you end up bringing back at once, but the entity will stop asking its object context to load the Ts_Companies since you would have already loaded them.
A side note: I have found that creation and disposal of object context on a per-method basis causes problems when the entities are passed outside the function. If you want to create and destroy the object context in every function, you probably want to have the function return something that is not an entity. In other words, have an object that can be constructed from an entity and has the properties you need, but don't have it reference the entity. In java these are often called Data Transfer Objects (DTOs). You lose the read-write ability of entity framework, but you don't have unexpected ObjectDisposedExceptions flying all over the place.
The problem comes when you ask an entity to be associated with another (for example, adding on entity to a ICollection property of another entity) when they come from different objectcontexts. This will cause headaches for you since you would have to manually attach the objects to the same context before performing that operation. Additionally, you lose the ability to save changes to those entities without manually attaching them to a different context.
My opinion on how I would do it:
I've found it easier to either have an object containing all of these database access functions control the lifetime of the context (i.e. have your containing object be IDisposable and during disposal, destroy the context) or simply not return entities and have the datastore be read-old, write-new essentially without any modification ability.
For example, I have my object (I will call it my data access object) with a bunch of methods for getting database objects. These methods return entities. The data access object also has a SaveChanges method which simply calls the context's SaveChanges method. The data access object contains the context in a protected property and keeps it around until the data access object itself is disposed. Nobody but the data access object is allowed to touch its context. At that point, the context is disposed by manually calling 'Dispose'. The data access object could then used inside a using statement if that is your use case.
In any case, it is probably best to avoid passing entities attached to a context outside the scope in which their context exists since entity framework keeps references to that context all over the place in the individual entities
But you didn't load your Ts_Companies, use Eager Loading instead:
var _branches = dbContext.Branches
.Where(b => b.Title.Contains(search) || b.Code.Contains(search))
.Include("Ts_Companies")
.OrderBy(c => c.Title)
.Skip((page - 1) * limit)
.Take(limit);
And I came across the same issue before System.ObjectDisposedException, in my MVC project and I didn't use using blocks,instead I define my context on class level.If I need to return and use an array (in my View) I use that context.If I need to just update some information then I have used using blocks.I hope this helps.

Why can't you attach new objects to tracked objects using navigation references?

In short, why does this fail (myChildObject is not added to the database). Note that it works with ObjectContext:
using (var db = new dbEntities())
{
var myParentObject = db.parentObjects.First(); // this definitely exists in the db
// this **should** attach myChildObject to the context,
// and therefore add it to the db on .SaveChanges() - it doesn't!
var myChildObject = new childObject(){
parentObject = myParentObject
};
db.SaveChanges();
}
This MSDN Blog Post says
You can add a new entity to the context by hooking it up to another entity that is already being tracked. This could be by adding the new entity to the collection navigation property of another entity or by setting a reference navigation property of another entity to point to the new entity.
Surely the above code should work because myChildObject references myParentObject which is a tracked object. EF should be smart enough to figure out that it needs adding into the childObjects collection. It worked fine when I was using ObjectContext and now I'm finding that I need to rewrite all of my code to get it to work with dbContext.
To get it to work I have to rewrite it like this:
using (var db = new dbEntities())
{
var myParentObject = db.parentObjects.First(); // this definitely exists in the db
var myChildObject = new childObject();
myParentObject.childObjects.Add(myChildObject);
db.SaveChanges();
}
If you were using POCO entities with ObjectContext it worked indeed. But not because EF's change tracking worked differently than with DbContext but because the POCO entities generated by the EF 4 T4 templates contained "relationship fixup" methods.
Basically the property setter for the line parentObject = myParentObject wasn't only an object assignment but the setter included a call to a method that in the end exactly did what you are doing manually now, namely: myParentObject.childObjects.Add(myChildObject). At this point the rule "You can add a new entity to the context by hooking it up to another entity that is already being tracked" applies and myChildObject gets added to the context and inserted into the database.
For the T4 templates that generate POCO entities for DbContext those fixup methods have been removed because they were causing trouble in other scenarios. Especially when lazy loading is involved your reference assignment and the automatic call of myParentObject.childObjects... in the property setter would trigger lazy loading on the collection and load all childObjects first that are already stored for myParentObject before the new child is added to the collection. If those are thousands this is a huge unnecessary overhead, performance gets disastrous and the reason for a suddenly bad performance (just because you assigned a single reference property) isn't easy to detect if you are not aware of the fixup methods that run behind the scenes.
Here and here and here and here are examples about the confusion that relationship fixup methods were causing.
You could modify the T4 templates and add relationship fixup methods again - or if you are using Code-First just write them by hand in your entity classes - to get the old behaviour back. But this might be more complex than and at least as much work as changing your existing code the way you've outlined in your last code snippet - which I would certainly prefer over having those bothersome fixup methods back.
#theyetiman you're doing a little interpretation mistake of the blog text.
see:
[...]
or by setting a reference navigation property of another entity to point to the new entity.
In this part the blog said you can set a reference navigation property of a tracked object with a new entity.
like this:
[tracked entity].NavigationProperty = [new entity];
But you tring to do:
[new entity].Navigation Property = [tracked entity];
This not works. If your childObject was tracked and parentObject not you would be able to add parentObject setting it in childObject property, but the opposite is not true.

Categories