My dbContext always returns null for my junction table. Can I solve the problem by not changing the DB relationship design?
My problem
I want just Id = 1 and Stationery_Id = 1 and UOM_Id = 1
Here is all the detail information...
Database Design
Class
Data in my db
I think it's related to lazy loading. As this tutorial said:
Lazy loading is delaying the loading of related data until you specifically request for it
You never ever request related data directly in this code snippet, so the expectation of loading related data is wrong.
If you wanna load related data at the moment, you can use Include to achieve that:
db.StockUOMs.Include(i => i.UOM).Include(i => i.Stationery).ToList();
for a deep dive, you can follow this link.
good luck.
Related
How would you Upsert without select? the upsert would be a collection of entities received by a method which contains DTOs that may not be available in the database so you can NOT use attach range for example.
One way theoretically is to load the ExistingData partially with a select like dbContext.People.Where(x => x exists in requested collection).Select(x => new Person { Id = x.Id, State = x.State }).ToList() which just loads a part of the entity and not the heavy parts. But here if you update one of these returned entityItems from this collection it will not update because of the new Person its not tracking it and you also cannot say dbContext.Entry<Person>(person).State = Modified because it will throw an error and will tell you that ef core is already "Tracking" it.
So what to do.
One way would be to detach all of them from the ChangeTracker and then do the state change and it will do the update but not just on one field even if you say dbContext.Entry<Person>(person).Property(x => x.State).Modified = true. It will overwrite every fields that you haven't read from the database to their default value and it will make a mess in the database.
The other way would be to read the ChangeTracker entries and update them but it will also overwrite and it will consider like everything is chanaged.
So techinically I don't know how ef core can create the following SQL,
update People set state = 'Approved' where state != 'Approved'
without updating anything else. or loading the person first completely.
The reason for not loading your data is that you may want to update like 14000 records and those records are really heavy to load because they contain byte[] and have images stored on them for example.
BTW the lack of friendly documentation on EFCore is a disaster compare to Laravel. Recently it has cost us the loss of a huge amount of data.
btw, the examples like the code below will NOT work for us because they are updating one field which they know that it exists in database. But we are trying to upsert a collection which some of those DTOs may not be available in the database.
try
{
using (var db = new dbContext())
{
// Create new stub with correct id and attach to context.
var entity = new myEntity { PageID = pageid };
db.Pages.Attach(entity);
// Now the entity is being tracked by EF, update required properties.
entity.Title = "new title";
entity.Url = "new-url";
// EF knows only to update the propeties specified above.
db.SaveChanges();
}
}
catch (DataException)
{
// process exception
}
Edit: The used ef core version is #3.1.9
Fantastic, I found the solution (You need to also take care about your unit tests).
Entityframework is actually working fine it can be just a lack of experience which I'm documenting here in case anyone else got into the same issue.
Consider that we have an entity for Person which has a profile picture saved as Blob on it which causes that if you do something like the following for let's say 20k people the query goes slow even when you've tried to have enough correct index on your table.
You want to do this query to update these entities based on a request.
var entityIdsToUpdate = request.PeopleDtos.Select(p => p.Id);
var people = dbContext.People.Where(x => entityIdsToUpdate.Contains(x.Id)).ToList();
This is fine and it works perfectly, you will get the People collection and then you can update them based on the given data.
In these kind of updates you normally will not need to update images even if you do, then you need to increase the `TimeOut1 property on your client but for our case we did not need to update the images.
So the above code will change to this.
var entityIdsToUpdate = request.PeopleDtos.Select(p => p.Id);
var people = dbContext.People
.Select(p => new Person {
Id = p.Id,
Firstname = p.Firstname,
Lastname = p.Lastname,
//But no images to load
})
.Where(p => entityIdsToUpdate.Contains(p.Id)).ToList();
But then with this approach, EntityFramework will lose the track of your entities.
So you need to attach it like this and I will tell you how NOT to attach it.
This is the correct way for a collection
dbContext.People.AttachRange(people); //These are the people you've already queried
Now DO NOT do this, you may want to do this because you get an error from the first one from EntityFramework which says the entity is already being tracked, trust it because it already is. I will explain after the code.
//Do not do this
foreach(var entry in dbContext.ChangeTracker.Entries())
{
entry.State = EntityState.Detached;
}
//and then on updating a record you may write the following to attach it back
dbContext.Entry(Person).State = EntityState.Modified;
The above code will cause EntityFramework not to follow the changes on the entities anymore and by the last line you will tell it literally everything edited or not edited is changed and will cause you to LOSE your unedited properties like the "image".
Note: Now what can u do by mistake that even messes up the correct approach.
Well since you are not loading your whole entity, you may assume that it is still fine to assign values to the unloaded ones even if the value is not different than the one in the database. This causes entity framework to assume that something is changed and if you are setting a ModifiedOn on your records it will change it for no good reason.
And now about testing:
While you test, you may get something out from database and create a dto from that and pass the dto with the same dbContext to your SystemUnderTest the attach method will throw an error here which says this entity is already bein tracked because of that call in your test method. The best way would be create a new dbContext for each process and dispose them after you are done with them.
BTW in testing it may happen that with the same dbContext you update an entity and after the test you want to fetch if from the database. Please take note that this one which is returning to you is the "Cached" one by EntityFramework and if you have fetched it in the first place not completely like just with Select(x => ) then you will get some fields as null or default value.
In this case you should do DbContext.Entry(YOUR_ENTRY).Reload().
It is a really complete answer it may not directly be related to the question but all of the things mentioned above if you don't notice them may cause a disaster.
Some previous code I had written used the Find() method to retrieve single entities by their primary key:
return myContext.Products.Find(id)
This worked great because I had this code tucked into a generic class, and each entity had a different field name as its primary key.
But I had to replace the code because I noticed that it was returning cached data, and I need it to return data from the database each call. Microsoft's documentation confirmed this is the behavior of Find().
So I changed my code to use SingleOrDefault or FirstOrDefault. I haven't found anything in documentation that states these methods return cached data.
Now I am executing these steps:
Save an entity via EF.
Execute an UPDATE statement in SSMS to update the recently saved
record's Description field.
Retrieve the entity into a new entity variable using SingleOrDefault
or FirstOrDefault.
The entities being returned still have the old value in the Description field.
I have run a SQL trace, and verified that the data is being queried during step 3. This baffles me - if EF is making a round trip to the database, why is it returning cached data?
I've searched online, and most answers apply to the Find() method. Furthermore, they suggest some solutions that are merely workarounds (dispose the DbContext and instantiate a new one) or solutions that won't work for me (use the AsNoTracking() method).
How can I retrieve my entities from the database and bypass the EF cache?
The behaviour you're seeing is described in Microsoft's How Queries Work article under point 3:
For each item in the result set
a. If this is a tracking query, EF checks if the data represents an entity already in the change tracker for the context instance
If so, the existing entity is returned
It's described a little better in this blog post:
It turns out that Entity Framework uses the Identity Map pattern. This means that once an entity with a given key is loaded in the context’s cache, it is never loaded again for as long as that context exists. So when we hit the database a second time to get the customers, it retrieved the updated 851 record from the database, but because customer 851 was already loaded in the context, it ignored the newer record from the database (more details).
All of this is saying that if you make a query, it checks the primary key first to see if it already has it in the cache. If so, it uses what's in the cache.
How do you avoid it? The first is to make sure you're not keeping your DbContext object alive too long. DbContext objects are only designed to be used for one unit of work. Bad things happen if you keep it around too long, like excessive memory consumption.
Do you need to retrieve data to display to the user? Create a DbContext to get the data and discard that DbContext.
Do you need to update a record? Create a new DbContext, update the record and discard that DbContext.
This is why, when you use EF Core with dependency injection in ASP.NET Core, it is created with a scoped lifetime, so any DbContext object only lives for the life of one HTTP request.
In the rare case you really do need to get fresh data for a record you already have an object for, you can use EntityEntry.Reload()/EntityEntry.ReloadAsync like this:
myContext.Entry(myProduct).Reload();
That doesn't help you if you only know the ID though.
If you really really need to reload an entity that you only have the ID for, you could do something weird like this:
private Product GetProductById(int id) {
//check if it's in the cache already
var cachedEntity = myContext.ChangeTracker.Entries<Product>()
.FirstOrDefault(p => p.Entity.Id == id);
if (cachedEntity == null) {
//not in cache - get it from the database
return myContext.Products.Find(id);
} else {
//we already have it - reload it
cachedEntity.Reload();
return cachedEntity.Entity;
}
}
But again, this should only be used in limited cases, when you've already addressed any cases of long-living DbContext object because unwanted caching isn't the only consequence.
Ok, I have the same problem and finally found the answer,
You doing everything right, that's just how EF works.
You can use .AsNoTracking() for your purposes:
return myContext.Products.AsNoTracking().Find(id)
make sure you addedusing Microsoft.EntityFrameworkCore; at the top.
It works like a magic
I have spent 2 days bashing my head against this problem, and I can't seem to crack it (the problem that is). The same code was working fine until I added database relationships, and I have since read a lot about lazy-loading.
I have two database tables with a 1:1 relationship between them. PromoCode table tracks codes, and has a PK column named id. CustomerPromo table has a column PromoId which is linked to the PromoCode table id. These two tables have no other relationships. I generated all this in SQL Server Management Studio, then generated the model from the database.
To make matters slightly more complicated, I'm doing this inside a WCF data service, but I don't believe that should make a difference (it worked before database relationships were added). After enabling logging, I always get an Exception in the log file with text:
DataContext accessed after Dispose.
My function currently returns all entries from the table:
using (MsSqlDataContext db = new MsSqlDataContext())
{
// This causes issues with lazy-loading
return db.PromoCodes.ToArray();
}
I have read numerous articles/pages/answers and they all say to use the .Include() method. But this doesn't work for me:
return db.PromoCodes.Include(x => x.CustomerPromos).ToArray();
I've tried the "magic string" version as well:
return db.PromoCodes.Include("CustomerPromos").ToArray();
The only code I've managed to get to work is this:
PromoCode[] toReturn = db.PromoCodes.ToArray();
foreach (var p in toReturn)
p.CustomerPromos.Load();
return toReturn;
I've tried added a .Where() criteria to the query, I've tried .Select(), I've tried moving the .Include() after the .Where() (this answer says to do it last, but I think that's only due to nested queries). I've read about scenarios where .Include() will silently fail, and after all this I'm no closer.
What am I missing? Syntax problem? Logic problem? Once I get this "simple" case working, I also need to have nested Includes (i.e. if CustomerPromo table had a relationship to Customer).
Edit
Including all relevant code. The rest is either LINQ to SQL, or WCF Data Services configuration. This is all there is:
[WebGet]
[OperationContract]
public PromoCode[] Test()
{
using (MsSqlDataContext db = new MsSqlDataContext())
{
return db.PromoCodes.Include(x => x.CustomerPromos).ToArray();
}
}
If I call that through a browser directly (e.g. http://<address>:<port>/DataService.svc/Test) I get a reset connection message and have to look up the WCF logs to find out "DataContext accessed after Dispose.". If I make the same query through an AJAX call in a webpage I get an AJAX error with status error (that's all!).
I prematurely posted the previous answer when I didn't actually have any child data to fetch. At the time I was only interested in fetching parent data, and that answer worked.
Now when I actually need child data as well I find it didn't work completely. I found this article which indicates that .Include() (he says Including() but I'm not sure if that's a typo) has been removed, and the correct solution is to use DataLoadOptions. In addition, I also needed to enable Unidirectional Serialisation.
And to top it off, I no longer need DeferredLoadingEnabled. So now the final code looks like this:
using (MsSqlDataContext db = new MsSqlDataContext())
{
DataLoadOptions options = new DataLoadOptions();
options.LoadWith<PromoCode>(p => p.CustomerPromos);
db.LoadOptions = options;
return db.PromoCodes.ToArray();
}
After setting Unidirectional Serialisation it will happily return a parent object without having to load the child, or explicitly set DeferredLoadingEnabled = false;.
Edit: This did not solve the problem entirely. At the time of testing there wasn't any child data, and I wasn't trying to use it. This only allowed me to return the parent object, it doesn't return child objects. For the full solution see this answer.
Contrary to everything I've read, the answer is not to use .Include() but rather to change the context options.
using (MsSqlDataContext db = new MsSqlDataContext())
{
db.DeferredLoadingEnabled = false; // THIS makes all the difference
return db.PromoCodes.ToArray();
}
This link posted in the question comments (thanks #Virgil) hint at the answer. However I couldn't find a way to access LazyLoadingEnabled for LINQ to SQL (I suspect it's for EntityFramework instead). This page indicated that the solution for LINQ to SQL was DeferredLoadingEnabled.
Here is a link to the MSDN documentation on DeferredLoadingEnabled.
I've got a list of entity object Individual for an employee survey app - an Individual represents an employee or outside rater. The individual has the parent objects Team and Team.Organization, and the child objects Surveys, Surveys.Responses. Responses, in turn, are related to Questions.
So usually, when I want to check the complete information about an Individual, I need to fetch Individuals.Include(Team.Organization).Include(Surveys.Responses.Question).
That's obviously a lot of includes, and has a performance cost, so when I fetch a list of Individuals and don't need their related objects, I don't bother with the Includes... but then the user wants to manipulate an Individual. So here's the challenge. I seem to have 3 options, all bad:
1) Modify the query that downloads the big list of Individuals to .Include(Team.Organization).Include(Surveys.Responses.Question). This gives it bad performance.
2) Individuals.Load(), TeamReference.Load(), OrganizationReference.Load(), Surveys.Load(), (and iterate through the list of Surveys and load their Responses and the Responses' Questions).
3) When a user wishes to manipulate an Individual, I drop that reference and fetch a whole brand new Individual from the database by its primary key. This works, but is ugly because it means I have two different kinds of Individuals, and I can never use one in place of the other. It also creates ugly problems if I'm iterating across a list repeatedly, as it's tricky to avoid loading and dropping the fully-included Individuals repeatedly, which is wasteful.
Is there any way to say
myIndividual.Include("Team.Organization").Include("Surveys.Responses.Question");
with an existing Individual entity, instead of taking approach (3)?
That is, is there any middle-ground between "fetch everything from the database up-front" and "late-load one relationship at a time"?
Possible solution that I'm hoping I could get insight about:
So there's no way to do a manually-implemented explicit load on a navigational-property? No way to have the system interpret
Individual.Surveys = from survey in MyEntities.Surveys.Include("Responses.Question")
where survey.IndividualID = Individual.ID
select survey; //Individual.Surveys is the navigation collection property holding Surveys on the Individual.
Individual.Team = from team in MyEntities.Teams.Include("Organization")
where team.ID = Individual.TeamID
select team;
as just loading Individual's related objects from the database instead of being an assignment/update operation? If this means no actual change in X and Y, can I just do that?
I want a way to manually implement a lazy or explicit load that isn't doing it a dumb (one relation at a time) way. Really, the Teams and Organizationss aren't the problem, but the Survey.Responses.Questions are a massive buttload of database hits.
I'm using 3.5, but for the sake of others (and when my project finally migrates to 4) I'm sure responses relevant to 4 would be appreciated. In that context, similar customization of lazy loading would be good to hear about too.
edit: Switched the alphabet soup to my problem domain, edited for clarity.
Thanks
The Include statement is designed to do exactly what you're hoping to do. Having multiple includes does indeed eager load the related entities.
Here is a good blog post about it:
http://thedatafarm.com/blog/data-access/the-cost-of-eager-loading-in-entity-framework/
In addition, you can use strongly typed "Includes" using some nifty ObjectContext extension methods. Here is an example:
http://blogs.microsoft.co.il/blogs/shimmy/archive/2010/08/06/say-goodbye-to-the-hard-coded-objectquery-t-include-calls.aspx
I use LINQ-to-SQL to load data from a database that has two tables in a one-to-many relationship (one Recipe has many Ingredients).
I load a Recipe and LINQ retrieves Ingredient objects into an EntitySet that is binded into a ListBox.
If I want to delete some Ingredients off a Recipe, I get a "An attempt was made to remove a relationship between a Recipe and a Ingredient. However, one of the relationship's foreign keys (Ingredient.RecipeID) cannot be set to null.
I SOLVED this problem using the well known solution by adding 'DeleteOnNull="true"' to the DBML file. But adding this setting only removes the problem when we are deleting Ingredient objects that were retrieved from the DB.
The problem is with the Ingredient objects that were created in code (added to a Recipe) and added to the EntitySet collection of Ingredients and then deleted BEFORE SubmitUpdates is called. Then, the same exception happens again. This usually happens on a new, unsaved recipe when user is adding ingredients to it, makes a mistake and erases an ingredient off a recipe. I added the DeleteOnNull to both 'Association Name="Recipe_Ingredient"' lines in DBML.
How am I supposed to remove such objects? The only solution I see at the moment is that I would load the ingredients into a collection not under the DataContext and then when saving, delete all ingredients off a recipe and add then again from that cache..
try
{
// Needed for existing records, but will fail for new records
yourLINQDataContext.Ingredients.DeleteOnSubmit(ingredient);
}
catch (Exception)
{
// Swallow
}
yourRecipeObject.Ingredients.Remove(ingredient);
It seems that you're looking for something that I was looking for myself just a few days back when I asked "How do I design backing data types for a databound WPF dialog with Ok/Cancel buttons?".
The answer is an intriguing post from Paul Stovell describing a sample IEditable adapter for Linq to Sql. This will let you create your desired "Apply/Cancel" semantics in a generalized manner without completely dissociating yourself from the underlying ORm-generated classes through a full custom-written layer.
It's a pretty slick trick, overall, that will essentially let you sidestep the problems you're fighting right now. :)
On a different note, I'm curious as to why your recipe to ingredient relationship is 1:n instead of m:n. Is it for simplicity's sake? I use garlic in a lot of recipes. :)
// Create new entities
Cart c = new Cart();
CartEntry ce = new CartEntry();
ce.Cart = c;
// Delete the entry
c.CartEntries.Remove(ce);
dc.Cartentries.Attach(ce);
dc.CartEntries.DeleteOnSubmit(ce);
// Insert the cart into database
dc.Carts.InsertOnSubmit(c);
dc.SubmitChanges();
Explaination of the issue: Both entities, c and ce, are not related to a data context - they are not being tracked. EntitySet.Remove() (first delete line) only removes the relation between c and ce. While c can exist without associated cart entries, ce can't exist without an assiciated cart because of a foreign key constraint. When submitting changes to the database, the disconnected ce is dealt with as well, causing a constraint violation and the exception.
In order to get rid of that untracked and disconnected cart entry you need to attach it to your data context (causing it to be tracked) and then mark it for delete on submit. The moment you submit your changes the cart entry will be deleted properly and not cause the exception.
For more details on that issue check this out:
http://msdn.microsoft.com/en-us/library/bb546187%28v=VS.100%29.aspx
you need to decouple the save code from the events in your GUI, it seems like you're a little to eager to save things to the db before the dust has settled and you're queuing and removing things from the db that never got there in the first place, it would be best if you could identify a point when the user will "commit" their changes, and at that moment, process the full condition of the GUI - this will save you a bunch of spaghetti code.
I would also be curious to know if your entities have autonumber IDs or if you're using some other ID mechanism. You're probably sending DELETEs to the database for the as-yet-uncommitted Ingredient records, if those include NULL IDs, I think the linq could get nasty.
Have you hooked up a textwriter to your DataContext.Log to see what sorts of SQL is generated just before you get your exeception?
Thank you for your answer, I will examine the posts and see what I can do. I must say I'm surprised to even see this problem occuring, it seems quite natural to me that one could add records to the LINQ-provided "cache" of data, then decide to erase some of them and then commit. Change tracking should be able to handle that. I just starting with LINQ so I might be doing a stupid mistake somewhere in the code (wouldn't be the first).
On the other note: You are quite correct that garlic can belong to many recipes (not my coctail recipes thought!). I actually model that with an Article object/table. But for a recipe, you need quantities. So in my model, you have a Recipe that has 1:n Ingredients, each of them having a Quantity, a 1:1 link to an Article (which has a Name, an AlcoholContent and some data to establish an interchangeability hierarchy) and a 1:1 link to an Unit (for the quantity to make sense).
So in a sense, Ingredient table makes a M:N relationship between Recipe and Article, and at the same time adding some additional information to each individual linked pair.
I had exactly the same problem. I had a parent / child hierarchy, and when adding and removing the child entity without saving to the database I received the "An attempt was made to remove a relationship" exception.
I discovered that this problem only arose when I set an object style property of the child to another linq-sql entity before saving. eg
1. This creates the error
RetailAccountCustomerCard racc = new RetailAccountCustomerCard();
Card addedCard = _idc.Cards.Where(c => c.CardId == card.CardId).ToList().First();
racc.Card = addedCard;
this.CurrentCustomer.RetailAccountCardsBindingList.Add(racc);
// Some code triggered by the user before saving to the db
CurrentCustomer.RetailAccountCardsBindingList.Remove(racc);
2. This doesn't create the error
RetailAccountCustomerCard racc = new RetailAccountCustomerCard();
racc.CardId = card.CardId; // note that I have set the Id property not the object
this.CurrentCustomer.RetailAccountCardsBindingList.Add(racc);
// Some code triggered by the user before saving to the db
CurrentCustomer.RetailAccountCardsBindingList.Remove(racc);
Strangely enough, the error that arises in 1. specifies the problem is to do with the relationship is on the RetailAccountCustomerId property of RetailAccountCustomerCard. IT HAS NOTHING to do with the Card object I added. It seems that simply setting any object property of the new entity triggers the problem.
NB. Example 1 works fine in terms of saving, it only causes a problem if the the new entity is deleted before saving.
I am running into a similar issue, as a workaround, I need to call DataContext.GetChanges(), then everything seems to have caught on again :)
Another problem you could have it that you are binding to columns and not entity properties, and hence the referential collections are not updated (already stated by someone else, but enforcing the fact).