Update / Delete Child Collection in Entity Framework - entity-framework

I am running Entity Framework Core with a Blazor WASM Hosted Solution. I have a model that is like follows:
public class Parent
{
public int ParentID { get; set; }
public List<Child> Children { get; set; }
}
The Client Side Blazor is getting the Parent object via an API call, the List is being modified to add/remove items from its list, then being sent back to the API for an Update/Save.
public async Task UpdateAsync(int id, Parent Parent)
{
try
{
if (!await ExistsAsync(id))
return;
_dataContext.Entry(Parent).State = EntityState.Modified;
await _dataContext.SaveChangesAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in UpdateAsync()");
throw;
}
}
The Code above does not update the collection of the Child so the next time the Get is called its as it was before the update. After doing some research I can see that EF cannot track the changes if the object is being serialized and deserialized via an API. I tried the following
public async Task UpdateAsync(int id, Parent Parent)
{
try
{
if (!await ExistsAsync(id))
return;
//_dataContext.Entry(Parent).State = EntityState.Modified;
_dataContext.Parents.Update(Parent);
await _dataContext.SaveChangesAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in UpdateAsync()");
throw;
}
}
With this change I am able to Add new items to the collection and it saves correctly, however removing items from the collection do not work.
I have found this answer which Manually tracks the addition and removal of child properties but this was over 6yrs ago. I was hoping that EF had changed since then to possibly support this.
How to add/update child entities when updating a parent entity in EF

Related

Entity framework core entry state does not change in relational entity

