I'm changing an entity by hand and after that I'm trying to verify if there is any entity in my DbContext that matches with my changes. The "answer" I expected was "true", however it's "false".
As my code is very complex and with many rules I've created a simple example to try to explain the problem:
var propertyValues = new Dictionary<string, object>()
{
{"MyProperty1", "My value"},
{"MyProperty2", 10}
};
var entityId = 13;
var entityType = typeof(MyEntity);
// Applies the changes
this.ApplyChanges(db, entityType, entityId, propertyValues);
// This is "false"
var hasEntityWithValue = db.MyEntity.Any(p => p.Id == entityId && p.MyProperty1 != null);
// The "myEntity" is null
var myEntity = db.MyEntity.FirstOrDefault(p => p.Id == entityId && p.MyProperty1 != null);
// Gets the entity only by Id
myEntity = db.MyEntity.FirstOrDefault(p => p.Id == entityId);
// And when I compare the "MyProperty1" it's "true". Why?????
hasEntityWithValue = myEntity.MyProperty1 != null;
The "ApplyChanges" method:
private void ApplyChanges(DbContext db, Type entityType, int entityId,
Dictionary<string, object> propertyValues)
{
var entity = db.Set(entityType).Find(entityId);
foreach (var propertyValue in propertyValues)
{
var propertyInfo = entityType.GetProperty(propertyValue.Key);
// Sets the value
propertyInfo.SetValue(entity, propertyValue.Value);
}
db.ChangeTracker.DetectChanges();
}
I believe this is happening because when I query the entities I'm querying them in the database instead of the EntityFramework "cache".
But is there a way to force EntityFramework identify the changes when I query the entities in the DbContext by using the IQueryable extension methods (such as "Any" and "FirstOrDefault" methods)?
You're right. When you use 'Any', 'FirstOrDefault' or any other Linq extension methods that look for data a SQL query is used. Because of this, any changes to the objects are not seen (for filtering purposes) unless you call 'SaveChanges'.
There is a way to look at materialized objects, but you'll have to do it manually. You'll have to make a Linq-to-Objects query ONLY on the materialized objects to see if what you want is there. An then, if it's not, make a regular Linq-to-Entities query searching it in the database. DO NOT mix these queries, or you may unleash hell.
To search the materialized objects:
context.ChangeTracker.Entries<MY_ENTITY>(); // strongly-typed, just an objects set
or
context.ChangeTracker.Entries(); // everything
Let look at the first two statements:
var entityId = 13;
...
// This is "false"
var hasEntityWithValue = db.MyEntity.Any(p => p.Id == entityId && p.MyProperty1 != null);
// The "myEntity" is null
var myEntity = db.MyEntity.FirstOrDefault(p => p.Id == entityId && p.MyProperty1 != null);
Both of these sends the same query to the database:
SELECT * FROM MyEntities WHERE ID = 13 AND MyProperty1 IS NOT NULL
This returns no records from the database because the database does not yet have the new data - there are no records saved in the database with an ID of 13 where MyProperty1 IS NOT NULL. This is because you have not yet called db.SaveChanges(). The first statement turns the result of that SQL statement into a value of false, whereas the second statement turns it into a value of null.
Moving on to the next statement:
// Gets the entity only by Id
myEntity = db.MyEntity.FirstOrDefault(p => p.Id == entityId);
This sends a query to the database like this:
SELECT * FROM MyEntities WHERE ID = 13
The database does have a MyEntitiy with an ID of 13, and it returns that MyEntity to EF. However, before EF returns the MyEntity to you, EF checks to see if it has a MyEntity with an ID of 13 in its cache. It does have a cached MyEntity with an ID of 13, so it sends the cached MyEntity. And the cached MyEntity just so happens to be the one you updated in your call to your custom ApplyChanges method.
// And when I compare the "MyProperty1" it's "true". Why?????
hasEntityWithValue = myEntity.MyProperty1 != null;
The reason this is true is because the entity returned to you is the one that is in the EF cache.
When you make a query with EF, it will send the query to the database, and if records are returned from the database, EF will check it's cache to see if records with the same key are in the cache. If they already exist in the cache, the cached records will be returned in place of the records that were found in the database, even if the cached records are different from the database records. (for more information on how to get around this caching, see http://codethug.com/2016/02/19/Entity-Framework-Cache-Busting/)
The cache checking is done at the point the query is run. So you could make this call and you would see your updated data:
var data = db.MyEntity
.Where(p => p.Id == entityId)
.ToList()
.Where(p => p.MyProperty1 != null);
The first Where function is processed by the database. The second is processed in memory wherever your C# code is running. The ToList call forces the query that has been built so far to be sent to the database and run before any more filtering or sorting is done.
You can also use a transaction for this, but as you mention, this will lock up resources for the duration of the transaction. Assuming you are working with EF6, you can do this:
using (var transaction = db.Database.BeginTransaction())
{
// Applies the changes
this.ApplyChanges(db, entityType, entityId, propertyValues);
db.SaveChanges();
// Should be true
var hasEntityWithValue = db.MyEntity.Any(p => p.Id == entityId && p.MyProperty1!=null);
// At this point, queries to the database will see the updates you made
// in the ApplyChanges method
var isValid = ValidateSave();
if (isValid)
{
// Assuming no more changes were made since you called db.SaveChanges()
transaction .Commit();
}
else
{
transaction .Rollback();
}
}
After I talked to my coworkers we decided to do something similar with the first suggestion of the #CodeThug. So I'm going to change the points of my code that queries "MyEntity" using "Linq to entities" to materialize the entity:
myEntity = db.MyEntity.First(p => p.Id == entityId);
var hasEntityWithValue = myEntity.MyProperty1 != null;
Related
I know there is a way to see SQL that will be executed when querying for data from a database. For example, when query is created I can use query.ToQueryString() (based on this answer from SO):
var query = _context.Widgets.Where(w => w.IsReal && w.Id == 42);
var sql = query.ToQueryString();
But is there a way to see SQL that will be executed when SaveChanges() is called without actually executing SaveChanges()?
Code example that gets data from db and updates one field:
var context = new AppContext();
var author = context.Authors
.Include(a => a.Books)
.FirstOrDefault(a => a.AuthorId == 1);
// update some property
author.Books[0].BasePrice += 2;
// here I want to see SQL that will be generated to
// update the database when I call context.SaveChanges()
var sql = ...?
This question is an abstract of the problem that I am facing: consider the below,
I have entities with the following properties
public class Entity
{
public int Id { get; set; }
public int Status { get; set; }
}
Status can be 0 (New) or 1 (Deleted).
I have a service call which alters the entity from 0 -> 1 which is called through the service. The repository involved does not call context.SaveChanges(), instead this is handled farther up the chain in middleware.
This looks like:
var entity = await Context.Entities.FirstOrDefaultAsync(x => x.Id == id);
entity.SetDeleted();
Where the SetDeleted method changes the property from 0 to 1.
Then, in another service method (in the same unit of work) this is read to check if there is an entity which isn't deleted. This is where the problem occurs.
The code looks like:
var entities = context.Entities.Any(x => x.Status != 1);
Usually you would expect the graph to not be returned, but unfortunately it is. Strangely enough when you execute the ToList() function and then run the same operation it doesn't include the graph. This means that if you have the below:
var entities = await Context.Entities.Where(x => x.Status!= 1).ToListAsync();
var secondFilter = entities.Where(x => x.Status != 1).ToList();
The entities will contain the entity, the secondFilter will not contain the entity despite it being subject to the same rules.
Has anyone had this problem, does anyone know a solution?
This is by-design behavior of EF.
When you have a tracked entity, and query the same entity from the database, the tracked entity is never overwritten.
After
var entity = await Context.Entities.FirstOrDefaultAsync(x => x.Id == id);
entity.SetDeleted();
You have one tracked entity, with a modified Status.
This
var entities = await Context.Entities.Where(x => x.Status!= 1).ToListAsync();
Runs a SQL query and finds the same entity as its Status=0 in the database. But when when EF tries to add the entity to the change tracker it discovers that there's a modified version already there, and so it discards the data read from the database and adds the existing modified entity to the entities collection. Then this
var secondFilter = entities.Where(x => x.Status != 1).ToList();
returns nothing because entity has Status=1.
I'm using Entity Framework from a couple of years and I have a little problem now.
I add an entity to my table, with
Entities.dbContext.MyTable.Add(obj1);
and here ok.
Then, I'd like to make a query on MyTable, like
Entities.dbContext.MyTable.Where(.....)
The code above will query on my MyTable in the db.
Is there a way to query also on the just added value, before the saveChanges? (obj1) How?
UPDATE
Why do I need this? Because, for each new element I add, I need to edit some values in the previous and the next record (there is a datetime field in this table)
UPDATE2
Let's say I have to add a lot of objects, but I call the saveChanges only after the last item is added. Every time I add the new item, I read its datetime field and I search in the database the previous and the next record. Here, I edit a field of the previous and of the next record. Now, here is problem: if I insert another item, and, for example, the next item is "Obj1", I have to find and edit it, but I can't find it since I haven't saved my changes. Is it clearer now?
You should be able to get your added entities out of the dbContext via the change tracker like this:
var addedEntities = dbContext.ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added && x.Entity is Mytable)
.Select(x => x.Entity as MyTable)
.Where(t => --criteria--);
Or using the type testing with pattern matching in c# 7.0:
var addedEntities = dbContext.ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added && x.Entity is Mytable t && --test t for criteria--)
.Select(x => x.Entity as MyTable);
because you are only querying added entities, you can combine this with
dbContext.MyTable.Where(t => --criteria--).ToList().AddRange(addedEntities);
to get all of the relevant objects
I think this is a good situation for Transactions. I am going to assume you are using EF 6 since you did not provide a version. =)
UPDATE2 changes
public void BulkInsertObj(List<TEntity> objList)
{
using (var context = new dbContext())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
foreach(var obj1 in objList)
{
dbContext.MyTable.Add(obj1);
//obj1 should be on the context now
var previousEntity = dbContext.MyTable.Where(.....) //However you determine this
previousEntity.field = something
var nextEntity = dbContext.MyTable.Where(.....) //However you determine this
nextEntity.field = somethingElse
}
context.SaveChanges();
dbContextTransaction.Commit();
}
catch (Exception)
{
dbContextTransaction.Rollback();
}
}
}
}
MSDN EF6 Transactions
I have "static" readonly entities which I simply load with QueryOver<T>().List<T>(). All their properties are not "lazy". So some of them have N+1 problem.
I tried to use Future to avoid N+1. But it looks like then NH considers entity properties as "lazy". And when I access them it even reloads entities from db one by one (leading to the same N+1 situation) despite that all entities were preloaded preliminary and should be cached in the session 1st level cache. Here is the code how I'm doing it:
var futures = new List<IEnumerable>();
futures.Add(s.QueryOver<DailyBonus>().Future<DailyBonus>());
futures.Add(s.QueryOver<DailyBonusChestContent>().Future<DailyBonusChestContent>());
// ... other entities ...
// all queries should be sent with first enumeration
// but I want to ensure everything is loaded
// before using lazy properties
foreach (IEnumerable future in futures)
{
if (future.Cast<object>().Any(x => false)) break;
}
// now everything should be in cache, right?
// so I can travel the whole graph without accessing db?
Serializer.Serialize(ms, futures); // wow, N+1 here!
I checked this behavior using hibernatingrhinos profiler.
So what is going on wrong here?
The only correct way of using futures for loading entity with collections is with Fetch which means joins for each query:
var q = session.Query<User>().Where(x => x.Id == id);
var lst = new List<IEnumerable>
{
q.FetchMany(x => x.Characters).ToFuture(),
q.Fetch(x=>x.UpdateableData).ToFuture(),
session.QueryOver<User>().Where(x => x.Id == id)
.Fetch(x=>x.Characters).Eager
.Fetch(x => x.Characters.First().SmartChallengeTrackers).Eager
.Future()
};
var r = session.QueryOver<User>().Where(x => x.Id == id)
.TransformUsing(Transformers.DistinctRootEntity)
.Future();
foreach (IEnumerable el in lst)
{
foreach (object o in el)
{
}
}
return r.ToArray();
It's still better than join everything in a one query - NHibernate won't have to parse thousands of rows introduced by join x join x join x join...
You can add normal select queries (without Fetch) to to the same batch but they won't be used for retrieving collections on another entity.
I am just starting to use linq to sql for data access. It is working fine for read only. But it does not work for update. I have been reading the threads over several forums. It is clear that anonymous types (in my case var) cannot be updated. I cannot find what I should replace the var with and where I find it. I will appreciate any help.
Below is the code. The exception is
Error 1 Property or indexer 'AnonymousType#1.date_last_logon' cannot be assigned to -- it is read only
fmcsaDataContext db = new fmcsaDataContext();
// DataTable _UserTable;
UserModel _UserModel = new UserModel();
var users = from u in db.FMCSA_USERs
where u.USER_NAME == pName && u.ACTIVE == true
select new
{
date_last_logon = u.DATE_LAST_LOGON,
date_current_logon = u.DATE_CURRENT_LOGON,
failed_login = u.FAILED_LOGIN,
};
if (users.Count() == 0)
return null;
foreach (var user in users)
{
user.date_last_logon = user.date_current_logon;
}
This is the case for any ORM tool; you will have to use the entity types that LINQ-to-SQL generates for you when you make your .dbml file if you want to perform CRUD operations.
Also, be aware that your query is being executed twice and is not concurrently safe; calling Count() executes your query with a Count aggregate in the database, then looping over it executes the query again, this time bringing back results. Given what you're doing, this may be better:
var users = (from u in db.FMCSA_USERs
where u.USER_NAME == pName && u.ACTIVE == true
select u).ToList(); // This executes the query and materializes
// the results into a List<FMCSA_USER>
if (users.Count == 0) return null;
foreach (var user in users)
{
user.date_last_logon = user.date_current_logon;
}
db.SaveChanges();
In order to update data, you cannot use anonymous types.
Instead, you can end your query with select u; to select the actual entities.
Table you are trying to update using LINQ, should have Primary key.