Add a relationship entity in generic/abstract way - c#

I'm trying to write a WCF method that will receive a detached EntityObject from the client and will be able to tell which properties and which relationships were changed for this entity comparing it with what's already in the context.
Of course that if this entity is a new entity or one of its relationships were added/deleted/modified it should also recognize that and act accordingly.
I'm already able to recognize if the entity's relationship is a new one but can't seem to be able to add it correctly. -With every approach I try I get a different exception.
Here is the method I use to update the detached object:
public static void AttachUpdated(this ObjectContext context, EntityObject objectDetached)
{
if (objectDetached.EntityState == EntityState.Detached)
{
object currentEntityInDb = null;
if (context.TryGetObjectByKey(objectDetached.EntityKey, out currentEntityInDb))
{
context.ApplyPropertyChanges(objectDetached.EntityKey.EntitySetName, objectDetached);
//Apply property changes to all referenced entities in context
context.ApplyReferencePropertyChanges((IEntityWithRelationships)objectDetached,
(IEntityWithRelationships)currentEntityInDb); //Custom extensor method
}
else
{
//The entity should be added
//?????
}
}
}
And this is a method I use to update the entity's relationships:
public static void ApplyReferencePropertyChanges(this ObjectContext context,
IEntityWithRelationships newEntity,
IEntityWithRelationships oldEntity)
{
foreach (var oldRelatedEnd in oldEntity.RelationshipManager.GetAllRelatedEnds())
{
var oldRef = oldRelatedEnd as EntityReference;
if (oldRef != null)
{
// this related end is a reference not a collection
var newRef = newEntity.RelationshipManager.GetRelatedEnd(oldRef.RelationshipName, oldRef.TargetRoleName) as EntityReference;
if (newRef.EntityKey != null)
{
oldRef.EntityKey = newRef.EntityKey;
}
else
{
//When oldRed is a 1:Many relationship
//newRef is an EntityReference<TEntity> object
EntityObject entity = newRef.GetType().GetProperty("Value").GetValue(newRef, null) as EntityObject;
oldRef.EntityKey = entity.EntityKey;
}
}
else
{
IRelatedEnd newRelatedEnd = newEntity.RelationshipManager.GetRelatedEnd(oldRelatedEnd.RelationshipName, oldRelatedEnd.TargetRoleName);
foreach (IEntityWithRelationships e in newRelatedEnd)
{
if (!oldRelatedEnd.Contains((e as IEntityWithKey).EntityKey))
{
//this is a new relation and it needs to be added.
//???????
}
else
{
//Find out if relation was modified - and update it if needed
//????????
}
}
IEnumerable entities = oldRelatedEnd as IEnumerable;
}
}
}
How should it be implemented?
Please help :(

Where is your ObjectContext coming from? (I am assuming that this is your Entity Framework database reference)
There may be two problems here:
Firstly, I do not think that ObjectContext is serializable, so if you are sending it to the client and then sending it back, you will get an error.
Secondly, If you are keeping ObjectConext on the server, the server objects are by default per call, not per session, therefore you will be trying to associate your Entity with a new ObjectContext.
In our projects we map Entity framework Objects to data transfer objects in order to send then over WCF. What you are trying to do may be easier (possible?) in the next version of Entity Framework.

I'm not sure what you want to achieve - if you want to save the changes in the server side you can use ADO .Net Data Services. Is that the case?

See Perseus:
Perseus is a small project designed to
explore ways for exchanging graphs of
Entity Framework entities over WCF web
services. The key piece of the project
is EntityBag which stores a graph
of entities along with change tracking
information. Here's hoping no one will
use this to store & transport
something as nasty as Medusa's head.
;-)

Related

The instance of the entity type cannot be tracked because another instance with the keyvalue is being tracked

