EF 4 delete many to many object - entity-framework

I am trying to delete an object from a many to many relationship but it refused to get worked.
The structure is as follows:
I have a Products table which every product has excProducts
I am trying to delete an excProduct with the following code
var objectContx = new objectContx();
var prod = objectContx.Products.Where(p => p.ProductID == prodID).FirstOrDefault();
var excProd = objectContx.ExcProducts.Where(exc => exc.ExcProductID == excProdid).FirstOrDefault();
prod.ExcProducts.Attach(excProd);
prod.ExcProducts.Remove(excProd);
objectContx.SaveChanges();
The excProd deleted from database but when I am getting the Product from objectContext it has the deleted excProd in it's list.
How could I delete it from the objectContext as well?

If your many to many relationship has an entity (e.g. if it has payload) then the instance of that entity must be deleted. This can be done manually or using cascaded delete (see option in property window while standing on relationship in EDMX designer).

Related

Delete multiple rows using Entity Framework

When deleting data related to an object in an HttpDelete call using Entity Framework Core, I can do this:
var video = _context.VideoList.Where(x => x.VideoId == nodeList.VideoId).FirstOrDefault();
But what if I have multiple rows that needed deleted using the same VideoId? Also the two entities are not related in the database(no foreign keys).
I've found answers that use a List then use RemoveRange, but I just need to delete potentially multiple records using the same ID...I know in SQL it would look like this:
DELETE FROM VideoList WHERE VideoId = '111xxx333aaa'
Is there a way to do this in EF core?
Thanks!
You can do it in 2 steps :
Even if your entities are not related get them in an IEnumerable like a List
var videos = _context.VideoList.Where(x => x.VideoId == nodeList.VideoId).ToList();
And then remove them with a RemoveRange
_context.VideoList.RemoveRange(videos);

How do i delete single record from table using EF 6.1.1

I am using Entity Framework 6.1.1.
I am deleting single record from table as following but i am not sure whether its the only way or could further rewrite it in an efficient way.
Can someone share comments?
Reason: I am asking because many solutions in earlier posts are referring to EF 4.0 and not using the latest version 6.1.1.
Guid studentId = student.Id;
StudentReportDetail stuDetails = _context.StudentReportDetail.Find(studentId);
if (stuDetails != null)
{
_context.StudentReportDetail.Remove(stuDetails);
_context.SaveChanges();
}
There are no changes about how to delete an entity between EF 4 and EF 6. To delete an entity using Entity Framework, you need to use the Remove method on DbSet. Remove works for both existing and newly added entities.
Calling Remove on an entity that has been added but not yet saved
to the database will cancel the addition of the entity. The entity is
removed from the change tracker and is no longer tracked by the
DbContext.
Calling Remove on an existing entity that is being change-tracked
will register the entity for deletion the next time SaveChanges is
called.
Deleting with loading from the database
As the example you show in your question, you need to load first the existing entity from your context to delete it. If you don't know the Id, you can execute a query as I show below to find it first:
var report= (from d in context.StudentReportDetail
where d.ReportName == "Report"
select d).Single();
context.StudentReportDetail.Remove(report);
context.SaveChanges();
Deleting without loading from the database
If you need to delete an entity, but it’s not already in memory, it’s a little inefficient to retrieve that entity from the database just to delete it. If you know the key of the entity you want to delete, you can attach a stub that represents the entity to be deleted, and then delete this stub. A stub is an instance of an entity that just has the key value assigned. The key value is all that’s required for deleting entities.
var toDelete = new StudentReportDetail {Id = 2 };
context.StudentReportDetail.Attach(toDelete);
context.StudentReportDetail.Remove(toDelete);
context.SaveChanges();
Other way could be changing the entity's state to Deleted.DbContext has methods called Entry and Entry<TEntity>, these methods get a DbEntityEntry for the given entity and provide access to the information about the entity and return a DbEntityEntry object able to perform the action on the entity. Now you can perform the delete operation on the context by just changing the entity state to EntityState.Deleted:
var toDelete = new StudentReportDetail {Id = 2 };
context.Entry(toDelete).State = EntityState.Deleted;
context.SaveChanges();
Using a 3rd party library
There is another way but is using a 3rd party library, EntityFramework Plus, there is a nugget package you can install. You can use the batch delete operation:
context.StudentReportDetail
.Where(u => u.Id== stuDetails)
.Delete();

Entity framework: how to read current data after delete from referenced table

