EFCore 2.2.6 vs 5.0.8 noTracking - c#

We have an application which was originally running on EFCore 2.2.6 and .Net Core 2.1. We have the below code to query and delete data, it uses AsNoTracking:
var deletedDiag = _unitOfWork.GetRepository<DIAGNOSIS_DETAIL>()
.GetAll(c => c.Diag.CMS_CASE_ID == model.DiagnosisModel.CASE_ID)
.Include(i => i.Diag).AsNoTracking().ToList();
if (deletedDiag != null)
{
foreach (var del in deletedDiag)
{
if (!model.SUBDETAILS.Any(a => a.DIAGNOSIS_DETAIL_ID == del.DIAGNOSIS_DETAIL_ID))
{
_unitOfWork.GetRepository<DIAGNOSIS_DETAIL>().Delete(del);
}
}
}
And Delete does:
public void Delete(TEntity entity) => _dbSet.Remove(entity);
The deletedDiag query has a include to bring in Diag as a navigational property, above this block of code another query gets the same Diag and marks it for update.
In EFCore 2.2.6 the delete works fine, it's able to update the Diag and then run the delete even though the deletedDiag has a path to it, there are no errors. In EFCore 5.0.8, we get an error stating that the entity is already being tracked when we try to delete, the fix was to get the deletedDiag to stop tracking it but setting it to null:
deletedDiag.ForEach(x => x.Diag = null);
that works, but why does it work in 2.2.6?

This is due to the capricious path EF-core has taken right from the start in lots of features and defaults. It was impossible to keep track of all changes between versions, even though they were fairly well documented.
One of those changes in v3 onward was in the area of no-tracking behavior. In EF-core 2, AsNoTracking performed identity resolution, which ensures that all occurrences of an entity with a given key in the result set are represented by the same entity instance. In EF-core 3 this was abandoned: each occurrence of the "same" entity now is a new instance.
That means that in EF-core 2, the statements _dbSet.Remove(entity) repeatedly marked the same Diag instance as deleted: i.e. attached it when not yet attached and set its state to Deleted. That's not a problem.
EF-core 3+ tries to attach different Diag instances with the same key, which fails.
Fortunately, in EF core 5 the earlier tracking behavior can be restored by using AsNoTrackingWithIdentityResolution.
var deletedDiag = _unitOfWork.GetRepository<DIAGNOSIS_DETAIL>()
.GetAll(c => c.Diag.CMS_CASE_ID == model.DiagnosisModel.CASE_ID)
.Include(i => i.Diag)
.AsNoTrackingWithIdentityResolution()
.ToList();

Related

EF core Does not insert all records for InMemoryDatabase

