Insert entity into a CollectionEntry to avoid DetectChanges() - c#

I am using the extension below to add an entity to a EntryCollection (List of FK navigations) in order to avoid the DetectChanges logic from EF:
public static void InsertToEFCollection<T>(this CollectionEntry collection, T newObject)
{
ICollection<T> currentValue = (ICollection<T>) collection.CurrentValue;
currentValue.Add(newObject);
// At this point the newObject has its navigation FK to null
}
Does it look correct? The problem I am having is that, when I call this method and I debug it, it looks like there is nothing updated in the newObject FK navigation or the FK id.
And I discovered, that if I run the statement below just after calling the previous method, then the fix up is occuring and everything is fine:
_unitOfWork.Context.Entry(itemRecipeDBTo);
// itemRecipeDBTo is the owner of the collection where we added "newObject" before.
This shouldn't happen, should it?

Related

Deleting an EF entity efficiently without retrieving inquiry by using .Local and new constructor

I'm reading some EF tutorials about deleting items by setting context.Entry().State to EntityState.Deleted. Two approaches were mentioned when we want to delete an item by Id:
First call table.Find(Id) to return the entity, then set its EntityState.
The Find() method will try to locate the entity if it already exists locally, saving a database inquiry, but otherwise it will retrieve the item from the database, wasting an inquiry.
Construct a placeholder entity using the Id, then set its EntityState
But the problem with #2 is that it won't work if the actual entity with the same Id does already exist locally.
Now assuming we do not know if the entity is actually already tracked or not, but we just need to delete it anyway, and we want to guarantee that no wasted retrieval from the database occurs. My question is does this method below work efficiently for this purpose by using the table.Local property:
public int Delete(int id)
{
T entityToDelete = table.Local.SingleOrDefault(e => e.Id == id);
if (entityToDelete == null)
{
entityToDelete = new T() { Id = id};
}
Context.Entry(entityToDelete).State = EntityState.Deleted;
return SaveChanges();
}
My thinking is that since .Local will never attempt to retrieve the entity from the database, using it instead of .Find and combine it with the new entity constructor approach could guarantee an entity being deleted without extra inquiry.
However, .Net documentation does say this about Local:
One final thing to note about Local is that because it is an ObservableCollection performance is not great for large numbers of entities. Therefore if you are dealing with thousands of entities in your context it may not be advisable to use Local.
Can I assume that the overheads of using Local should be smaller than a database inquiry in most cases?
Another approach I can think of is wrapping #2 inside a try/catch, and handle the exception (which implies that the entity is indeed already being tracked) by using .Find() to retrieve the entity locally. How would this compare to the .Local approach?
Your approach is very good, the performance will be much better than a classic delete, because you're finding an element from a memory collection instead of retrieving the object from database.
IMPORTANT: If an entry is attached manually and marked as deleted/modified, and the entry does not exist, a DbUpdateConcurrencyException exception will be thrown after invoke the SaveChanges.
I wanted to recommend EntityFramework Plus it provides free functions to improve the EF performance such as Batch Delete, Batch Update, Query Cache and much more (BTW I don't work for EntityFramework Plus). You can read more about EF+ here
Here is a code snip to add the Delete function to your DbContext. To make it generic, we need to have an entity base class, let's call it EntityBase.
public abstract class EntityBase
{
public int Id { get; set; }
}
public int Delete<T>(int id) where T : EntityBase, new()
{
//using local view/manually attaching
var item = Set<T>().Local.SingleOrDefault(f => f.Id == id);
if (item is null)
{
item = new T { Id = id };
Set<T>().Attach(item); //WARNING: a DbUpdateConcurrencyException will be thrown is entry does not exist
}
Entry(item).State = EntityState.Deleted;
return SaveChanges();
}
public int DeleteEfPlus<T>(int id) where T : EntityBase, new()
{
//using entityframework-plus
return Set<T>().Where(x => x.Id == id).Delete();
}

Entity Framework validation fails on unused property update

I have a problem that I can't seem to understand, let alone solve. Any help would be appreciated.
So I have an MVC application, Entity Framework, the usual. I have a request that can update one property of one entity. This request sends the Id of the entity and the new value of the property.
In order to avoid querying the database for the entity, and since I only want to do the update on that one property and then return, I attach a new entity to the context with the id and then set the CurrentValue. Something like this:
public class MyEntityHandler
{
// dbContext is my EF context, instantiated from a DI container
// ...
public void UpdateProperty<TProp>(MyEntity entity, Expression<Func<MyEntity , TProp>> property, TProp value)
{
var memberName=(property.Body as MemberExpression).Member.Name;
var temp = dbContext.ChangeTracker.Entries<MyEntity>()
.SingleOrDefault( o => o.Entity.Id == entity.Id );
if( temp == null || temp.State == EntityState.Detached)
{
dbContext.MyEntities.Attach( entity );
}
this.dbContext.Entry(entity).Property(memberName).IsModified = true;
this.dbContext.Entry(entity).Property(memberName).CurrentValue = value;
}
}
Then, this can be used like this: UpdateProperty(e, e=>e.Prop, "NewValue");
where e is of type MyEntity, and Prop is a string property in it.
When the post request comes in, I simply create a new MyEntity, I assign the id and then call this method. Something like this (there are a couple more layers, but it makes no difference for my question):
public ActionResult MyMethod(int id, string newValue)
{
var e=new MyEntity { MyEntityId=id };
new MyEntityHandler().UpdateProperty(e,e=>e.Prop,newValue);
return View();
}
My understanding is that in this case, EF should attach the entity in unchanged state. The entity in my case is not in the context and the attach succeeds. Then I update that one single property, which will put the entity into Modified state, so an update statement should be generated on SaveChanges(). Since only the one property is changed, that should be the only one in the update statement. I double checked the values in the ChangeTracker, I can see that the entity is modified and the property is modified, but every other property i not modified.
The problem is that when I call SaveChanges(), I get a DbValidationException, because one of the other properties is null, but it has a Required attribute. This is all rightfully so - since I attach a new entity and set only the id before attaching, it should be null. I just don't understand the validation error - I'm not trying to insert that value into the database (again, I checked the ChangeTracker and the state of the entity is modified and the property in question is not modified).
Why do I get this validation error? Is this by design? Is there a way to tell EF to let this save through (without disabling the validation altogerher on the context)?

How to delete detached entities when they share the same foreign keys but different object instances?

I am trying to delete a list of detached entities. The entities to be deleted have relationships to other entities (foreign keys), for example:
public class Foo
{
public int BarId {get;set;}
public virtual Bar Bar {get;set;}
}
In some cases, 2 Foo entities might have the same Bar id. However, because these are detached entities and not being tracked by the context, the 2 Foo entities have 2 unique Bar entities.
To delete Foo I'm doing (in a generic repository class):
public virtual void Delete(T entity)
{
DbEntityEntry dbEntityEntry = dataContext.GetEntry(entity);
if (dbEntityEntry.State != EntityState.Deleted)
{
dbEntityEntry.State = EntityState.Deleted;
}
else
{
dbSet.Attach(entity);
dbSet.Remove(entity);
}
}
This works for the first Foo entity only. For other Foo entities where Bar is the same, I get the exception:
Attaching an entity of type failed because another entity of the same type already has the same primary key value
As a work around, I'm setting Foo.Bar = null before calling Delete(). This works fine.
However, is there a better way to delete multiple entities from the context?
If you just want to delete an entity you can use direct SQL statement, like this
dataContext.Database.ExecuteSqlCommand("delete from YOURTABLE where id=#id", new SqlParameter("#id", entity.id));
Try setting state without attaching, like:
dataContext.Entry(entity).State = EntityState.Deleted;
What you are doing, is that you are check if state of entity has not already been set to Deleted then set its state to Deleted. This is fine, but if state of entity has already been set to Deleted, you are trying to attach and remove. It is unnecessary. If entity state is Deleted it measn that it is not Detached, there is no need to re-attach and re-delete it.

Is it really impossible to update child collection in EF out of the box (aka non-hacky way)?

Let's say you have these classes in your entities.
public class Parent
{
public int ParentID { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
public int ChildID { get; set; }
public int ParentID { get; set; }
public virtual Parent Parent { get; set; }
}
And you have a user interface to update the Parent along with its Children, meaning if the user add new Child then you have to insert, if the user edits an existing Child then you need to update, and if the user removes a Child then you have to delete. Now obviously if you use the following code
public void Update(Parent obj)
{
_parent.Attach(obj);
_dbContext.Entry(obj).State = EntityState.Modified;
_dbContext.SaveChanges();
}
it won't be able to detect the changes inside the Child because EF cannot detect changes inside a Navigation Property.
I've been asking this question for like 4 times and get mixed answers. So is it actually possible to do this stuff without it getting complicated? This problem can fix the problem by separating the user interface between Parent and Child but I don't want to because merging both Child and Parent in one menu is pretty common in business application development and more user friendly.
UPDATE :
I'm trying the solution below but it doesn't work.
public ActionResult(ParentViewModel model)
{
var parentFromDB = context.Parent.Get(model.ParentID);
if (parentFromDB != null)
{
parentFromDB.Childs = model.Childs;
}
context.SaveChanges();
}
Instead of detecting changes inside the Children, EF won't be able to tell what to do with old child. For example if parentFromDB has 3 children the first time I pull it from DB then I delete the 2nd and 3rd child. Then I'm getting The relationship could not be changed because one or more of the foreign-key properties is non-nullable when saving.
I believe this is what happened :
The relationship could not be changed because one or more of the foreign-key properties is non-nullable
Which took me back to square one because in my scenario, I can't just fetch from the DB and update the entry and call SaveChanges.
because EF cannot detect changes inside Navigation Property
This seems to be a somewhat distorted description of the fact that _dbContext.Entry(obj).State = EntityState.Modified doesn't mark navigaton properties as modified.
Of course EF tracks changes in navigation properties. It tracks changes in properties and associations of all entities that are attached to a context. Therefore, the answer to your question, now positively stated...
Is it possible to update child collection in EF out of the box
... is: yes.
The only thing is: you don't do it out of the box.
The "out of the box" way to update any entity, whether it be a parent or a child in some collection is:
Fetch entities from the database.
Modify their properties or add/remove elements to their collections
Call SaveChanges().
That's all. Ef tracks the changes and you never set entity States explicitly.
However, in a disconnected (n-tier) scenario, this gets more complicated. We serialize and deserialize entities, so there can't be any context that tracks their changes. If we want to store the entities in the database, now it's our task to make EF know the changes. There are basically two ways to do this:
Set the states manually, based on what we know about the entities (like: a primary key > 0 means that they exist and should be updated)
Paint the state: retrieve the entities from the database and re-apply the changes from the deserialized entities to them.
When it comes to associations, we always have to paint the state. We have to get the current entities from the database and determine which children were added/deleted. There's no way to infer this from the deserialized object graph itself.
There various ways to alleviate this boring and elaborate task of painting the state, but that's beyond the scope of this Q&A. Some references:
Generic repository to update an entire aggregate
GraphDiff
Its cozs your doing it weirdly.
This requires Lazy loading for getting childs (obviously modify for your usage)
//get parent
var parent = context.Parent.Where(x => x.Id == parentId).SingleOrDefault();
wrote a whole test method for you. (apply to your case)
EmailMessage(parent) is the parent and it has none or many EmailAttachment's(child's)
[TestMethod]
public void TestMethodParentChild()
{
using (var context = new MyContext())
{
//put some data in the Db which is linked
//---------------------------------
var emailMessage = new EmailMessage
{
FromEmailAddress = "sss",
Message = "test",
Content = "hiehdue",
ReceivedDateTime = DateTime.Now,
CreateOn = DateTime.Now
};
var emailAttachment = new EmailAttachment
{
EmailMessageId = 123,
OrginalFileName = "samefilename",
ContentLength = 3,
File = new byte[123]
};
emailMessage.EmailAttachments.Add(emailAttachment);
context.EmailMessages.Add(emailMessage);
context.SaveChanges();
//---------------------------------
var firstEmail = context.EmailMessages.FirstOrDefault(x => x.Content == "hiehdue");
if (firstEmail != null)
{
//change the parent if you want
//foreach child change if you want
foreach (var item in firstEmail.EmailAttachments)
{
item.OrginalFileName = "I am the shit";
}
}
context.SaveChanges();
}
}
Update
Do your AutoMappper Stuff... as you said in your comment.
Then when you are ready to save and you have it back as the correct types ie once which represent entitys(Db) then do this.
var modelParent= "Some auto mapper magic to get back to Db types."
var parent = context.Parent.FirstOrDefault(x => x.Id == modelParent.Id);
//use automapper here to update the parent again
if (parent != null)
{
parent.Childs = modelParent.Childs;
}
//this will update all childs ie if its not in the new list from the return
//it will automatically be deleted, if its new it will be added and if it
// exists it will be updated.
context.SaveChanges();
I have spent hours trying different solutions to find out some decent way of dealing with this problem. The list is so long that I can't write all of them here but few are...
changing parent entity state
changing child entity state
attaching and detaching entity
clearing dbSet.Local to avoid tracking errors
tried writing customer logic in ChangeTracker
rewriting mapping logic between DB to View models
....and so on....
Nothing worked but finally, here is how just a minor change solved the whole mess.
With this solution, you need to stop setting states manually. Just call dbSet.Update() method once, EF will take care of internal state management.
NOTE: This works even though you are working with detached graphs of entities or even with the entities having nested parent-child relationships.
Before code:
public void Update(Parent obj)
{
_parent.Attach(obj);
_dbContext.Entry(obj).State = EntityState.Modified;
_dbContext.SaveChanges();
}
After code:
public void Update(Parent obj)
{
dbSet.Update(obj);
_dbContext.SaveChanges();
}
Reference: https://www.learnentityframeworkcore.com/dbset/modifying-data#:~:text=DbSet%20Update&text=The%20DbSet%20class%20provides,with%20individual%20or%20multiple%20entities.&text=This%20method%20results%20in%20the,by%20the%20context%20as%20Modified%20
If you are using entityframework core, it provides dbSet.Update() method, which takes care of any update in any level of object tree.
For reference please check the documentation link here

Using Linq-To-SQL, how can I ensure forced execution on a complex type?

More LINQ questions from me, what a surprise.
So right now, I am working on a website that uses an internal SQLite data source. I'm using LINQ-To-SQL in order to "talk" to it, utilizing Entity Framework. Now, say I have a complex object. This object contains a List inside it, which in turn maps a One-To-Many relationship.
ParentObject {
Integer Id
List<RelatedObject> RelatedObjects
string SomeField
}
RelatedObject {
Integer Id
ParentObject Parent
NestedObject Nested
string SomeField
}
NestedObject {
Integer Id
RelatedObject Parent
string SomeField
}
Now this is a DB structure I cannot change. Suppose I am executing a query in a context on page load:
var results = context.ParentObjects.Where(parent => parent.SomeField == AnotherField).ToList();
As far as I know, using ToList() will "force" linq to execute the query there and then, causing us to not have to worry about deferred execution. So, it is safe to do this:
result = results.First();
(result is defined in the scope outside of results() due to an if() statement).
Now this works fine, until I try and execute an extension method, which does this:
// Assume this is an extension method on ParentObject
return this.RelatedObjects.Where(related => this.SomeField == related.SomeField).Select(related.Nested).First();
(very convoluted when I try and explain it)
We then get an exception because the DataContext from which "this" (The ParentObject) was created has been disposed.
My question is, how can I access the List<RelatedObject> RelatedObjects field inside ParentObject after the context has ended?
I'm silly.
I forgot to disable Lazy-Loading in the context.
public Context()
{
this.Configuration.LazyLoadingEnabled = false;

Categories

Resources