I have the following method, which receives an entity which was mapped from a dto. Therefor it is not tracked yet.
public async Task UpdateAsync(Entity value, CancellationToken cancellationToken)
{
dbContext.Update(value);
await dbContext.SaveChangesAsync(cancellationToken);
}
public class Entity
{
public Guid Id { get; set; }
public ICollection<Item> Items { get; set; }
}
public class Item
{
public Guid Id { get; set; }
public Guid EntityId { get; set; }
}
The Entity has a property Items which is a one to many relation.
What is the best way to update the Entity and remove existing or add new relations?
Currently it does not remove existing Items, which are not part of the collection anymore. I guess this is because, not all items are tracked, since the Entity is created from a dto.
It seems like I have to load the existing Entity and all relations from the database first, and then manually map all the properties and relations (Add, Remove).
This means a lot of work. Is there a better way to achieve this? Can EF somehow remove untracked relations?
No, actually you need to separate add/update or delete logic. It is also better way to maintain you code later.
Maybe you can try such approach (_factory is IServiceScopeFactory):
public async Task<TModel> UpdateAsync(TModel model)
{
using var scope = _factory.CreateScope();
await using var context = scope.ServiceProvider.GetRequiredService<ApplicationContext>();
var entry = await context.Set<TModel>().FirstAsync(t => t.Id == model.Id); // try other methods
var entryEntry = context.Entry(entry);
entryEntry.CurrentValues.SetValues(model);
await context.SaveChangesAsync();
return entryEntry.Entity;
}
Related
I need to remove all records in a given table, using a DbContext in Entity Framework. I need this to be portable, so it can't rely on SQL statements or stored procedures. I've looked through the answers to this question, but none of them meet my requirements or are ideal solutions for a variety of reasons.
I could use the RemoveRange method, i.e.
DbContext.Table.RemoveRange(all);
But this doesn't scale well, because it selects all entries before deleting, which could take a long, long time with Entity Framework. Iterating through each record and removing them individually with Remove(record) has the same problem.
Using SQL this is simple, using a TRUNCATE command. Even a simple DELETE FROM [TableName] command works, but I don't know how scalable that is.
Is there any solution that uses only Entity Framework (no SQL), and doesn't require selecting all records first before deleting them?
This is currently not something that is possible using Entity Framework. see https://github.com/dotnet/efcore/issues/795
There may be an extension out there that will allow you to do that, but I am not sure it will work will all RDBMS systems.
Let's suppose you have BrandData table with records about some brands:
public class BrandData
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Also you've created a dbcontext:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
Database.Migrate();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<BrandData>().HasData(new BrandData { Id = 999, Name = "LG", Description = "TV brand" });
}
public DbSet<BrandData> BrandData { get; set; }
}
Finally, this is the method that deletes brand by its ID:
public async Task DeleteModelAsync(int id)
{
var data = _dbContext.ModelData.FirstOrDefault(b => b.Id == id);
if (data != null)
{
_dbContext.ModelData.Remove(data);
await _dbContext.SaveChangesAsync();
}
}
Changes will be done after SaveChangesAsync() method run.
UPDATE
To delete all records:
var brands = await _dbContext.BrandData.ToListAsync();
foreach(var brand in brands)
{
_dbContext.BrandData.Remove(brand);
}
await _dbContext.SaveChangesAsync();
public class Practice
{
public List<Participation> Participation { get; set; }
}
public class Participation
{
public string Id { get; set; }
public virtual Practice Practice { get; set; }
}
public void test()
{
var practice = _ctx.Practice.SingleOrDefault(p => p.Id == practiceId);
practice.Participations.AddRange(NewParticipations);
_ctx.Participation.AddRange(NewParticipations)
await _ctx.SaveChangesAsync();
}
If I have the above, would I need the 3rd line in the test function to save new participations or would the practice.Participations.AddRange() handle that implicitly?
practice.Participations.AddRange should be enough.
If you reference a new entity from the navigation property of an entity that is already tracked by the context, the entity will be discovered and inserted into the database.
source: https://learn.microsoft.com/en-us/ef/core/saving/related-data#adding-a-related-entity
You can observe it like so...
var practice = _ctx.Practice.SingleOrDefault(p => p.Id == practiceId);
practice.Participations.AddRange(NewParticipations);
Debug.WriteLine(_ctx.Participation.Count()); //note count
await _ctx.SaveChangesAsync();
Debug.WriteLine(_ctx.Participation.Count()); //count increased
You should be able to add the new data to the database either way. If you added through the context, you would need to set the foreign key in the NewParticipations objects yourself, so that a link would exist to the Practice object.
I'm trying to update a one-to-many relationship with EntityFramework, but EF won't save the relationship for some reason. I'm using ASP.Net MVC, but that does not seem to matter in this case as the data is received correctly.
I've tried a lot of possible solutions and some tutorials, unfortunately almost all of them describe a scenario where the connection is made via a foreign key property in the class itself.(I'm aware that EF adds a FK in the database, but i cant access that directly.) My approach seems to be significantly different as none of their solution seems to work for me.
The code below seems to me to be the most promising, but it still doesn't work. the foreign key of the activity object doesn't get updated.
Removing context.Entry(act.ActivityGroup).State = EntityState.Detached; causes a Primary Key collision, as EF tries to insert the ActivityGroup as a new Entity. Marking it as Modified, doesn't do the trick either.
Models:
public class Activity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid ActivityID { get; set; }
public string Name { get; set; }
public ActivityGroup ActivityGroup { get; set; }
}
public class ActivityGroup
{
public int ActivityGroupID { get; set; }
public string GroupName { get; set; }
public string BackgroundColor { get; set; }
}
Method to save Data
public ActionResult SaveActivities(List<Activity> activities)
{
if (ModelState.IsValid)
{
using (TSSDBContext context = new TSSDBContext())
{
foreach (Activity act in activities)
{
if (act.ActivityGroup != null)
{
context.Entry(act.ActivityGroup).State = EntityState.Detached;
}
context.Entry(act).State = (act.ActivityID == null || act.ActivityID == Guid.Empty) ? EntityState.Added : EntityState.Modified;
}
context.SaveChanges();
return new HttpStatusCodeResult(200);
}
}else
{
return new HttpStatusCodeResult(500);
}
}
You could try something like this.
EF context is tracking each entity you don't need manually marking
entities , Modified or Added for each. Read about Entityframework context tracking
Just fetch the entities what you need and decide to insert or update on based on your condition and just Add what should be added and update
Just do a SaveChanges EF will show the magic
This is a basic idea of inserting and updating entities at one shot. If you have concerns about performance i would suggest you to update using AddRange method in EF 6.0
using(var db1 = new Entities1())
{
var activitylists = db.Activity.ToList();
foreach (var item in activitylists )
{
if(item.Id==null)
{
var newActivity= new Activity();
//Your entities
newActivity.Name="Name";
db.Activity.Add(newActivity);
db.Entry<Activity>(item).State = System.Data.Entity.EntityState.Added;
}
else
{
item.Name="new name update";
db.Entry<Activity>(item).State = System.Data.Entity.EntityState.Modified;
}
}
db.SaveChanges();
}
Update : if your getting data from PostRequest , you need to manually mark the entities as modified or added as the context is not aware of what to do with entities
I was trying to create a generic method to update an Entity and all it's collection properties from a detached object. For example:
public class Parent{
public int Id { get; set; }
public string ParentProperty { get; set; }
public List<Child> Children1 { get; set; }
public List<Child> Children2 { get; set; }
}
public class Child{
public int Id { get; set; }
public string ChildProperty { get; set; }
}
So, my first intention was to use something like this:
Repository<Parent>.Update(parentObj);
It would be perfect have a magic inside this method that update Parent properties and compare the list of Children of the parentObj to the current values in database and add/update/remove them accordingly, but it's too complex to my knowledge about EF/Reflection/Generic... and so I tried a second more easier way like this:
Repository<Parent>.Update(parentObj, parent => parent.Children1
parent => parent.Children2);
This method would be a little harder to use, but yet acceptable. But how I think the second parameter had to be params Expression<Func<TEntity, ICollection<TRelatedEntity>>>[] relatedEntities I had problems to specify multiple TRelatedEntity. So my try was to 3rd step with no success yet...
Now I tried to call a method to update Parent and a sequence of methods to update Childreen, like this:
Repository<Parent>.Update(parentObj);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children1);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children2);
And the code:
public virtual void Update(TEntity entityToUpdate)
{
context.Entry(entityToUpdate).State = EntityState.Modified;
}
public virtual void UpdateChild<TRelatedEntity>(TEntity entityToUpdate, Func<TEntity, object> keySelector, Expression<Func<TEntity, ICollection<TRelatedEntity>>> relatedEntitySelector) where TRelatedEntity: class
{
var entityInDb = dbSet.Find(keySelector.Invoke(entityToUpdate));
var current = relatedEntitySelector.Compile().Invoke(entityToUpdate);
var original = relatedEntitySelector.Compile().Invoke(entityInDb);
foreach (var created in current.Except(original))
{
context.Set<TRelatedEntity>().Add(created);
}
foreach (var removed in original.Except(current))
{
context.Set<TRelatedEntity>().Remove(removed);
}
foreach (var updated in current.Intersect(original))
{
context.Entry(updated).State = EntityState.Modified;
}
context.Entry(entityInDb).State = EntityState.Detached;
}
First problem was to get original values, because when I call dbSet.Find the entity is already in context (context.Entry(entityToUpdate).State = EntityState.Modified;).
So I tried to change order calling first Child:
Repository<Parent>.Update(parentObj);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children1);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children2);
And now I have the error:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
In summary, it would be very nice the first way, but I would be satisfied with the second/third too.
Thanks very much
Edit 1
Please, I need a native solution or using Automapper (which we already use in the project), because my customer don't like external dependencies and if we need to adapt something to the project, like working with Attached objects to update their related entities, so GraphDiff mencioned in the comments doesn't fit our needs (and VS 2015 RC crashed when I tried to install the package for tests)
Have you considered getting the object from the DB and using AutoMapper to modify all the property values?
I mean:
var obj = GetObjectFromDB(...);
AutoMapObj(obj, modifiedObj);
SaveInDb();
I am using Entity Framework 4.3 Code First, and I have problem with updating many-to-many relationships.
I defined the following classes:
public abstract class Entity
{
[Column(Order = 0)]
[Key]
public int Id { get; set; }
[Timestamp]
[Column(Order = 1)]
public byte[] Version { get; set; }
}
public class Video : Entity
{
public string Title { get; set; }
public string Description { get; set; }
public TimeSpan Length { get; set; }
public virtual ICollection<Coworker> Coworkers { get; set; }
}
public class Coworker : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Video> Videos { get; set; }
}
When the database is created, the schema look right:
There is a Videos, Coworkers and VideoCoworkers table too, without
I use repository pattern in an N-Tier application to access database, my Insert and Update method looks like this:
public T Insert(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
session.Context.Set<T>().Add(entity);
}
}
public T Update(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<T>().Attach(entity);
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
When I update an entity, I create the entity object from a DTO, that's why use DbSet.Attach instead of selecting it and updating the properties one-by-one.
When I initialize the database, I add some test data:
Create 3 Coworkers, where I set first and last name. (A, B, C)
Create 3 Videos, where I set title, description and length, and also set some coworkers. First video has A,B, second has B,C and third has A,C.
When I list the Videos from code, I can see that Video.Coworkers collection is filled with good values, and when I query the link table (VideoCoworkers) in SQL Server Management Studio, it also looks good.
My problem is
when I update for example the title of the Video, it works. But when I try to delete from Video2 the existing coworkers (B and C), and try to add coworker A, then the relationship is not updated. It also does not work when I only try to add new coworker, or only try to delete one. I create the entity which is used as the parameter of the Update() method by creating a new Video entity with a new collection of Coworkers (which are selected from the database with Find() method by Id).
What is the correct way to update many-to-many relationships?
But when I try to delete from Video2 the existing coworkers (B and C),
and try to add coworker A, then the relationship is not updated.
Without using a generic repository the correct procedure would be:
using (var session = new DatabaseSession())
{
video2 = session.Context.Set<Video>().Include(v => v.Coworkers)
.Single(v => v.Id == video2Id);
coworkerA = new Coworker { Id = coworkerAId };
session.Context.Set<Coworker>().Attach(coworkerA);
video2.Coworkers.Clear();
video2.Coworkers.Add(coworkerA)
session.Context.SaveChanges();
}
The essential part is that you must load or attach the entity in its original state, change the entity, i.e. remove and add children, and then save the changes. EF's change detection will create the necessary INSERT and DELETE statements for the link table entries. The simple procedure to set the state to Modified you are trying in your generic Update method is suited only for updating scalar properties - like changing the video title - but won't work for updating relationships between entities.
For solve this problem:
attach the entity to context
load the collection(the collection is not loaded, because )
change the state of entity to modified
save changes
So your code for update should be like this:
public Video Update(Video entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<Video>().Attach(entity);
session.Context.Entry(entity).Collection(p => p.Coworkers).Load();
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
Please refer here to see how to save master detail in asp.net mvc with database first. Hopefully it will give you the idea about the code first. You may also have a look at knokout.js example