I am using entity framework core. I am adding a Widget using Dasboard object. And saving database. Following code was working before when Widget and Dashboard id type was string. I changed type of id to Guid (But I don't know if this is the cause of the problem). And now I am getting an while saving changes error.
Database operation expected to affect 1 row(s) but actually affected 0. Data may have been modified or deleted since entities were loaded
row(s)
public async Task<Widget> AddWidget(WidgetCreateCommand command)
{
var dashboard = await _context.Dashboards.FirstOrDefaultAsync(s => s.Id == command.DashboardId);
var widget = new Widget(command.DashboardId, command.WidgetContentId, command.GridsterSettings);
dashboard.AddWidget(widget);
await _context.SaveChangesAsync(); // error is in here.
return widget;
}
Entities are relational:
public class Dashboard : Entity<Guid>
{
....
....
public ICollection<Widget> Widgets { get; private set; }
public void AddWidget(Widget widget)
{
if (Widgets.All(u => u.Id != widget.Id))
Widgets.Add(widget);
}
}
public class Widget : Entity<Guid>
{
...
public Dashboard Dashboard { get; private set; }
}
I have looked at breakpoint, and see the _context.Entry(widget).State is Detached. But it should be Added.
Why _context.Entry(widget).State does not set as EntityState.Added automatically?
If I set _context.Entry(widget).State = EntityState.Added; as manually, it works. But I was not doing this before.

Problem: Page is crashing and consuming a lot of memory after that (Blazor + EF 3.1.1)

I recently started to work with Blazor and Entity Framework and ran into a problem I don't know how to solve properly:
I use Blazor server + webassembly and code-first approach for DB.
I have 2 entities with relation one to many and want to load child object with parent included. If I get all child objects for a table, everything is fine; however, if I get only one child by using Where method with Include, app starts a recursion. It gets parent, then all its children, all their parents and so on, until I get "out of memory" exception in client app.
I turned on Newtonsoft.Json.ReferenceLoopHandling.Ignore, and it helped me for table view, but it doesn't work for loading only one child.
Parent object:
public partial class Project
{
public long ProjectId { get; set; }
public string Name { get; set; }
public ICollection<Environment> Environments { get; set; }
}
Parent access layer:
public IEnumerable<Project> GetAllProjects()
{
try
{
return _context.Projects.ToList();
}
catch
{
return new List<Project>();
}
}
public Project GetProjectData(long id)
{
try
{
Project project = _context.Projects.Find(id);
return project;
}
catch { throw; }
}
Child object:
public partial class Environment
{
public long EnvironmentId { get; set; }
public string Name { get; set; }
public long ProjectId { get; set; }
public Project Project { get; set; }
}
Child access layer:
public IEnumerable<Environment> GetAllEnvironments() // this one works fine
{
try
{
return _context.Environments
.Include(e => e.Project)
.ToList();
}
catch
{
return new List<Environment>();
}
}
public Environment GetEnvironmentData(long id) // this one starts endless recursion
{
try
{
Environment env = _context.Environments
.Where(e => e.EnvironmentId == id)
.Include(e => e.Project)
.FirstOrDefault();
return env;
}
catch { throw; }
}
For now I'm loading parent object manually, but it would be good to figure out how to do it automatically with Include.
Think about what you want the JSON to look like, and you'll see you need to break the cycle in your JSON serializer by supressing the serialization of one of the navigation properties, probably Environment.Project.
Looks like the solution was really simple, I didn't check for null for 'Project' property of 'Environment' object. After I added this check to the page problem is gone.
I'm still not sure why the page was consuming a lot of memory after it had encountered the 'Object reference not set to an instance of an object' error.
#if (env == null || env.Project == null)
{
<strong>Loading...</strong>
}
else
{
// page markup
}

Attaching Entity to context fails because it already exist

I use the Unity of Work and Generic Repository of CodeCamper.
to update an entity, the generic repo has:
public virtual void Update(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State == EntityState.Detached)
{
DbSet.Attach(entity);
}
dbEntityEntry.State = EntityState.Modified;
}
the web api method:
public HttpResponseMessage Put(MyEditModel editModel)
{
var model = editModel.MapToMyEntity();
_myManager.Update(model);
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
The Update method:
public void Update(MyEntity model)
{
Uow.MyEntities.Update(model);
Uow.Commit();
}
In the Unityof Work:
IRepository<MyEntity> MyEntities { get; }
When updating an entity I get the following error:
Additional information: Attaching an entity of type 'X' failed because another entity of the same type already has the same primary key value.
This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values.
This may be because some entities are new and have not yet received database-generated key values.
In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
The update works fine, when it is the first method you call of the repository.
(I created an entity with an id already in the DB and called the Update.)
The update doesn't work when you do a get of the entity before you update it.
(For example, I get an entity X, convert it to a DTO, then change some values in the UI,
then call a web api that creates an entity X with the new values and
call the Update of the repository.)
Any ideas to avoid this?
When you have a CRUD app, you always call the get before the update.
I'm using my own attach method:
public void Attach<E>(ref E entity)
{
if (entity == null)
{
return;
}
try
{
ObjectStateEntry entry;
bool attach = false;
if (ObjectStateManager.TryGetObjectStateEntry(CreateEntityKey(entitySetName, entity), out entry))
{
attach = entry.State == EntityState.Detached;
E existingEntityInCache = (E)entry.Entity;
if (!existingEntityInCache.Equals(entity))
{
existingEntityInCache.SetAllPropertiesFromEntity(entity);
}
entity = existingEntityInCache;
}
else
{
attach = true;
}
if (attach)
objectContext.AttachTo(entitySetName, entity);
}
catch (Exception ex)
{
throw new Exception("...");
}
}
I had the same issue. The problem was in mixed contexts. When you read entity from DB in context1. Then if you can update this entity with contex2 (other instance of the same context with own entity cache). This may throw an exception.
Pls check for references too:
by context1:
read entity1 with referenced entity2 from DB
by context2:
read entity2 from DB. Then update entity1 (with referenced entity2 from context1).
When you try attach entity1 with referenced entity2 to context2, this throw exception because entity2 already exists in context2.
The solution is use only one context for this operation.

Generic repository to update an entire aggregate

I am using the repository pattern to provide access to and saving of my aggregates.
The problem is the updating of aggregates which consist of a relationship of entities.
For example, take the Order and OrderItem relationship. The aggregate root is Order which manages its own OrderItem collection. An OrderRepository would thus be responsible for updating the whole aggregate (there would be no OrderItemRepository).
Data persistence is handled using Entity Framework 6.
Update repository method (DbContext.SaveChanges() occurs elsewhere):
public void Update(TDataEntity item)
{
var entry = context.Entry<TDataEntity>(item);
if (entry.State == EntityState.Detached)
{
var set = context.Set<TDataEntity>();
TDataEntity attachedEntity = set.Local.SingleOrDefault(e => e.Id.Equals(item.Id));
if (attachedEntity != null)
{
// If the identity is already attached, rather set the state values
var attachedEntry = context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(item);
}
else
{
entry.State = EntityState.Modified;
}
}
}
In my above example, only the Order entity will be updated, not its associated OrderItem collection.
Would I have to attach all the OrderItem entities? How could I do this generically?
Julie Lerman gives a nice way to deal with how to update an entire aggregate in her book Programming Entity Framework: DbContext.
As she writes:
When a disconnected entity graph arrives on the server side, the
server will not know the state of the entities. You need to provide a
way for the state to be discovered so that the context can be made
aware of each entity’s state.
This technique is called painting the state.
There are mainly two ways to do that:
Iterate through the graph using your knowledge of the model and set the state for each entity
Build a generic approach to track state
The second option is really nice and consists in creating an interface that every entity in your model will implement. Julie uses an IObjectWithState interface that tells the current state of the entity:
public interface IObjectWithState
{
State State { get; set; }
}
public enum State
{
Added,
Unchanged,
Modified,
Deleted
}
First thing you have to do is to automatically set the state to Unchanged for every entity retrieved from the DB, by adding a constructor in your Context class that hooks up an event:
public YourContext()
{
((IObjectContextAdapter)this).ObjectContext
.ObjectMaterialized += (sender, args) =>
{
var entity = args.Entity as IObjectWithState;
if (entity != null)
{
entity.State = State.Unchanged;
}
};
}
Then change your Order and OrderItem classes to implement the IObjectWithState interface and call this ApplyChanges method accepting the root entity as parameter:
private static void ApplyChanges<TEntity>(TEntity root)
where TEntity : class, IObjectWithState
{
using (var context = new YourContext())
{
context.Set<TEntity>().Add(root);
CheckForEntitiesWithoutStateInterface(context);
foreach (var entry in context.ChangeTracker
.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
}
context.SaveChanges();
}
}
private static void CheckForEntitiesWithoutStateInterface(YourContext context)
{
var entitiesWithoutState =
from e in context.ChangeTracker.Entries()
where !(e.Entity is IObjectWithState)
select e;
if (entitiesWithoutState.Any())
{
throw new NotSupportedException("All entities must implement IObjectWithState");
}
}
Last but not least, do not forget to set the right state of your graph entities before calling ApplyChanges ;-) (You could even mix Modified and Deleted states within the same graph.)
Julie proposes to go even further in her book:
you may find yourself wanting to be more granular with the way
modified properties are tracked. Rather than marking the entire entity
as modified, you might want only the properties that have actually
changed to be marked as modified.
In addition to marking an entity as modified, the client is also
responsible for recording which properties have been modified. One way
to do this would be to add a list of modified property names to the
state tracking interface.
But as my answer is already too long, go read her book if you want to know more ;-)
My opinionated (DDD specific) answer would be:
Cut off the EF entities at the data layer.
Ensure your data layer only returns domain entities (not EF entities).
Forget about the lazy-loading and IQueryable() goodness (read: nightmare) of EF.
Consider using a document database.
Don't use generic repositories.
The only way I've found to do what you ask in EF is to first delete or deactivate all order items in the database that are a child of the order, then add or reactivate all order items in the database that are now part of your newly updated order.
So you have done well on update method for your aggregate root, look at this domain model:
public class ProductCategory : EntityBase<Guid>
{
public virtual string Name { get; set; }
}
public class Product : EntityBase<Guid>, IAggregateRoot
{
private readonly IList<ProductCategory> _productCategories = new List<ProductCategory>();
public void AddProductCategory(ProductCategory productCategory)
{
_productCategories.Add(productCategory);
}
}
it was just a product which has a product category, I've just created the ProductRepository as my aggregateroot is product(not product category) but I want to add the product category when I create or update the product in service layer:
public CreateProductResponse CreateProduct(CreateProductRequest request)
{
var response = new CreateProductResponse();
try
{
var productModel = request.ProductViewModel.ConvertToProductModel();
Product product=new Product();
product.AddProductCategory(productModel.ProductCategory);
_productRepository.Add(productModel);
_unitOfWork.Commit();
}
catch (Exception exception)
{
response.Success = false;
}
return response;
}
I just wanted to show you how to create domain methods for entities in domain and use it in service or application layer. as you can see the code below adds the ProductCategory category via productRepository in database:
product.AddProductCategory(productModel.ProductCategory);
now for updating the same entity you can ask for ProductRepository and fetch the entity and make changes on it.
note that for retrieving entity and value object of and aggregate separately you can write query service or readOnlyRepository:
public class BlogTagReadOnlyRepository : ReadOnlyRepository<BlogTag, string>, IBlogTagReadOnlyRepository
{
public IEnumerable<BlogTag> GetAllBlogTagsQuery(string tagName)
{
throw new NotImplementedException();
}
}
hope it helps

