Using EF4.1 is there a event of function I can override on my POCO that will be called when it is deleted? I save images on the file system with the DB containing a reference to the file. When I delete from the DB I also want to delete the matching file.
You can override the SaveChanges method of your DbContext.
public override int SaveChanges()
{
var deletedEntities = ChangeTracker.Entries().Where(entry => entry.State == EntityState.Deleted);
foreach (var deletedEntity in deletedEntities)
{
if (deletedEntity .Entity is MyEntity)
{
//delete the file
}
}
return base.SaveChanges();
}
You can wrap the file delete and database update in a single transaction as follows
using (var scope = new TransactionScope())
{
//your deletion logic
myContext.SaveChanges();
scope.Complete();
}
Try doing it in a database level trigger. EF is not the proper place to handle this.
Related
I set up a Generic repository using this code for update
private void AttachIfNot(TEntity entityToActive)
{
if (_dbContext.Entry(entityToActive).State == EntityState.Detached)
{
_dbSet.Attach(entityToActive);
}
}
private void UpdateEntity(TEntity entityToUpdate)
{
AttachIfNot(entityToUpdate);
_dbContext.Entry(entityToUpdate).State = EntityState.Modified;
}
It just attach the entity and set the modified state to save.
But when I use efocre ownsone to map a value object,the update entity function is not working.
I found out that it only works when I set Valueobject to modified too.
_dbContext.Entry(entityToUpdate).State = EntityState.Modified;
_dbContext.Entry(entityToUpdate.Valueobject).State = EntityState.Modified;
But It is hard for me to specify all the value objects in a Generic Repository.
This is code also has problems with one to many or other relations.
The working way is like this:
Classroom classroom = new Classroom
{
Id = 1,
Name = "b",
Students = new List<Student>
{
new Student()
{
Name = "aa",
Id = 2
}
}
};
if (_defaultDbContext.Entry(classroom).State == EntityState.Detached)
{
_defaultDbContext.Classrooms.Attach(classroom);
foreach(var stu in classroom.Students)
{
_defaultDbContext.Students.Attach(stu);
}
}
_defaultDbContext.Entry(classroom).State = EntityState.Modified;
foreach (var stu in classroom.Students)
{
_defaultDbContext.Entry(stu).State = EntityState.Modified;
}
_defaultDbContext.SaveChanges();
I found out one way is get the entity form repo then update it using automapper:
targetEntity = repo.GetById(entityId);
automapper.map(souceEntity,targetEntity);
//or
automapper.map(souceDto,targetEntity);
_dbContext.Save();
The entity comes by query, so the change will be tracked.
But I have to configure the automapper with this entity map when I want to change entity
CreateMap<EntityType, EntityType>();
I think it's not the best solution. Is there a bettere way?
DbContext.Update would be fine to fix this problem.
see:
https://www.learnentityframeworkcore.com/dbcontext/change-tracker
In my EF 6 MVC app, I have an entity Seller which has a 1:1 relationship to SellerShippingPolicies. When I update the seller entity, EF is also attempting to update the SellerShippingPolicies entity, and I don't want this to happen.
I have the following method that updates a Seller entity:
public Entities.Seller Save(Entities.Seller seller)
{
// Instantiate a helper method
HelperMethods helper = new HelperMethods(this.UnitOfWork);
// Map the domain entity to an EF entity
var sellerRecord = Mapper.Map<Seller>(seller);
// Attempt to prevent the updating of the SellerShippingPolicies entity
helper.GetDbContext().Entry(sellerRecord.SellerShippingPolicies).State = EntityState.Detached;
// Save the entity
sellerRecord = helper.SaveItem<Seller>(sellerRecord);
}
Here is the SaveItem method that gets called:
public T SaveItem(T entity)
{
var row = this._dbSet.Find(GetPrimaryKeyValue(entity));
if ( row == null )
return AddItem(entity);
else
return UpdateItem(entity);
}
And the Update method that eventually gets called:
public T UpdateItem(T entity)
{
// Retrieve the current copy of the entity to be updated.
var currentEntity = GetItem(GetPrimaryKeyValue(entity));
// Copy the contents of the modified entity on top of the copy we just retrieved. This way EF will save everything correctly.
currentEntity = Copy.ShallowCopy<T>(entity, currentEntity);
this._dbContext.SaveChanges();
return currentEntity;
}
Not sure it's necessary, but here is the method for ShallowCopy and GetItem.
public static T ShallowCopy<T>(object source, T target)
{
foreach (PropertyInfo pi in typeof(T).GetProperties())
{
var property = source.GetType().GetProperty(pi.Name);
if (property == null)
continue;
if (property.GetSetMethod() != null)
property.SetValue(target, pi.GetValue(source, null), null);
}
return target;
}
public T GetItem(object primaryKeyValue)
{
return this._dbSet.Find(primaryKeyValue);
}
All these methods share the same context object.
You can see that I'm attempting to prevent the updating of the SellerShippingPolicies entity by setting its state to detached. This does not work. I've also tried setting the state to Unchanged. That doesn't work either. In both cases, EF attempts to update the SellerShippingPolicies entity. What am I missing?
I am trying to remove all references followed by adding them back from a list of disconnected objects.
using(var scope = new TransactionScope())
{
_autoIncidentService.AddNewCompanyVehicles(
autoIncidentModel.CompanyVehiclesInvolved.Where(v => v.Id == 0));
_autoIncidentService.ClearCollections(autoIncidentModel.Id);
_autoIncidentService.Update(autoIncidentModel);
scope.Complete();
}
return Json(ResponseView);
The ClearCollections removes items references. The GetAutoIncident includes the collection.
public void ClearCollections(int id)
{
var autoIncident = GetAutoIncident(id);
foreach (var vehicle in autoIncident.CompanyVehiclesInvolved.ToArray())
{
autoIncident.CompanyVehiclesInvolved.Remove(vehicle);
}
db.SaveChanges();
}
When I try to update the entity right after the ClearCollections method it fails.
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
I am using a singleton to get the DbContext so there shouldn't be any situation where the context is different. It is being stored in the HttpContext.Current.Items.
The update method is as follows:
public override void Update(AutoIncidentModel model)
{
var data = GetData(model.Id);
Mapper.CreateMap<AutoIncidentModel, AutoIncident>()
.ForMember(m => m.CompanyVehiclesInvolved, opt => opt.ResolveUsing(m =>
{
var ids = m.CompanyVehiclesInvolved.Select(v => v.Id);
return db.Vehicles.Where(v => ids.Contains(v.Id)).ToList();
}));
Mapper.Map(model, data);
db.SaveChanges();
}
Obviously, I am missing something important here. Do the entites from my ResolveUsing method need to somehow be associated with the parent entity or is automapper overwriting the property (CompanyVehiclesInvolved) and causing a problem?
I am working on Entity Framework 4.0 . Here Adding control into database using AddObject() and save that suing SaveChange() methods.
But once I delete that added control and try to add again same I am getting this error again and again
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
I am not able to add it. Once I close my application then try to add then I am able to add that control.
I tried to search a lot here and there how it going wrong but could not find solution.
As I am new born in field in Entity Framework.
public void Add(SearchPath entity) {
_context.SearchPaths.AddObject(entity);
// _context.Save(entity, false);
}
public void Remove(SearchPath entity)
{
if (entity.Path != null)
{
using (EntityContext entityObj = new EntityContext())
{
entityObj.SearchPaths.Attach(entity);
entityObj.SearchPaths.DeleteObject(entity);
entityObj.SaveChanges();
}
}
}
public void Modify(SearchPath entity)
{
using (EntityContext entityObj = new EntityContext())
{
try
{
entityObj.SearchPaths.Attach(entity);
entityObj.ObjectStateManager.ChangeObjectState(entity, System.Data.EntityState.Modified);
entityObj.SaveChanges();
}
catch (OptimisticConcurrencyException)
{
entityObj.Refresh(RefreshMode.StoreWins, entity);
entityObj.SaveChanges();
}
}
}
public void Add(Package entity)
{
_context.Packages.AddObject(entity);
}
public void Remove(Package entity)
{
if (_context.GetEntityState(entity) == EntityState.Unchanged)
_context.Packages.Attach(entity);
_context.Packages.DeleteObject(entity);
}
Answer for above problem is Just call your own save method like this.
public void Save(object entity)
{
using (var transaction = Connection.BeginTransaction())
{
try
{
SaveChanges();
transaction.Commit();
}
catch (OptimisticConcurrencyException)
{
if (ObjectStateManager.GetObjectStateEntry(entity).State == EntityState.Deleted || ObjectStateManager.GetObjectStateEntry(entity).State == EntityState.Modified)
this.Refresh(RefreshMode.StoreWins, entity);
else if (ObjectStateManager.GetObjectStateEntry(entity).State == EntityState.Added)
Detach(entity);
AcceptAllChanges();
transaction.Commit();
}
}
}
once I delete that added control and try to add again
The problem is that you apparently do that in the same context. To me that indicates that the context's lifespan is too long. The best way to use context instances in EF is one context per unit of work (or business transaction, or use case).
So if you want to delete a control: create a context, delete the the control, dispose the context. If you want to add it again (for some reason): create a context, add the control, dispose the context. From your code snippets it is not entirely clear to me if this happens in your code.
I am inserting a record into the database, which looks like this:
class Transaction
{
int Id;
}
What I want, is when I insert this object, I want to create another record, like this:
class TransactionUpdate
{
int StartingTransactionId;
int EndingTransactionId;
}
What I have so far, is a loop in my SaveChanges on the DbContext, which takes new Transaction objects that will be created and creates TransationUpdate objects and attaches these to the DbContext.
public override int SaveChanges()
{
foreach(var entry in this.ChangeTracker.Entries())
{
if(entry.Entity is Transaction)
{
var update = new TransactionUpdate();
update.StartingTransactionId = ((Transaction)entry.Entity).PreviousTransactionId;
update.EndingTransactionId = ((Transaction)entry.Entity).Id; // This is zero because the entity has not been inserted.
this.TransactionUpdates.Add(update);
}
}
}
The problem is, I cannot properly create a TransactionUpdate because I do not have 'EndingTransactionId', or, the Id of the Transaction I am currently inserting.
How can I solve this problem?
Many Thanks.
SOLVED
I have done what Ladislav suggested and am now creating a list of items to add, along with references to the objects that are required to insert them. Thus:
public override int SaveChanges()
{
var transactionUpdatesToAdd = new List<Tuple<TransactionUpdate, Transaction>>();
foreach (var entry in this.ChangeTracker.Entries<Transaction>())
{
if (entry.State == EntityState.Added)
{
var update = new TransactionUpdate();
update.StartingTransactionId = ((Transaction)entry.Entity).PreviousTransactionId;
transactionUpdatesToAdd.Add(new Tuple<TransactionUpdate, Transaction>(update, entry.Entity));
}
}
using(var scope = new TransactionScope())
{
// Save new Transactions
base.SaveChanges();
// Update TransactionUpdates with new IDs
foreach (var updateData in transactionUpdatesToAdd)
{
updateData.Item1.EndingTransactionId = updateData.Item2.Id;
this.TransactionUpdates.Add(updateData.Item1);
}
// Insert the new TransactionUpdate entities.
return base.SaveChanges();
}
Based on your description I guess you are using autogenerated Id in database. You will not receive this Id befere executing SaveChanges on the context. You have to divide operation into two separate modifications:
public override int SaveChanges()
{
// call base context saving operation to insert all Transactions
base.SaveChanges();
foreach(var entry in this.ChangeTracker.Entries())
{
if(entry.Entity is Transaction)
{
var update = new TransactionUpdate();
update.StartingTransactionId = ((Transaction)entry.Entity).PreviousTransactionId;
update.EndingTransactionId = ((Transaction)entry.Entity).Id;
this.TransactionUpdates.Add(update);
}
}
// save changes again to insert all TransactionUpdates
base.SaveChanges();
}
You should wrap it into TransactionScope to perform whole saving as atomic operation.
If you haven't inserted TransactionId, you have it anyway in your object. Pass your object as parameter to an overloaded method SaveChanges and use it to pass the Id