I need to update an entity that has two children which also has two children that both are dependent on both parents.
Job(PK: Jobid)
Holes(PK: Holeid, FK: Jobid) / Orders(PK: Orderid, FK: Jobid)
Tools(PK: Toolid, FK: Holeid, FK: Orderid) / ToolHoles(PK: Holeid, Orderid)
Tools also has 7 children that inherit from it.
The job will already exist on save. The job may or may not already contain 1 or more of each child entity.
I need to be able to save all this information in one transaction so that partial info is not saved to the database.
My current attempt has been to build up the Job entity with all the relevant information and call SaveChanges. If I'm adding new entities, keys will have to be generated on save for all but Jobid. Is what I'm trying to accomplish even possible?
Making some assumptions here, let me know if I'm off base. If your scenario looks like the below, then you should be fine.
var myHoles = new Holes();
var myOrders = new Orders();
var myTools = new Tools();
var myToolHoles = new ToolHoles();
myJob.Holes.Add(myHoles); //myJob already exists
myJob.Orders.Add(myOrders);
myHoles.Tools.Add(myTools);
myOrders.Tools.Add(myTools);
myHoles.ToolHoles.Add(myToolHoles);
myOrders.ToolHoles.Add(myToolHoles);
db.SaveChanges();
You say "one transaction" but the reality is that several transactions take place.
myHoles and myOrders will be inserted to the database with their JobId set appropriately.
EF will find out what their IDs are
myTools and myToolHoles will be inserted with the HoleId and OrderId set with the values found in the second step.
Related
I have looked at several similar questions on deleting items from child collections in EF Core, and most do not have an accepted or answer relevant for EF Core:
https://stackoverflow.com/a/51349007
https://stackoverflow.com/a/49679247
I have a class called "Albums" and a property on it called "Musicians".
When I load Albums from the database with EF, I include the Musicians property so that the collection is already tracked by EF. If I add an item to the Musicians list and save changes on the Album entity, I notice it is added to the junction table for Musicians and Albums, and this works great. For deletes, this does not seem to have an effect.
Example:
var albums = repository.LoadAlbumsWithMusicians();
var musicians = CreateNewMusiciansForAlbumOne();
var test = albums.First().Musicians;
test.Clear();
test.AddRange(musicians);
With this code, if a composite primary key (MusicianId, MusicianType, and AlbumId) for a musician matches one which EF already knows about, even if it is actually a different object, it will do nothing as it knows this relationship already exists. That's what I expect.
If the primary key does not match an existing one then it will be treated as a new object in the collection and a new row is added to the database. That's also what I expect.
However, I can't seem to do anything that will make EF recognize that the items which were previously in the collection [and have been removed] should be deleted. In EF6, I simply used .Clear() and .AddRange() to handle these relationships, and EF handled the inserts and deletes for child relationships. Without explicitly removing these objects from the context as in this suggested answer, https://stackoverflow.com/a/49679247, how can I ensure these deleted child items are removed from the database?
Using the example below lets say our "selectedAppender" contains a record by ID and all junction table data. In order to add items and remove items from that junction table all we need to do is account for them in a separate object (delete and add object).
Once we have a list of items to delete it's as simple as calling the "remove" method.
Let me know if this isn't clear enough. Hopefully it helps.
file_appender selectedAppender = context.file_appender.Find(dto.Id);
int[] ids = dto.Loggers.Where(x => !x.Selected).Select(x => x.Id).ToArray();
var loggers_to_delete = selectedAppender.logger.Where(x => ids.Contains(x.id));
loggers_to_delete.ToList().ForEach(x =>
{
selectedAppender.logger.Remove(x);
});
ids = dto.Loggers.Where(x => x.Selected).Select(x => x.Id).ToArray();
var loggers_to_add = context.logger.Where(x => ids.Contains(x.id));
loggers_to_add.ToList().ForEach(x =>
{
selectedAppender.logger.Add(x);
});
One downside to linq is you have to approach these types of problems iteratively. There is no way I know of to do a bulk delete. If you know of a way and can improve on it please share it.
I'm using EF 6.1
Model
I have entries, containing an user which itself contains a city. The entry also has a supplier.
I retrieve my entries with .AsNoTracking():
return dbContext.Set<entry>()
.Include(x => x.user.city)
.Include(x => x.supplier).AsNoTracking().ToList();
I have even more properties but they are all similar.
View
The user sees one entry and can change all member variables of all properties. He can change the entry id (not the PrimaryKey). He can change the user's last name. He can change the city's name or zip code. And so on...
Problem
Think about this example:
entry: id = 123
user: first_name = "Foo", last_name = "Bar"
city: name = "FooCity", zip_code = "4711"
The user sees this. He can now change the first_name to "Peter" and click save. Then I'm doing this:
dbContext.Set<entry>().Add(modifiedAndNotTrackedEntry);
dbContext.Entry(modifiedAndNotTrackedEntry).State = EntityState.Modified;
dbContext.SaveChanges();
But then EF duplicates all child entites. So in the DB I have a new user, which might be what I want if the user does not yet exist. But this user points to a new city altough the city wasn't changed. Furthermore the supplier now exists two times in the database.
Why AsNoTracking()
I have used AsNoTracking() because the user sees the real entity. If he changes something it directly affects all entities: Setting first_name to "Peter" all entries which had "Foo" "Bar" as user, then have "Peter" "Bar" as user. But only the modified entity should have this change. I think that happens because I directly modify the member variable of the "real" entity in the context.
Question
How can I reattach a modified entity without recreating existing child entities.
OR
How can I achieve what I want without using AsNoTracking()
I´m trying same thing as you, i´m not sure to understand quite well your reasson thought, but probably this document will help you.
https://msdn.microsoft.com/en-us/library/jj592676(v=vs.113).aspx
Also, i´m almost sure that you don´t need to execute .Add() in your example, that will result in new record most of the cases, or in "Unchanged" if apply.
I can achieve to attach same entity with new values just by attaching and applying state = modified, still researching how to re-attach related properties (my worst problem is the 1toN relationship)
Check under "Attaching an existing entity to the context", probably it leads you a solution.
I´m also researching an interesting post here in code project:
https://www.codeproject.com/Articles/33088/Reattaching-Entity-Graphs-with-the-Entity-Framewor
It seems this guy invented an extension to re-attach node graphs on entity framework, like "here you have the missing feature from EF." I´ll give it a try.
Regards.
I'm working with a Database First model with Entity Framework 5.
In particular, we have one table with a list of predefined categories that are used as reference to a second table of items.
TABLE ITEM
(lots of fields)
integer category_id
TABLE CATEGORY
integer category_id
string name
string description
The table with the categories is populated initially and will not be modified (unless there is a change in the requirements)
The category for each object is calculated inside the code and returns a Category object for each Item object passed to a class written for that purpouse. This class does not need to use the objects in the CATEGORY TABLE, because they are known in advance. It is basically something like this:
var calculatedCategory = Categorizer.calculateCategoryFor(itemToCategorize);
itemToCategorize.category = calculatedCategory;
QUESTION
I want to find the most transparent way to work with the entities when I am calculating the Category for an Item whatever I want to use the category inside or outside a DbContext. The method to returns a Category object instead of just a String to make a proper use of the entities even in a dettached scenario, but this category can just be created on the fly inside the method because it knows the name, wich is the important field.
The key point here is how to make it transparent to whoever uses this Categorizer to avoid possible duplicated insertions (besides during runtime enforcing some constrain in the DB).
The solution I am using right now is passing a collection of categories to the constructor of the Categorizer, so it can use them when they are actual references in the database. When I just want to calculate it whithout any database operation involved, I just create a collection of this Categories dettached. However this is an additional duplicity and cumbersome, and feels very wrong.
I would like something that could be used like this
using (var ctx = new context())
{
var itemToCategorize = getItemSomeHow();
var calculatedCategory = Categorizer.calculateCategoryFor(itemToCategorize);
itemToCategorize.category = calculatedCategory;
ctx.SaveChanges()
}
Instead of something like that
using (var ctx = new context())
{
var itemToCategorize = getItemSomeHow();
var calculatedCategory = Categorizer.calculateCategoryFor(itemToCategorize);
//Code that searches for the matching category in the DB
...
itemToCategorize.category = matchingCategory;
ctx.SaveChanges()
}
and avoid the returned Category to be inserted as a new one in the database, and just update the reference of the Item to the right preexisting Category
I'm thinking in two approaches:
Override some methods in the model to do this verification and avoid inserts in the database in the Category table.
Modify the database to remove the category_id and use the name field as primary key (the problem here is that I'm not an expert in databases and I don't know the effects in the preformance of this, as the ITEMS TABLE can be huge)
You can't avoid having to do some sort of name checking before storing a Category. I think the cheapest way to do this is:
var categoryId = ctc.Categories.Where(c => c.Name == category.Name)
.Select(c => c.Id).FirstOrDefault();
if (categoryId.HasValue)
{
itemToCategorize.CategoryId = categoryId.Value;
}
else
{
ctc.Categories.Add(category);
itemToCategorize.Category = category;
}
So you only fetch an Id value from the database to do the check. And by setting CategoryId only (not the Category object reference) you avoid ever duplicating an existing Category.
To prevent concurrency issues due to latency between the check and the actual insert, of course as a final guard there should be a unique index on the name column (needless to say, I guess).
I have couple of questions with update functionaliy using NHibernate
I have Customer and location entities with 1:n relationship. Customer has location property. While creating/updating customer entity, I just assigned location property and commited changes.
new Location() { Id = ViewModel.LocationId };
Is it proper way to do it or do I need to retrieve the location entity from db and attach it again like below
newCust.Location = GetlocationfromDB(ViewModel.LocationId);
And how does it work with m:n relationship. I have order and orderitems entities. So, if a newgroup is added/deleted, do I need to check which group is added and get from db and attach it or just groupid will do fine..
This isn't the right way to do it - it might work if you have your unsaved-value mapping right for the primary key, but the proper way to do it is to use session.Load(ViewModel.LocationId) see http://ayende.com/blog/3988/nhibernate-the-difference-between-get-load-and-querying-by-id
There are a number of ways of dealing with this, but it sounds like you want your relationship to be mapped as a set (to prevent duplicates) rather than a bag. If you map it as a set and use ISet for the property type of the relationship, the duplicates will be handled for you. If however you use a bag, you would need to remove duplicates in your own code. Again, you should be using session.Load to get the group if it's an already existing group.
In Entity Framework, when I've mapped my tables to the corresponding entities through the designer and get to actually using them, I'll find that an entity - Thing, who has a relationship (many to one, or one to one) with another object, say, Bob, for example, would produce the following three properties on Thing:
Bob
BobId
BobReference
And were I to set BobId, and save my entity, the next time I fetch this Thing, I'll be able to navigate the Bob property without trouble. I'm curious, however, if it is possible to configure EF to allow me to navigate the property without having to immediately save.
You can do something like this: (EF 4.1)
//Has to exists a record on Bob table with Id = 1
var thing = new Thing() { BobId = 1 };
var context = new YouContext();
context.Entry(thing ).State = EntityState.Unchanged;
context.Entry(thing ).Reference(x => x.Bob).Load();
and then thing.Bob is != null