Data outside a certain set of records will be deleted, only data added or updated will remain. How do I do this entity framework
Sample
public void MultipleAddOrUpdateDifferenceDelete(Expression<Func<DataClass, bool>> expression)
{
_context.Entry(_context).State = EntityState.Deleted;
_context.DataClass.Where(expression);
_context.Entry(_context).State = EntityState.Added;
_context.SaveChanges();
}
Related
I have a many-to-many relationship between Entity A and Entity B. Entity Framework has automatically created a junction table in SQL Server after running the migration. (I don't have this junction table defined anywhere in the code.) For example:
class EntityA
{
// ...
public ICollection<EntityB> Foo { get; set; }
}
class EntityB
{
// ...
public ICollection<EntityA> Bar { get; set; }
}
I need to replace the Foo collection on EntityA using a (detached) list coming in from a client application. I've spent the better part of a day trying to figure this out. Here is what I've tried:
[HttpPut]
public async Task<IActionResult> Update(EntityA someEntity)
{
var entry = context.EntityA.Attach(someEntity);
entry.State = EntityState.Modified;
var collection = entry.Collection(x => x.Foo);
collection.IsModified = true;
await context.SaveChangesAsync();
}
I've also tried changing the CurrentValue property of collection, and obviously I've also tried replacing Foo directly, but nothing seems to work -- the junction table remains empty. How can this child list be entirely replaced without having to Include() / load the entire list into memory for manual tracking / removal?
Ivan (in the comments above) is right. After some trial and error, I ended up writing an extension method that works for my case. Before I get to that, I want to credit this answer for pointing me in the right direction, which I ended up modifying to get it working with auto-generated EF junction tables. First, the extension method:
// assuming your models inherit from a base class or implement an interface
public interface IEntity
{
Guid Id { get; set; } // or int or whatever your ID field is
}
public static class DbExtensions
{
// Updates the many-to-many child collections of an entity (for an auto-generated EF junction table)
public static async Task UpdateJunctionTableAsync<T, Y>(this DbContext baseContext, T entity, Expression<Func<T, IEnumerable<Y>>> property)
where T : class, IEntity
where Y : class, IEntity
{
// scope these calls to a new context -- working off the base context
// tends to cause issues down the line with the change tracking
using var context = new DbContext();
// EF internally compares with DB entities, so we'll do the same
var dbEntity = await context.FindAsync<T>(entity.Id);
var dbEntry = context.Entry(dbEntity);
// access the collection entry that resulted in a junction table
var dbItemsEntry = dbEntry.Collection(property);
// get its associated CLR collection accessor
var accessor = dbItemsEntry.Metadata.GetCollectionAccessor();
// load the entry's items
await dbItemsEntry.LoadAsync();
// build a dictionary to track what needs to be added vs removed
var dbItemsMap = dbItemsEntry.CurrentValue.ToDictionary(e => e.Id);
// get the current items in the entity (not DB)
var items = (IEnumerable<Y>)accessor.GetOrCreate(entity, false);
// add them to the DB as needed
foreach (var item in items)
{
// if this already exists, no need to process it.
if (dbItemsMap.ContainsKey(item.Id))
dbItemsMap.Remove(item.Id);
else
{
// otherwise, add a tracked version of it.
context.Set<Y>().Attach(item);
accessor.Add(dbEntity, item, false);
}
}
// anything still left here has been deleted from the entity
foreach (var oldItem in dbItemsMap.Values)
accessor.Remove(dbEntity, oldItem);
// we have to clear the junction table from the incoming model's collection,
// otherwise EF will try to attach to it again, which will cause errors
// further down the line
var memberSelectorExpression = property.Body as MemberExpression;
if (memberSelectorExpression != null)
{
var propertyInfo = memberSelectorExpression.Member as PropertyInfo;
if (propertyInfo != null)
propertyInfo.SetValue(entity, null, null);
}
await context.SaveChangesAsync();
}
}
Using this is simple:
[HttpPut]
public async Task<IActionResult> UpdateFoo(EntityA model)
{
// update the junction table first
await context.UpdateJunctionTableAsync(model, x => x.Foo);
// then update whatever else you want
// e.g., if we were updating the whole row:
// context.EntityA.Attach(model).State = EntityState.Modified;
// save
await context.SaveChangesAsync();
return Ok();
}
I thought I had this approach nailed down but I'm getting an odd result. I have a form that passes in a ViewModel to my controller. From there the controller extracts the passed in data, assigns it to the entity and then calls Update and SaveChanges. However, the update wipes all data from my database row leaving only the modified data I passed in.
It was my understanding that calling Update meant that ef could detect which items were modified and update accordingly. Have I got this wrong?
Here is my code
[HttpPost]
public IActionResult AddPositionFixture(ViewPosition model)
{
if (ModelState.IsValid && model != null)
{
var fixture = new Fixture
{
Id = model.FixtureId,
WorkRole = model.TempScope
};
_context.Fixture.Update(fixture);
_context.SaveChanges();
}
return View();
}
My context is injected beforehand:
private readonly MyContext _context;
public PositionController(HaglandContext context)
{
_context = context;
}
I have a Repository project like this.
https://github.com/tugberkugurlu/GenericRepository/tree/master/src
I have a method.
public void Edit(TEntity entity)
{
_dbContext.SetAsModified(entity);
}
public void SetAsAdded<TEntity>(TEntity entity) where TEntity : class
{
DbEntityEntry dbEntityEntry = GetDbEntityEntrySafely(entity);
dbEntityEntry.State = EntityState.Added;
}
But I am getting while update record. I am getting sometimes this error.
Attaching an entity of type 'TP.Model' 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.
I solved the problem like this. I checked the first columns. Afterwards, I did something like this.
_dbSet = dbContext.DbSet<TEntity>();
The rest is cake.
_dbSet.Attach(entity);
DbEntityEntry entry = _dbContext.Entry(entity);
foreach (var proprty in entry.OriginalValues.PropertyNames)
{
var Current = entry.CurrentValues.GetValue<object>(proprty);
var New = entry.GetDatabaseValues().GetValue<object>(proprty);
if (Current != null)
{
if (!object.Equals(New, Current))
{
entry.Property(proprty).IsModified = true;
}
}
}
I'm using Entity Framework and Unit of Work.
I have a decimal column OrderBalance in the Person table and I have an Order table. I want to update orderbalance column by itself at the db level to support concurrent order creations.
I want to insert an order and update OrderBalance column with atomocity (all or nothing).
public override void Create(Order order)
{
_orderReposiory.Add(order);
var person = _personRepository.GetById(order.PersonId);
person.OrderBalance += order.Amount*order.Price;
_personRepository.Edit(person);
_unitOfWork.Commit();
}
As you can see, '+=' process is on object level. How can I do this on db level without breaking atomicity?
I'm using ExeceutSqlCommand with transactionscope and it's work.
public class PersonRepository : GenericRepository<Person>, IPersonRepository
{
public void UpdateOrderBalance(decimal amount,long personId)
{
Entities.Database.ExecuteSqlCommand("Update Person set OrderBalance=OrderBalance+#p0 where id=#p1", amount,personId);
}
}
I have changed my Create Method to this
public override void Create(Order order)
{
using (var scope = new System.Transactions.TransactionScope())
{
_orderReposiory.Add(order);
AddOrderBalancePerson(order);
_unitOfWork.Commit();
scope.Complete();
}
}
private void AddOrderBalancePerson(Order order)
{
_personRepository.UpdateOrderBalance(order.Amount*order.Price, order.PersonId);
}
Entities in PersonRepository and UnitofWork are using same Dbcontext
You need to use the same DbContext instance in both repositories. If you do then you can wrap any number of inserts/updates in a transaction which will give you all or nothing. EF statements will automatically enlist in any transaction pending.
using (var tran = dbContext.Database.BeginTransaction())
{
// your updates here
}
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.