Exception when adding more than one entity at once

Whenever I do something like the following:
public class MyDto
{
[Key]
public int ID { get; set; }
public int ParentID { get; set; }
public String Name { get; set; }
}
MyDataContext dataContext = new MyDataContext();
MyParentDto myParentDto; // <-- Existing parent DTO querried from the server. Has a relation to MyDto on MyDto.ParentID == MyParentDto.ID.
List<MyDto> myDtos = new List<MyDto>();
myDtos.Add(new MyDto
{
Name = "First MyDto!"
});
myDtos.Add(new MyDto
{
Name = "Second MyDto!"
});
// Some time later.
foreach (var myDto in myDtos)
{
myDto.ParentID = myParentDto.ID;
dataContext.MyDtos.Add(myDto);
}
dataContext.SubmitChanges(OnMyCallback)
I get the following vague exception, but my data submits just fine:
System.ServiceModel.DomainServices.Client.DomainOperationException: Submit operation failed. An entity with the same identity already exists in this EntitySet.
The stack trace ends with:
System.ServiceModel.DomainServices.Client.EntitySet.AddToCache(Entity entity)
System.ServiceModel.DomainServices.Client.Entity.AcceptChanges()
Both MyDto instances are set to Detached before they are added to dataContext and New afterwards. If I reduce the number of added MyDto instances to one, I get no error. If I call SubmitChanges in between the two adds. Again, both of the MyDto instances are added to the database just fine, but the client crashes with the Exception. What is going on? Thanks.
Edits:
// On the server
[Insert]
public void InsertMyDto(MyDto a_myDto) // <- Yes I prefix. :p
{
try
{
MyEntity myEntity = new MyDto
{
ParentID = a_myDto.ParentID,
Name = a_myDto.Name
}
ObjectContext.MyEntities.AddObject(myEntity);
ObjectContext.SaveChanges();
}
catch (Exception)
{
throw; // <- Never hits this spot.
}
}
// Call back
public void OnMyCallback(SubmitOperation a_submitOperation)
{
if (a_submitOperation.HasError)
throw a_submitOperation.Error; // <- It doesn't matter if I have this or not, the client crashes anyway.
// Other stuff that is not hit because it throws the exception above.
}
I found that the solution to my problem is to save the ID back to the dto when the entity is saved. Like this:
[Insert]
public void InsertMyDto(MyDto a_myDto) // <- Yes I prefix. :p
{
try
{
MyEntity myEntity = new MyDto
{
ParentID = a_myDto.ParentID,
Name = a_myDto.Name
}
ObjectContext.MyEntities.AddObject(myEntity);
ObjectContext.SaveChanges();
a_myDto.ID = myEntity.ID; // <- Solution
}
catch (Exception)
{
throw; // <- Never hits this spot.
}
}
Have you tried setting the parent instead of it's ID?
foreach (var myDto in myDtos)
{
myDto.Parent = myParentDto;
} //Assuming myParentDto is already in the context, if not add it first
Edit: I'm taking a wild guess here but could you check the HashCode of the objects right before the Exception occurs? You could also try overriding the GetHashCode() method to return something random every time just to test those are the exact entities involved in the exception.