I am basically trying to implement CRUD using EntityFrameWork core and .Net core 3.1. I have an issue with my update operation where I am not able update the context with the modified value.
I am using postman to initiate the request.
As you can see in the code below, I am trying to check if that customer exist and if it does pass the modified object to the context.
Function code
[FunctionName("EditCustomer")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous,"post", Route = "update-customer")] HttpRequest req)
{
var customer = JsonConvert.DeserializeObject<CustomerViewModel>(new StreamReader(req.Body).ReadToEnd());
await _repo.UpdateCustomer(customer);
return new OkResult();
}
Repository method
public async Task UpdateCustomer(CustomerViewModel customerViewModel)
{
if (customerViewModel.CustomerId != null)
{
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
if (customer == null)
{
throw new Exception("customer not found");
}
else
{
_context.Customers.Update(_mapper.Map<Customers>(customerViewModel));
await _context.SaveChangesAsync();
}
}
}
Mapping
public class CustomerManagerProfile : Profile
{
public CustomerManagerProfile()
{
CreateMap<CustomerDetails, CustomerDetailsViewModel>().ReverseMap();
CreateMap<CustomerOrders, CustomerOrdersViewModel>().ReverseMap();
CreateMap<CustomerOrderDetails, OrderDetailsViewModel>().ReverseMap();
CreateMap<Customers, CustomerViewModel>().ReverseMap();
}
}
Solution
public async Task UpdateCustomer(CustomerViewModel customerViewModel)
{
if (customerViewModel.CustomerId != null)
{
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
if (customer == null)
{
throw new Exception("customer not found");
}
else
{
var customerModel = _mapper.Map<Customers>(customerViewModel);
_context.Entry<Customers>(customer).State = EntityState.Detached;
_context.Entry<Customers>(customerModel).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
}
}
Entity Framework tracks your entities for you. For simplicity's sake, think of it like keeping a dictionary (for every table) where the dictionary key is equal to your entity's PK.
The issue is that you can't add two items of the same key in a dictionary, and the same logic applies to EF's change tracker.
Let's look at your repository:
var customer = _context
.Customers
.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
.FirstOrDefault();
The fetched customer is retrieved from the database and the change tracker puts it in his dictionary.
var mappedCustomer = _mapper.Map<Customers>(customerViewModel);
_context.Customers.Update();
I split your code in two steps for the sake of my explanation.
It's important to realize that EF can only save changes to tracked objects. So when you call Update, EF executes the following check:
Is this the same (reference-equal) object as one I have I my change tracker?
If yes, then it's already in my change tracker.
If not, then add this object to my change tracker.
In your case, the mappedCustomer is a different object than customer, and therefore EF tries to add mappedCustomer to the change tracker. Since customer is already in there, and customer and mappedCustomer have the same PK value, this creates a conflict.
The exception you see is the outcome of that conflict.
Since you don't need to actually track your original customer object (since EF doesn't do anything with it after fetching it), the shortest solution is to tell EF to not track customer:
var customer = _context
.Customers
.AsNoTracking()
.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
.FirstOrDefault();
Since customer is now not put into the change tracker, mappedCustomer won't cause a conflict anymore.
However, you don't actually need to fetch this customer at all. You're only interested in knowing whether it exists. So instead of letting EF fetch the entire customer object, we can do this:
bool customerExists = _context
.Customers
.Any(c => c.CustomerId.Equals(customerViewModel.CustomerId));
This also solves the issue since you never fetch the original customer, so it never gets tracked. It also saves you a bit of bandwidth in the process. It's admittedly negligible by itself, but if you repeat this improvement across your codebase, it may become more significent.
The most simple adjustment that you could make would be to avoid tracking your Customers on retrieval like this:
var customer = _context
.Customers
.AsNoTracking() // This method tells EF not to track results of the query.
.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
.FirstOrDefault();
It's not entirely clear from the code, but my guess is your mapper returns a new instance of Customer with the same ID, which confuses EF. If you would instead modify that same instance, your call to .Update() should work as well:
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
customer.Name = "UpdatedName"; // An example.
_context.Customers.Update(customer);
await _context.SaveChangesAsync();
As a matter of fact, if you track your Customer you don't even need to explicitly call .Update() method, the purpose of tracking is to be aware of what changes were made to the entities and should be saved to the database. Therefore this will also work:
// Customer is being tracked by default.
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
customer.Name = "UpdatedName"; // An example.
await _context.SaveChangesAsync();
EDIT:
The solution you yourself provide begins by tracking the results of your query (the Customer) instance, then stops tracking it (a.k.a. gets detached) before writing to database and instead starts tracking the instance that represents the updated Customer and also marks it as modified. Obviously that works as well, but is just a less efficient and elegant way of doing so.
As a matter of fact if you use this bizarre approach, I don't see the reason for fetching your Customer at all. Surely you could just:
if (!(await _context.Customers.AnyAsync(c => c.CustomerId == customerViewModel.CustomerId)))
{
throw new Exception("customer not found");
}
var customerModel = _mapper.Map<Customers>(customerViewModel);
_context.Customers.Update(customerModel);
await _context.SaveChangesAsync();
You use AutoMapper wrong way. It is not created to map from View model or DTO to Entity classes. It makes many problems and you are facing with only one of them now.
If you have more complex bussiness logic in you app (not just udpate all fields), it will be horrible to manage, test and debug what actually is happening in your code. You should write you own logic with some bussiness validation in case when you want to make some other update than CRUD.
If I were you I would create UpdateFields method in Customer class which would update them and finally call SaveChanges. It depends on whether you use anemic entity (anti)pattern or not. If you do not want your entity class to have any method you can create just method which manually map you VM do entity with some domain validation