I have three tables Job, Contact and a reference table between them named JobContact. When I delete a record from JobContact table, so record is deleted from database, but it is still present in code. I mean, when I do a select Job by key and when I'm accessing job.JobContact, so record is still there.
How can I force EF to get the current data from this table?
Edited:
I'm using EF to delete the record. Here is a code sample how I'm doing it:
Step 1: delete record from JobContact:
var jobContactRepos = RepositoryFactory.GetRepository<JobContact>();
var jobContact = jobContactRepos.SelectByKey(jobContactId);
jobContactRepos.Delete(jobContact);
jobContactRepos.Save();
Step 2: get the job record from DB after step 1 is done:
var jobRepos = RepositoryFactory.GetRepository<Job>();
var job = jobRepos.SelectByKey(id);
After Step 1, record is deleted from DB: it is OK.
After Step 2, record is still present in the job.JobContact entity: it is not OK.
RepositoryFactory creates already a new context. So I don't understant. In which place in my code should I use Refresh() method?
thanks
You can dispose your EF context and create a new one, this will force EF to get fresh data from the DB instead of using possibly cached data. Alternatively you can call Refresh() on your context using RefreshMode.StoreWins.
But the real question is why do you delete this record from the database directly and don't use EF for it? Had you used the EF context to remove the Contact entity from the Contacts navigation property collection of your Job entity, this problem shouldn't be there in the first place.
Edit:
The reference table should be represented in EF as a navigation property Contacts in your Job entities, and a navigation property Jobs in your Contact entities. Are you using an older version of EF (I am probably not familiar enough with previous versions) or have a custom repository layer that introduces this reference entity?

EF 4.1 loading filtered child collections not working for many-to-many

I've been looking at Applying filters when explicitly loading related entities and could not get it to work for a many-to-many relationship.
I created a simple model:
Brief description:
A Student can take many Courses and a Course can have many Students.
A Student can make many Presentation, but a Presentation can be made by only one Student.
So what we have is a many-to-many relationship between Students and Courses, as well as a one-to-many relationship between Student and Presentations.
I've also added one Student, one Course and one Presentation related to each other.
Here is the code I am running:
class Program
{
static void Main()
{
using (var context = new SportsModelContainer())
{
context.Configuration.LazyLoadingEnabled = false;
context.Configuration.ProxyCreationEnabled = false;
Student student = context.Students.Find(1);
context.
Entry(student).
Collection(s => s.Presentations).
Query().
Where(p => p.Id == 1).
Load();
context.
Entry(student).
Collection(s => s.Courses).
Query().
Where(c => c.Id == 1).
Load();
// Trying to run Load without calling Query() first
context.Entry(student).Collection(s => s.Courses).Load();
}
}
}
After loading the presentations I see that the count for Presentations changed from 0 to 1: . However, after doing the same with Courses nothing changes:
So I try to load the courses without calling Query and it works as expected:
(I removed the Where clause to further highlight the point - the last two loading attempts only differ by the "Query()" call)
Now, the only difference I see is that one relationship is one-to-many while the other one is many-to-many. Is this an EF bug, or am I missing something?
And btw, I checked the SQL calls for the last two Course-loading attempts, and they are 100% identical, so it seems that it's EF that fails to populate the collection.
I could reproduce exactly the behaviour you describe. What I got working is this:
context.Entry(student)
.Collection(s => s.Courses)
.Query()
.Include(c => c.Students)
.Where(c => c.Id == 1)
.Load();
I don't know why we should be forced also to load the other side of the many-to-many relationship (Include(...)) when we only want to load one collection. For me it feels indeed like a bug unless I missed some hidden reason for this requirement which is documented somewhere or not.
Edit
Another result: Your original query (without Include) ...
context.Entry(student)
.Collection(s => s.Courses)
.Query()
.Where(c => c.Id == 1)
.Load();
... actually loads the courses into the DbContext as ...
var localCollection = context.Courses.Local;
... shows. The course with Id 1 is indeed in this collection which means: loaded into the context. But it's not in the child collection of the student object.
Edit 2
Perhaps it is not a bug.
First of all: We are using here two different versions of Load:
DbCollectionEntry<TEntity, TElement>.Load()
Intellisense says:
Loads the collection of entities from
the database. Note that entities that
already exist in the context are not
overwritten with values from the
database.
For the other version (extension method of IQueryable) ...
DbExtensions.Load(this IQueryable source);
... Intellisense says:
Enumerates the query such that for
server queries such as those of
System.Data.Entity.DbSet,
System.Data.Objects.ObjectSet,
System.Data.Objects.ObjectQuery,
and others the results of the query
will be loaded into the associated
System.Data.Entity.DbContext,
System.Data.Objects.ObjectContext or
other cache on the client. This is
equivalent to calling ToList and then
throwing away the list without the
overhead of actually creating the
list.
So, in this version it is not guaranteed that the child collection is populated, only that the objects are loaded into the context.
The question remains: Why gets the Presentations collection populated but not the Courses collection. And I think the answer is: Because of Relationship Span.
Relationship Span is a feature in EF which fixes automatically relationships between objects which are in the context or which are just loaded into the context. But this doesn't happen for all types of relationships. It happens only if the multiplicity is 0 or 1 on one end.
In our example it means: When we load the Presentations into the context (by our filtered explicit query), EF also loads the foreign key of the Presentation entites to the Student entity - "transparently", which means, no matter if the FK is exposed as property in the model of not. This loaded FK allows EF to recognize that the loaded Presentations belong to the Student entity which is already in the context.
But this is not the case for the Courses collection. A course does not have a foreign key to the Student entity. There is the many-to-many join-table in between. So, when we load the Courses EF does not recognize that those courses belong to the Student which is in the context, and therefore doesn't fix the navigation collection in the Student entity.
EF does this automatic fixup only for references (not collections) for performance reasons:
To fix relationship, EF transparently
rewrites the query to bring
relationship info for all relations
which has multiplicity of 0..1 or1 on
the other end; in other words
navigation properties that are entity
reference. If an entity has
relationship with multiplicity of
greater then 1, EF will not bring back
the relationship info because it could
be performance hit and as compared to
bringing a single foreign along with
rest of the record. Bringing
relationship info means retrieving all
the foreign keys the records has.
Quote from page 128 of Zeeshan Hirani's in depth guide to EF.
It is based on EF 4 and ObjectContext but I think this is still valid in EF 4.1 as DbContext is mainly a wrapper around ObjectContext.
Unfortunately rather complex stuff to keep in mind when using Load.
And another Edit
So, what can we do when we want to explicitely load one filtered side of a many-to-many relationship? Perhaps only this:
student.Courses = context.Entry(student)
.Collection(s => s.Courses)
.Query()
.Where(c => c.Id == 1)
.ToList();

