It goes like this:
MyDbContext ctx = new MyDbContext();
IFooRepository repo = new FooRepository(ctx);
var items = repo.GetAvailableItem().ToList(); //this will query all item.sold = false.
// here it returns three rows
foreach(var item in items) {
item.sold = true;
}
repo.commit(); // this will call the SaveChanges() in DbContext
Thread.sleep(10000)
// Now I quickly execute a query in SQL Server Management Studio
// UPDATE Item SET Sold = 0;
var items02 = repo.GetAvailableItem().ToList(); // this will query all item.sold = false.
// here items02 also contains three rows
// HOWEVER, when I watch the value of item.sold in items02, it is all True
Is this the behavior by design?
Why? Is it because DbContext cache the entity and never refresh even if you run the same query again?
UPDATE
Here is the code in my repo:
public IQueryable<Item> GetAvailableItem()
{
var items = from x in DbContext.Item
where x.Sold == 0
select x;
return items;
}
public virtual int Commit()
{
return DbContext.SaveChanges();
}
OK. This is what happening:
Creating a new context.
Loading items from db by calling GetAvailableItem()
Context will load them, and also cache them.
Updating items via context. So: the db rows ARE updated, and the cached versions ARE updated too.
Updating items via pure sql, outside the context (through SSMS). So: the db rows ARE updated. But, since you are using the same context as before, and it has it's own version of items, and there is no way for it to know what's happening outside itself, so the cached version of items, stays how they were: ARE NOT updated.
If you want to your context know the changes outside itself, the easiest way is to create a new context and query again. Another way is to tell context explicity to re-load entities from db by yourContext.Entry<YourEntityType>(entityInstance).Reload();.
My guess is that your DbContext is not up to date with your changes that happened in the Database (you said that you are running an update during the Thread.sleep). DbContext won't pick up these updates (the data is cached).
That's why you want to have your lifescope of your context as short as possible to reduce concurrency. This is an expected behavior.
See this MSDN post
Related
I'm using the latest EF 6 version connecting to an SQL Server 2012 database. Everything has been working smoothly for months in a live deployment, but I recently added a feature where I'm calling SaveChanges() twice on the same context after adding two new entities.
I add an entity of type T to my context after model validation, then invoke SaveChanges() which succeeds without errors or validation problems. This entity is correctly inserted.
Using the same context, I then proceed to create a fresh instance of type T by copying over some of the values from the original (mostly primitive types, but a few references too). This too validates fully and SaveChanges() does not raise any errors, but maybe 50% of the time, the ID is still 0 and no SQL output is logged, so the entity is not inserted.
If I call Context.Entry(entity), it says the status is Added, and Context.ChangeTracker.Entries returns this new entity in the list of changes.
I created a test loop that continuously calls SaveChanges while the entity ID is 0, and it loops forever with no errors.
I'm stumped. Anyone have any bright ideas what I can try now?
Edit: to prove this is a legit issue, I changed the save code so that the second save happens on a brand new context, where I reload all the entities referenced by the second entity I want to insert. The insert succeeds every time. So code went from this broken code running on a single context:
// insert 'item' if model is valid, should only attempt second insert if first save is successful
if (ModelState.IsValid && this.SaveIsValid(item))
{
var next = item.NextRun(); // create a new run from existing run, copying some data
// save entity to db (insert or update)
this.SaveIsValid(next); // returns true=no errors, but entity NOT inserted
}
to this working code that uses two contexts:
// insert 'item' if model is valid, should only attempt second insert if first save is successful
if (ModelState.IsValid && this.SaveIsValid(item))
{
// ugly hack workaround because saving twice on the same context sometimes fails
var saved = db;
db = new Models.SanDbContext();
try
{
// create a new run in Running status, and redirect to edit it
var next = item.NextRun();
next.Machine = db.Machines.Find(next.Machine.Id);
next.Operator = db.Users.Find(next.Operator.Id);
next.PartCode = db.PartCodes.Find(next.PartCode.Id);
foreach (var m in next.Measurements)
m.InputType = db.MeasurementTypes.Find(m.InputType.Id);
foreach (var c in next.Coils)
c.Coil = db.SanCoils.Find(c.Coil.Id);
this.SaveIsValid(next); // returns true=no errors, entity inserted
}
finally
{
db.Dispose();
db = saved;
}
}
There should be no functional difference between the two that I can see, but one works every time, the other fails about half of the time.
Edit 2: per a suggestion in the comments, I'd like to clarify that I do NOT make use of any no tracking queries, so all entities are tracked.
Background: I'm using EF6 and Database First.
I'm run into a scenario that has me perplexed. After creating a new object, populating the Navigation Properties with new objects, and calling SaveChanges, the navigation properties are reset. The first line of code that references the navigation property after the SaveChanges call will end up re-fetching the data from the database. Is this expected behavior, and can someone explain why it behaves this way? Here is a sample code block of my scenario:
using (DbContext context = new DbContext) {
Foo foo = context.Foos.Create();
context.Foos.Add(foo);
...
Bar bar = context.Bars.Create();
context.Bars.Add(bar);
...
FooBar foobar = context.FooBars.Create();
context.FooBars.Add(foobar)
foobar.Foo = foo;
foobar.Bar = bar;
//foo.FooBars is already populated, so 1 is returned and no database query is executed.
int count = foo.FooBars.Count;
context.SaveChanges();
//This causes a new query against the database - Why?
count = foo.FooBars.Count;
}
I can't say 100%, but I doubt this behavior is made specifically.
As for why it behaves this way, the root of the issue is that DbCollectionEntry.IsLoaded property is false until the navigation property is either implicitly lazy loaded or explicitly loaded using Load method.
Also seems that lazy loading is suppressed while the entity is in Added state, that's why the first call does not trigger reload. But once you call SaveChanges, the entity state becomes Unmodified, and although the collection is not really set to `null' or cleared, the next attempt to access the collection property will trigger lazy reload.
// ...
Console.WriteLine(context.Entry(foo).Collection(e => e.FooBars).IsLoaded); // false
//foo.FooBars is already populated, so 1 is returned and no database query is executed.
int count = foo.FooBars.Count;
// Cache the collection property into a variable
var foo_FooBars = foo.FooBars;
context.SaveChanges();
Console.WriteLine(context.Entry(foo).State); // Unchanged!
Console.WriteLine(context.Entry(foo).Collection(e => e.FooBars).IsLoaded); // false
// The collection is still there, this does not trigger database query
count = foo_FooBars.Count;
// This causes a new query against the database
count = foo.FooBars.Count;
If you want to avoid the reload, the workaround is to explicitly set IsLoaded property to true.
// ...
context.SaveChanges();
context.Entry(foo).Collection(e => e.FooBars).IsLoaded = true;
context.Entry(bar).Collection(e => e.FooBars).IsLoaded = true;
// No new query against the database :)
count = foo.FooBars.Count;
Why would you expect it not to re-query the database? EF isn't holding a complete cached copy of your database. What if you have some trigger(s) doing a row insert after something you changed or some column that serves as a function column that EF doesn't know the exact calculation for? It needs to get the most recent data otherwise your Count won't be correct.
I think the issue is the concept of 'context' is being confused. You are in a 'connected' state when you are in the context of EF. It doesn't always care to reuse your data smartly. It knows "I exist as an extension of the database level through a context set up to talk to it." If you have an object you create or one already existing say 'FooBars' and you do this:
foo.Foobars.(some operation)
If foo is your context and Foobars is some object off of it, it is referenced as an extension of context. If you want to realize reuse without incurring roundtrip to the database collect an object or objects outside the scope of the context like:
var foobars= new Foobars;
using(var foo = new new DbContext)
{
foobars = foo.FooBars;
}
var cnt = foobars.Count();
Generally speaking with EF, I see a lot of people do something like have a whole 'using(var context = new Efcontext())' over an entire method that may be long and do this process over and over everywhere. It is not bad out of the box per say, but doing this everywhere I would simply say: "Do you need to keep a db connection open over and over again?" Sometimes you do, most of the time you do not though.
I am pre-loading product data on my website as I need this to be accessible fast and would rather this be loaded once on application start up rather than just when it's request. This is one way to make sure things load fast. I do this by eager loading my product entity alongside some other entities and store them in memory.
I then have something like the following to retrieve my data:
public override IEnumerable<Product> Get()
{
var result = _cacheManager.Get(PreLoadCacheKey) as IEnumerable<Product>;
if (result != null)
return result;
return base.Get();
}
The issue I am facing is that with me using dependency injection, I don't control the scope of my data context (other than setting it to InstancePerRequest() and leaving it be) so when I cache my entities they still have a reference to the original context.
I've tried various things but can't seem to get around the issue of:
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
when trying to attach an entity retrieved from the cache to a new entity I am adding to the database.
Things I've tried:
Retrieving the data using AsNoTracking()
Looping through all the results before adding them to the cache and Detaching them from the current context then attaching them to the new context on retrieval from the cache.
Ideally, I'd like to be able to detach a context from an entity from the side of the entity so I could see if the entity is attached to the current context and if not, detach it from the old one and attach it to the new one. From what I've seen though, the only you can detach it though is by having a reference to the old context which I won't have.
Am I missing something obvious?
Edit
This is one example where I'd attach the entity to another context:
var orderLine = order.Lines.FirstOrDefault(ol => ol.Product.Id == product.Id) ?? new SalesOrderLine();
orderLine.Price = price.Price;
orderLine.Product = product; //This is my cache'd entity.
orderLine.ProductPriceType = price.ProductPriceType;
orderLine.Quantity += qty;
order.Customer = customer;
order.Lines.Add(orderLine);
order.Status = SalesOrderStatus.Current;
Update(order);
SaveChanges();
As it stands at the moment, I am forcibly loading an uncached version to get around this issue.
Retrieving the cached data with AsNoTracking is the first step. But (apparently) the objects in the cache are created as proxies, so they are aware of having been created by another context.
So make sure that no proxies, but just POCO's are cached, which you do by disabling proxy creation in the context that fetches the objects to the cache:
db.Configuration.ProxyCreationEnabled = false;
(where db is the DbContext instance).
Then make sure you attach the object to a new context before you use it there. This is important, because if you Add() a new OrderLine, EF may try to add a new Product as well.
I'd like to add that this would all be so much easier if you'd exposed OrderLine.ProductId in your entity model: just setting the Id value would be enough.
Since you can't control the life time of your context object i think it would make sense to always detach your product object at the time of loading and attach them when saving the them.
so when you your Get() method is called instead of just checking the cache manger and if not found returning from base.get() do the following
public override IEnumerable<Product> Get()
{
var result = _cacheManager.Get(PreLoadCacheKey) as IEnumerable<Product>;
if (result != null)
return result;
return DetachAndReturn();
}
private IEnumerable<Product> DetachAndReturn()
{
foreach (var product in base.Get())
{
base.Detach(product);
yield return product;
}
}
I have some code (more complex than in this example) where I don't understand why the FirstOrDefault call, which clearly round-trips to DB, is not returning a fresh entity from DB:
var dbContext = new MyDBContext();
// this will not round-trip to Db if found in context
var user = this.dbContext.EFUsers.Find(someUID);
// some other work here with 'user'
this.dbContext.ObjectContext.Detach(user);
// stuff in disconnected mode
var newUser = new EFUser() { UID = someUID };
// stuff in disconnected mode
this.dbContext.EFUsers.Attach(newUser);
// finish working with newUser
// (eg. deletion of many-to-many relation to avoid loading related entities in memory)
user = this.dbContext.EFUsers.FirstOrDefault(us => us.UID == someUID);
// I would expect at this point that 'user' will be queried with fresh values from DB,
// In reality, I get back only the entity with UID filled in, 'newUser' from above
// some update user goes here which fails because of the above
dbContext.SaveChanges();
I thought Find will return me the user from the context, if available, but FirstOrDefault always a user with fresh values from database.
Can anyone explain? How would I overcome this effect?
Worth reading this http://msdn.microsoft.com/en-us/data/jj573936,
"When results are returned from the database, objects that do not
exist in the context are attached to the context. If an object is
already in the context, the existing object is returned (the current
and original values of the object's properties in the entry are not
overwritten with database values)."
the load and reload methods are worth a read too.
Sounds like you want that behaviour.
http://msdn.microsoft.com/en-us/data/jj592911.aspx
http://msdn.microsoft.com/en-us/library/system.data.entity.infrastructure.dbentityentry.reload(v=vs.113).aspx
I am using EF 4.5 and have to fetch some records and update a flag of the fetched record. Can I do this in same context?
using (var context = new MyDBEntities())
{
var qry = from a in context.Table1
select a;
foreach(var item in qry)
{
// Logic to fill custom entity (DTO) from qry
item.Fetched = 2; // Changing the status of fetched records in DB
context.Table1.Add(item);
}
context.SaveChanges();
}
You can. You have to, even. The context must have the objects you modify in its change tracker. That's what happens in the query (qry). Then you modify them, the change tracker detects changes when SaveChanges() is called and update statements are generated and send to the database.
The only thing is, you should not do
context.Table1.Add(item);
because that creates new Table1 records, it does not update the existing ones. Just remove the statement.
(By the way, there are other ways to make a context start change tracking, viz. attaching or adding objects to a context).