Good day,
I am failing to insert multiple records in EF core using InMemory database. By failing I mean it works, but it seems that it does not insert all records.
This is my code to insert:
var f1= new F1[]{ /* Where I have populated this*/}
await context.F1.AddRangeAsync(f1);
var f2= new F2[]{ /* Where I have populated this*/}
await context.f2.AddRangeAsync(f2);
var f3= new F3[]{ /* Where I have populated this*/}
await context.F3.AddRangeAsync(f3);
var f4= new F4[]{ /* Where I have populated this*/}
await context.F4.AddRangeAsync(f4);
var f5= new F5[]{ /* Where I have populated this*/}
await context.F5.AddRangeAsync(f5);
var f6= new F6[]{ /* Where I have populated this*/}
await context.F6.AddRangeAsync(f6);
var f7= new F7[]{ /* Where I have populated this*/}
await context.F7.AddRangeAsync(f7);
await context.SaveChangesAsync();
Where each collection has a minimum of 2 items and a maximum of 100.
After I run this code:
var f1= await context.F1.ToListAsync();
var f2= await context.F2.ToListAsync();
var f3= await context.F3.ToListAsync();
var f4= await context.F4.ToListAsync();
var f5= await context.F5.ToListAsync();
var f6= await context.F6.ToListAsync();
var f7= await context.F7.ToListAsync();
And breakpoint after the f7 line and hover over the collections, I can see all of them have only part of the items.
This is how I create the in memory database:
services.AddDbContext<FamousDbContext>(opt => opt.UseInMemoryDatabase("MyFamousDatabase"));
What I have tried so far:
1. I have tried to save (trigger SaveChanges) each time an item/collection is added.
2. Tried not using async to insert.
3. tried inserting only one collection and then only asking for on DbSet.
4. Tried researching documentation on EF core.
5. Tried changing the name of the in memory database.
My Assumption is that this is a limitation of InMemory Core, but I cannot confirm this without enough knowledge and not finding anything mentioned in documentation or anywhere else.
So my question is how do I get around this? Is this a limitation of using InMemory Db? Or am I doing something wrong on this one?
UPDATE:
I am using .NET core 2.2 and EF InMemory version: 2.2.4
UPDATE SOLVED:
I have managed to solve the problem, it seems it was for the reason EF core InMemory does not really support relational database, though I read it I did not think it was because of this until I went through try and error sessio, source: https://learn.microsoft.com/en-us/ef/core/miscellaneous/testing/in-memory. Because when I try to pre-populated the data each of the objects bellow "f1" has a reference to the F1 type in the following way:
public int F1Id {get;set}
public F1 F1 {get;set;}
As soon as I removed the object reference from all the objects and left only the F1Id it worked like a charm.

EF 6 Adding row limit automatically

I have this below very simple function which returns list of rows from a table.
public IEnumerable<Configuration> GetConfigurations()
{
var tbl = Context.Configurations.AsNoTracking().Where(a => a.ActiveFlag == 'Y').ToList();
return tbl;
}
Issue is sometimes when IIS app-pool restarts, the above code creates below SQL:
SELECT
"Extent1"."ID" AS "ID",
"Extent1"."NAME" AS "NAME",
"Extent1"."VALUE" AS "VALUE",
"Extent1"."DESCRIPTION" AS "DESCRIPTION",
"Extent1"."ACTIVE_FLAG" AS "ACTIVE_FLAG",
"Extent1"."CATEGORY_ID" AS "CATEGORY_ID"
FROM "SCHEMANAME"."CONFIGURATIONS" "Extent1"
WHERE ('Y' = "Extent1"."ACTIVE_FLAG")
WHERE (ROWNUM <= (50) );
Notice the last line in there, there is no reason it to be there. application starts throwing exceptions as "SQL command not ended properly" when it happens, Just restarting the app-pool fixes the issue automatically.
Anybody knows why Entity Framework 6.1.3 would add extra where clause without any reason there? I use Oracle.ManagedDataAccess.EntityFramework version="12.1.2400" with "Oracle.ManagedDataAccess" version="12.1.24160419" package.
Changed the way EF loads the 1st table when application loads after app pool recycle as below fixed the issues.
Context.Configurations.Load();
var tbl = Context.Configurations.Local.Where(a => a.ActiveFlag == 'Y').ToList();
Issue didn't happened again.

Completely delete EF database in c#

I've been messing around with changing my object properties, and that requires me to keep updating my table, but I keep getting errors, so does anyone know how I can just delete the whole DB and start over?
I have this code
using (var ctx = new Context())
{
foreach (Item2 block in new_Items)
{
ctx.items_db2.Add(block);
}
ctx.SaveChanges();
test = (from b in ctx.items_db2
orderby b.Index
select b).ToList();
}
I've tried truncating the table but I can't do that since I've changed the object properties, and even when I run the commands from the package manager console to update the table, I get other errors so I'd just like to start from a clean slate.
You can do it from from your code just by running:
using (var ctx = new Context())
{
ctx.Database.Delete();
}
Alternatively connect to your database using VS - View --> SqlServer Object Explorer connect to your database server, right click on the database you want to delete and select Delete. You want to check the checkbox to close existing connection otherwise deletion may fail.
If the migrations are not important, I:
Delete migrations.
Delete all the tables in the database.
Run the Enable-migration command.
Run the Add-migration command, to add an initial migration.
Run the Upgrade-database command. This will rebuild all the tables in the db, and you back to square one :)
I use code first.