Adding & Removing Associations - Entity Framework

I'm trying to get to grips with EF this week and I'm going ok so far but I've just hit my first major snag. I have a table of items and a table of categories. Each item can be 'tagged' with many categories so I created a link table. Two columns, one the primary ID of the item, the other the primary ID of the category. I added some data manually to the DB and I can query it all fine through EF in my code.
Now I want to 'tag' a new item with one of the existing categories. I have the category ID to add and the ID of the Item. I load both as entities using linq and then try the following.
int categoryToAddId = Convert.ToInt32(ddlCategoriesRemaining.SelectedValue);
var categoryToAdd = db.CollectionCategorySet.First(x => x.ID == categoryToAddId);
currentCollectionItem.Categories.Add(categoryToAdd);
db.SaveChanges();
But I get "Unable to update the EntitySet 'collectionItemCategories' because it has a DefiningQuery and no element exists in the element to support the current operation."
Have I missed something? Is this not the right way to do it? I try the same thing for removing and no luck there either.
I think I have managed to answer this one myself. After alot of digging around it turns out that the Entity Framework (as it comes in VS2008 SP1) doesn't actually support many to many relationships very well. The framework does create a list of objects from another object through the relationship which is very nice but when it comes to adding and removing the relationships this can't be done very easily. You need to write your own stored procedures to do this and then register them with Entity Framework using the Function Import route.
There is also a further problem with this route in that function imports that don't return anything such as adding a many to many relationship don't get added to the object context. So when your writing code you can't just use them as you would expect.
For now I'm going to simply stick to executing these procedures in the old fashioned way using executenonquery(). Apparently better support for this is supposed to arrive in VS2010.
If anyone feels I have got my facts wrong please feel free to put me right.
After you have created your Item object, you need to set the Item object to the Category object on the Item's Categories property. If you are adding a new Item object, do something like this:
Using (YourContext ctx = new YourContext())
{
//Create new Item object
Item oItem = new Item();
//Generate new Guid for Item object (sample)
oItem.ID = new Guid();
//Assign a new Title for Item object (sample)
oItem.Title = "Some Title";
//Get the CategoryID to apply to the new Item from a DropDownList
int categoryToAddId = Convert.ToInt32(ddlCategoriesRemaining.SelectedValue);
//Instantiate a Category object where Category equals categoryToAddId
var oCategory = db.CategorySet.First(x => x.ID == categoryToAddId);
//Set Item object's Categories property to the Category object
oItem.Categories = oCategory;
//Add new Item object to db context for saving
ctx.AddtoItemSet(oItem);
//Save to Database
ctx.SaveChanges();
}
Have you put foreign keys on both columns in your link table to the item and the category or defined the relationship as many to many in the Mapping Details?