Why doesn't entity framework concretize my entity's one to many relationship?

I am using a code-first approach with Entity Framework, and a repository pattern to get entities back from my database. In my data model, each OverallEvent has many EventInConcept children. I want my GetEvents method to return an IList of OverallEvents, and I want the children of the aforementioned relationship to be concretized such that they can be accessed outside my DbContext (which AssessmentSystemContext is). This is the code I currently have:
public IList<OverallEvent> GetEvents() {
using (var context = new AssessmentSystemContext()) {
return context.OverallEvents
.Select(evnt => new {
OverallEvent = evnt,
// evnt.EventsInConcept is a public virtual ICollection<EventInConcept>
ConcreteEventsInConcept = evnt.EventsInConcept
})
.AsEnumerable()
.Select(evntData => {
evntData.OverallEvent.EventsInConcept = evntData.ConcreteEventsInConcept.ToList();
// foreach (var eic in evntData.OverallEvent.EventsInConcept) {
// eic.Name = eic.Name;
// }
return evntData.OverallEvent;
})
.ToList();
}
}
It gives me back a list of OverallEvent entities, which is fine, but the trouble is that if I try to access the child relationship EventsInConcept, I get an error. For example:
EventRepository repoEvent = new EventRepository();
var gotEvents = repoEvent.GetEvents();
var firstEventInConcept = gotEvents[0].EventsInConcept.FirstOrDefault();
... gives me the error "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection."
I understood from the answer to an earlier question that if I projected EventsInConcept into a wrapper object, then explicitly set it in a later .Select call (ie. evntData.OverallEvent.EventsInConcept = evntData.ConcreteEventsInConcept.ToList();), it would concretize this one:many relationship and I would be able to access EventsInConcept outside of the DbContext, but it isn't working here. Note that if I uncomment the foreach loop, it starts working, so to get it to work I have to explicitly set a property on every single entry of EventsInConcept. I don't really want to have to do this (I'm picking an arbitrary property, .Name, which feels wrong anyway). Is there a better way?
Disable lazy loading for this query. It is of no use in that situation and when you dispose the context after the entities have been retrieved:
public IList<OverallEvent> GetEvents() {
using (var context = new AssessmentSystemContext()) {
context.Configuration.LazyLoadingEnabled = false;
return ...
}
}
It might be possible that EF doesn't recognize that the collection has been loaded when you use a projection (instead of eager or explicit loading) and triggers lazy loading as soon as you access the collection.

How to access related objects after using statement has finished in Entity Framework?

I use the EF 3.5 in VS 2010. I have a method which returns a struct. In the struct there is an object armatuur. When the struct is returned i want to access the related objects from the armatuur instance.
However
the method returning the struct:
public LampPostDetail getLamppostInfo(int id)
{
LampPostDetail lpd;
lpd.xPos = 0;
lpd.ypos = 0;
lpd.armatuur = new Armatuur();
//get the info from object
using (var db = new OvisionDBEntities())
{
var objects = from o in db.Objects
where o.ObjectId == id
select o;
foreach (OVSL.Data.Object o in objects)
{
lpd.xPos = o.XCoordinatie;
lpd.ypos = o.YCoordinatie;
lpd.armatuur = o.Armatuur; //which is a table in my db
}
return lpd;
}
}
struct:
public struct LampPostDetail
{
#region [ Data Members (14)]
//lamppost info
public double? xPos;
public double? ypos;
//a lamppost can have several armaturen
public OVSL.Data.Armatuur armatuur; //is a table in my db
#endregion [ Data Members ]
}
when doing this in my client:
LampPostDetail lpd = client.getLamppostInfo(id);
string brand = lpd.armatuur.producer.name; //producer is related object of armatuur
I get a ObjectDisposedException. I understand that this happens because the LampPostDetail object is disposed after the using block is finished. But how do i get this to work? Retrieving all information I need (like brand name e.g.) before I return it to the client is not not an option.
The only thing that gets disposed here is the OvisionDBEntities context. After that, no lazy loading is possible. How to deal with that? In fact your question is: what can you do to feed a client with all data that are potentially required for user actions at any time? I see three or four options:
The standard way to enable access to navigation properties of entities after context disposal is calling Include: from o in db.Objects.Include("Armatuur.Producer")... But that's clearly not an option for you.
Let the context live and rely on lazy loading to fetch data on demand. This may be an option for you. But long-lived contexts may cause problems like gradually declining performance as the internal change track record grows, and stale cached data giving rise to refresh/reload statements scattered all over the place.
In stead of navigation properties/lazy loading fetch data on demand from a service/repository layer that uses context instances per call. I think this option could work well for you.
More a functional than a technical option: design use cases that can do with less data (so that Include may suffice after all). No one can take in a grid with thousands of rows and tens of columns. Well-designed user interaction can drastically reduce the amount of data that is pumped into a client (and I'm only at the beginning of getting this).
Its not you LampPostDetail that is getting disposed, it is the Armatuur object you retrieved from the database that it references, or an object that Armatuur is referencing.
I can see two options to getting around this. The first is to make the Entity context an optional parameter to your getLamppostInfo info method. Since you are using 3.5 you will have to do an overload to keep the orignal functionality:
public LampPostDetail getLamppostInfo(int id,OvisionDBEntities context)
{
...
try
{
OvisionDBEntities db;
if (context == null)
db = new OvisionDBEntities();
else
db = context;
...
}
finally
{
if (context == null && db != null)
db.Dispose() // or close maybe
}
retun lpd;
}
// Overloaded function to keep orignal functionality (C# 3.5 does not have
// optional parameters)
public LampPostDetail getLamppostInfo(int id)
{
return LampPostDetail(id,null)
}
Now you can call it as:
using (var db = new OvisionDBEntities())
{
LampPostDetail lpd = client.getLamppostInfo(id,db);
string brand = lpd.armatuur.producer.name;
}
And your objects will still exist when you try to reference them.
The other option is to detach your referenced objects from the entity context, before disposing of it.
db.Detach(o.Armatuur);
However, I don't believe that detaches any objects references by that object. So you would have to interate the reference trees and detach thoes objects as well.

Entity Framework - "The relationship between the two objects cannot be defined" error, but I think I'm using the same context

In my ViewModel I have some code like that:
public class OrderViewModel
{
private UserOrder order;
private DeliveryCentre deliveryCentre;
// This is my EF Container
private CatalogueContainer catalogue = new CatalogueContainer();
// do some stuff...
public void Save()
{
if (order == null)
{
order = catalogue.UserOrders.CreateObject();
}
// do some other stuff...
if ((deliveryCentre == null)
|| (deliveryCentre.Id != deliveryCentreId))
{
deliveryCentre = catalogue.DeliveryCentres.First(centre => centre.Id == deliveryCentreId);
//Causes a context error, not sure why...
order.DeliveryCentre= deliveryCentre;
}
catalogue.SaveChanges();
}
So when the delivery centre is new and the order is new, I am hit by the old "The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects" error, which seems a trifle unfair to me - I just can't figure out what I need to do to make them belong more to the same object context. I assume this is due to some fundamental misunderstanding of the behaviour of Entity Framework.
You are not disposing your context. It may be possible that one of the entities order or deliveryCentre is attached to an old context which still holds references to the entities. You can create and dispose your context with an using statement inside of the Save method instead to using it as a member variable:
public void Save()
{
using (var catalogue = new CatalogueContainer())
{
// your code...
}
}
And remove the private catalogue member.
The solution turned out to only be indirectly related to the error message- #Slauma asked about the //do stuff... placeholders and when I commented those out the error disappeared.
It turned out that there was another relationship there, where I was creating the object as this.Item = new Item() rather than using this.Item = catalogue.Items.CreateObject() so it was being created out of context and when it was added to the order, although the order itself was created from the local context, when the Item was added to it this was somehow dirtying up the context but for some reason this only showed up as a problem when I added the next related object.

How to save combined (new+modified) detached entities in Entity Framework?

What is the proper and fast way to save combined new and modified detached POCO entities?
I was thinking about these methods:
private void Method_2(IList<Entity> entities) //detached entities
{
//This method is using SELECT to check if entity exist
using (var context = new ModelContainer())
{
foreach (Entity entity in entities)
{
var foundEntity = context.CreateObjectSet<Entity>().SingleOrDefault(t => t.Id == entity.Id);
context.Detach(foundEntity); //Remove it from ObjectStateManager
if (foundEntity != null)//It is modified entity
{
context.AttachTo("EntitySet", entity); //Attach our entity
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified); //We know it exists
}
else//It is new entity
{
context.CreateObjectSet<Entity>().AddObject(entity);
}
}
context.SaveChanges();
}
}
private void Method_1(IList<Entity> entities) //detached entities
{
//This method doesn't select anything from DB, but i have ta call Savechanges after each object
using (var context = new ModelContainer())
{
foreach (Entity entity in entities)
{
try
{
context.AttachTo("EntitySet", entity);
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
context.SaveChanges();
}
catch (OptimisticConcurrencyException)
{
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Added);
context.SaveChanges();
}
}
}
}
When you are working in detached environment you have to know which entity was added and which is modified - it is your responsibility to keep this information and provide it to ObjectContext.
Well i agree with this statement if you found yourself in situation when you need to use EF code like this in EF definitely something is wrong with you decision. I have chosen wrong tool for this job.
When you are working in detached environment you have to know which entity was added and which is modified - it is your responsibility to keep this information and provide it to ObjectContext.
The very easy way is:
foreach (var entity in entities)
{
if (entity.Id == 0) // 0 = default value: means new entity
{
// Add object
}
else
{
// Attach object and set state to modified
}
}
The example requires that you have some db auto-generated primary key (Id).
Your Method 2 is possible with some modifications. It is not needed to detach entity when you load it. Instead use ApplyCurrentValues. The approach with loading entity first is very usefull when you decide to work with object graphs instead of single entity. But in the case of object graph you have to do synchronization manually. ApplyCurrentValues works only for scalar (non navigation) properties. You can try to futher optimize your method to load needed enitites in single roundtrip to database instead of loading entities one by one.
Your Method 1 is terrible solution. Using exceptions raised on database server to control program flow is bad approach.
I agree with #Ladislav - Method_1 is a bad approach. Let the database raise exceptions which are caught by EF - don't try and swallow these exceptions yourself.
Your on the right track with Method 1.
Here is how i do it - as i also have a detached context (POCO's, no change tracking, ASP.NET MVC).
BLL Interface: (note i have TPT in my model, hence generics. "Post" is abstract)
void Add(Post post);
void Update<TPost>(TPost post) where TPost : Post, new();
The new() constraint is crucial - you'll see why shortly.
I won't show how i do "Add", because it's simple as you think - AddObject(entity);
The "Update" is the tricky part:
public class GenericRepository<T> : IRepository<T> where T : class
{
public void Update<T2>(T2 entity) where T2: class, new()
{
var stub = new T2(); // create stub, now you see why we need new() constraint
object entityKey = null;
// ..snip code to get entity key via attribute on all domain entities
// once we have key, set on stub.
// check if entity is already attached..
ObjectStateEntry entry;
bool attach;
if (CurrentContext.ObjectStateManager.TryGetObjectStateEntry(CurrentContext.CreateEntityKey(CurrentContext.GetEntityName<T>(), stub), out entry))
{
// Re-attach if necessary.
attach = entry.State == EntityState.Detached;
}
else
{
// Attach for first time.
attach = true;
}
if (attach)
CurrentEntitySet.Attach(stub as T);
// Update Model. (override stub values attached to graph)
CurrentContext.ApplyCurrentValues(CurrentContext.GetEntityName<T>(), entity);
}
}
And that works for me.
As for the entity key, i have used attributes on my domain classes. An alternative (which i'm about to move to), is have all my domain entities implement an interface, which specifies that all domain entities must have a property called "EntityKey". Then i'll use that interface on my constraints. Basically, i needed a dynamic way to create stub entities in a generic repository.
I don't personally like the idea of "checking the ID, if its > 0 then it's an update". Because i'm working with ASP.NET MVC, if i (or another developer) forgets to bind the ID to the View, it won't be passed through, so even though it may be an update, because the ID == 0 it will be added.
I like to be explicit about the operations. This way, i can perform Add/Update seperate validation logic.
Perhaps take a look at Self Tracking POCO entities. IMHO they are perfect for any scenario that requires the entity to be separated from the context. It takes care of all the plumbing code for you.

Categories