Why does the cached entities suddenly jump?

I have :
var c = cboCustomer.SelectedItem as Customer;
var t = cboTrailer.SelectedItem as Trailer;
using (var db = new CAPSContainer())
{
db.Attach(c); --> Tracker has now 1 entity
db.Attach(t); --> Tracker has now 2 entities
c.Trailers.Remove(t); --> Tracker has now 29! entities loaded
db.DeleteObject(t);
db.SaveChanges();
}
I am trying to understand how this loading / caching is working because I am having some other issues related to it, any ideas why the cached amount suddenly jumps?
I am using EF 5.0.
Your default configuration probably had LazyLoadingEnabled set. So child lists won't get loaded until they are accessed, see this article for a more detailed explanation:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
By the way, if you just want to remove one without loading the lot, then you can use DeleteObject as Boomer said.

Entity Framework, SQLite and Lazy loading

Hi I had developed a C# Budget application using SQL Compact and EF4, I created the EF model through the VS2010 Entity Data Model template. It is all working very well. However I am considering developing a iPhone app to support cash transactions and thought it would be better to have the back end DB supported on both platforms. After creating the SQLite DB and creating a new model I have come across a problem when trying to access referenced data via the Navigation properties in my model. I am getting a NullReferenceException when trying to display a property of a referenced table.
When using the following code I get the exception on the last line:
BudgetEntities budget = new BudgetEntities();
var accounts = budget.BankAccounts.ToList();
foreach (BankAccount a in accounts)
{
Console.WriteLine("Name:" + a.Description);
Console.WriteLine("Number:" + a.AccountNumber);
Console.WriteLine("Type:" + a.BankAccountType.AccountType); //Exception occurs here.
}
Strange thing is that the exception doesn't occur in this example. I'm not sure what is going on?
BudgetEntities budget = new BudgetEntities();
var accoutTypes = budget.BankAccountTypes;
var account = new BankAccount();
account.ID = Guid.NewGuid();
account.AccountTypeID = accoutTypes.First(t => t.AccountType.StartsWith("Credit")).ID;
account.BSB = "3434";
account.AccountNumber = "32323";
account.Description = "Test";
account.TrackingAccount = true;
budget.AddObject("BankAccounts", account);
budget.SaveChanges();
var accounts = budget.BankAccounts.ToList();
foreach (BankAccount a in accounts)
{
Console.WriteLine("Name:" + a.Description);
Console.WriteLine("Number:" + a.AccountNumber);
Console.WriteLine("Type:" + a.BankAccountType.AccountType); //Exception doesn't happen.
}
This is only a simple example and I know I could fix it by adding .Include("BankAccountTypes") to the query however I have other queries that are quite complex that are creating object which include properties from referenced object with in the query and I am not quite sure how to get around this issue for them.
EDIT:
After having a break between projects I have come back to this problem and I have finally resolved my problem. it had nothing to do with the code. It was with the data. I had converted a SQL Compact database to SQLite via a dump and load and had the syntax wrong for my Guid column data. I was inserting the Guid as '7cee3e1c-7a2b-462d-8c3d-82dd6ae62fb4' when it should have been x'7cee3e1c7a2b462d8c3d82dd6ae62fb4'
Hopefully the hair I pulled out working through this problem will grow back :)
Thanks everyone for your input.
In second example your code snippet begins with:
var accoutTypes = budget.BankAccountTypes;
This loads all bank account types to your application and you don't need lazy loading anymore (EF will automatically recognize that these entities were already loaded and fix relations with bank accounts).
First check if your account class is dynamic proxy (just check type of a in the debugger). If it is not you made some mistake in the class definition and lazy loading will not work. Next check if lazy loading is enabled on your context instance (budget.ContextOptions.LazyLoadingEnabled property).
Make sure the BankAccountType property is declared virtual in BudgetEntities.

Categories