Entity Framework: Attached Entities not Saving - entity-framework

I can't figure out why calling SaveChanges() on the following code results in no changes to the objects I attached:
public void Update()
{
AccountUser accountUser = new AccountUser();
// update
using (var db = new MedicalSystemDBEntity())
{
var query = from user in db.AccountUsers
where user.id == this.UserID
select user;
if (query.Count() > 0)
{
accountUser = query.First();
accountUser.AccountRoles.Load();
accountUser.SecurityNavigationNodes.Load();
// delete existing user roles before re-attaching
if (accountUser.AccountRoles.Count > 0)
{
foreach (AccountRole role in accountUser.AccountRoles.ToList())
{
accountUser.AccountRoles.Remove(role);
}
}
db.SaveChanges();
// get roles to add
List<int> roleIDs = new List<int>();
foreach (UserRole r in this.AccountRoles)
{
roleIDs.Add(r.RoleID);
}
var roleEntities = from roles in db.AccountRoles
where roleIDs.Contains(roles.id)
select roles;
accountUser.AccountRoles.Attach(roleEntities);
accountUser.username = this.Username;
accountUser.firstname = this.FirstName;
accountUser.middlename = this.MiddleName;
accountUser.lastname = this.LastName;
accountUser.enabled = this.Enabled;
if (this.LastActivityDate != null || this.LastActivityDate != DateTime.MinValue)
accountUser.lastactivitydate = this.LastActivityDate;
db.SaveChanges();
}
}
}
In the debugger, I see that the correct roleEntities are being loaded, and that they are valid objects. However, if I use SQL Profiler I see no UPDATE or INSERT queries coming in, and as a result none of my attached objects are being saved.

They're not saving because you change the entities before attaching them. Changes are tracked by the context (usually), so changes to detached entities aren't tracked. Hence, nothing to save.

Off the top of my head, shouldn't you do SaveChanges() after you've removed the roles from the account? Aren't you just removing the roles attached to the user, then reattaching the same ones? Since its saving the changes, nothing would have changed would it?

Sometimees its because the entit objects were modified while detached. The post here will show how to clear that up.

Related

.NET Core - EF - The instance of entity type 'User' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked

I know there are already answers for this one, but none of them worked so far
EF keeps returning the error :
The instance of entity type 'User' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked.
although I disabled traking using QueryTrackingBehavior.NoTracking
I tried _context.Entry(user).State = EntityState.Detached;
I also tried
services.AddDbContext<SomeDBContext>(opt =>
opt.UseSqlServer(Configuration.GetConnectionString("***********"))
.EnableSensitiveDataLogging()
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
I cannot even figure which line produces the error
[HttpPost]
public async Task<ActionResult<int>> PostPartner(PartnerCreation partnerCreation)
{
_context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
_context.ChangeTracker.AutoDetectChangesEnabled = false;
var user = await _context.Users.FindAsync(1);
_context.Entry(user).State = EntityState.Detached;
if (user == null)
{
return NotFound();
}
else
{
partnerCreation.Partner.Id = partnerCreation.Partner.Id > -1 ? partnerCreation.Partner.Id : null;
partnerCreation.Manager.Id = partnerCreation.Manager.Id > -1 ? partnerCreation.Manager.Id : null;
partnerCreation.Partner.retributionPlan.Id = partnerCreation.Partner.retributionPlan.Id > -1? partnerCreation.Partner.retributionPlan.Id : null;
partnerCreation.Partner.retributionPlan.PartnerId = partnerCreation.Partner.retributionPlan.PartnerId > -1 ? partnerCreation.Partner.retributionPlan.PartnerId : null;
partnerCreation.Partner.ModificationUser = user;
partnerCreation.Partner.ModificationDate = DateTime.Now;
foreach (ProductRetributionData data in partnerCreation.Partner.retributionPlan.ProductRetributionData)
{
data.Id = null;
}
partnerCreation.Partner.CreationDate = DateTime.Now;
_context.Partners.Add(partnerCreation.Partner);
_context.Users.Add(partnerCreation.Manager);
await _context.SaveChangesAsync();
return partnerCreation.Partner.Id;// CreatedAtAction("GetPartners", new { id = partner.Id }, partner);
}
}
thanks for helping me on this
The Add method causes the provided entities to be set to the Added state. So removing the user from the ChangeTracker has little effect, as in _context.Partners.Add(...) it is added again. From then on, whenever another untracked User is added with the same id, it would cause the error. This could happen in _context.Users.Add(...).
If you step through the code using the debugger you should be able to identify the line causing the error.
See the Change Tracking
docs for further information.

Can I configure Audit.NET to create audit records in two tables on for one audit event?

I am currently implementing Audit.NET into an ASP.NET Core Web API project that is using EF Core. I am using the Entity Framework Data Provider and currently have it configured to map all entities to a single audit log (AuditLog) table with the code below.
Audit.Core.Configuration.Setup()
.UseEntityFramework(_ => _
.AuditTypeMapper(t => typeof(AuditLog))
.AuditEntityAction<AuditLog>((ev, entry, audit) =>
{
audit.Date = DateTime.UtcNow;
audit.AuditData = JsonConvert.SerializeObject(entry);
audit.UserIdentifier = userId;
})
.IgnoreMatchedProperties(true));
This is working great, however, I would like to write audit entries to the BlogApprovals table if the entity type is Blog - in addition to the entry getting added to AuditLog. So for a Blog entity I would like an audit record in both BlogApprovals and AuditLog. Is this possible?
Not really, since the EntityFrameworkDataProvider is designed to map each entity to only one audit entity.
But you could trigger the extra insert, after the operation is complete, by using an OnSaving Custom Action, like this:
Audit.Core.Configuration.AddOnSavingAction(scope =>
{
// OnSaving event fires after context SaveChanges
var efEvent = (scope.Event as AuditEventEntityFramework)?.EntityFrameworkEvent;
if (efEvent != null && efEvent.Success)
{
foreach (var e in efEvent.Entries)
{
if (e.Table == "Blogs" && e.Action == "Insert")
{
// there was an insert on blogs table, insert the blogapproval
var ctx = efEvent.GetDbContext() as MyContext;
ctx.BlogApproval.Add(new BlogApproval() { Note = "note..." });
(ctx as IAuditBypass).SaveChangesBypassAudit();
}
}
}
});

EF Core in memory, delete unit testing giving error: entity already tracked

I am working with entity framework core in memory to test my code.I created in memory context and added data. I have delete method which is going to delete record with id=1. Test method is throwing error following error:
The instance of entity type 'cats' cannot be tracked because another instance with the key value '{Id: 204}' is already being tracked When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Could any one help me , how to resolve this issue. I tried different scenarios on internet, but nothing did not work.
Test.cs:
[Fact(DisplayName ="Delete Service Must Return Result")]
public void DeleteService()
{
var serviceId = _collectionFixture.context.cat.Where(x => x.Id == 201).AsNoTracking();
var okObject = _collectionFixture.CatController.DeleteService(serviceId) as OkObjectResult;
var baseResponse = okObject.Value as BaseResponse;
Assert.True(baseResponse.Success);
}
my service class:
public catResponse DeleteService(int serviceId)
{
try{
var serviceDto = _Context.catserviceTreatment.Where(x => x.Id
==serviceId).AsNoTracking().firstordefault();
if(serviceDto!=null)
{
this._Context.catserviceTreatment.Remove(serviceDto);
this._Context.SaveChanges();
return new catResponse { Success = true, Error = "service
deleted successfully" };
}
}catch(exception e)
{
}
}
my collection fixture:
public catContext GetDbContext()
{
var options = new DbContextOptionsBuilder<catContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
.ConfigureWarnings(x =>
x.Ignore(InMemoryEventId.TransactionIgnoredWarning))
// .ConfigureWarnings(x=>x.Ignore(InMemoryEventId.))
.EnableSensitiveDataLogging()
.Options;
var context = new CatDirectoryContext(options);
var catCategory = new List<catcategory>()
{
new catCategory{Id=100,CategoryName="Youth
teereafjkd",UpdatedBy="Test",UpdatedWhen=DateTime.Now},
new catCategory{Id=101,CategoryName="Adult
fdsafd",UpdatedWhen=DateTime.Now},
};
context.catcategory.AddRange(catCategory);
context.SaveChanges();
var catService = new List<catserviceTreatment>()
{
new catserviceTreatment{Id=200,Name="House of
Hope",Company="",
Address ="2001 st street",City="alone
city",State="UT",ZipCode=8404,Category=null,
CategoryId =100,Description="this is
description",UpdatedBy="test",UpdatedWhen=DateTime.Now},
new catserviceTreatment{Id=201,Name="Odyssey
House",Company="",
Address ="2001 st",City="bare
city",State="UT",ZipCode=84dfd,Category=null,
CategoryId =101,Description="this is d
description",UpdatedBy="test",UpdatedWhen=DateTime.Now},
}
context.catserviceTreatment.AddRange(catService);
context.SaveChanges();
return context;
}
Most probably you made some query and then navigate through the results of that query and try to delete the found items.
Something like:
var catsToDelete = myContext.Cats
.Where(cat => cat.Id == 204);
foreach (var cat in catsToDelete) {
myContext.Cats.Remove(cat);
}
It will not work this way because each item is still tracked by Entity Framework.
To avoid that tracking you need either to add ToList() or AsNoTracking() call at the end of the LINQ query:
var catsToDelete = myContext.Cats
.AsNoTracking()
.Where(cat => cat.Id == 204);
foreach (var cat in catsToDelete) {
myContext.Cats.Remove(cat);
}
I had the same error.
During setup I added some test records starting with id 1.
It appeared that the in memory database also starts numbering at 1 when new records are added, causing the "entity already tracked" error.
After I changed my initialization code to use ids as of 101, the problem was solved.

Updating in Entity Framework - The following objects have not been refreshed because they were not found in the store

I'm saving an Order object using the following code:
public void SaveOrder (string orderNo)
{
using (var se = new StoreEntities)
{
var order = new Order { OrderNumber = orderNo }
try
{
//// Update
if (se.Orders.Any(e => e.OrderNumber == orderNo))
{
se.Orders.Attach(order);
se.ObjectStateManager.ChangeObjectState(order, EntityState.Modified);
}
//// Create
else
{
se.Orders.AddObject(order);
}
se.SaveChanges();
}
catch (OptimisticConcurrencyException){
se.Refresh(RefreshMode.ClientWins, order);
se.SaveChanges();
}
}
}
This works fine when it's a new order and I'm just inserting into the DB.
However, if I'm trying to update an existing order, I get his error:
The following objects have not been refreshed because they were not
found in the store: 'EntitySet=Orders;OrderID=0'.
In the database, the Order table looks like
OrderID | OrderNumber
13 567-87
15 567-93
where OrderID is an Identity key. There are no other rows besides these two as they have been deleted.
What am I doing wrong that I can't update a record?
Looks like you're getting the error because you're attaching the new Order object you've created as if it's an Order which already exists - that's why the OrderID in the error is zero.
Try this:
try
{
//// Update
var existingOrder = se.Orders.FirstOrDefault(e => e.OrderNumber == orderNo);
if (existingOrder != default(Order))
{
existingOrder.DateLastUpdated = DateTime.Now;
se.ObjectStateManager.ChangeObjectState(existingOrder, EntityState.Modified);
}
//// Create
else
{
se.Orders.AddObject(order);
}
se.SaveChanges();
}
Edit
You can update the order details property-by-property, or if you have an Order (or an OrderViewModel, perhaps) with the details you want to update, you could use something like AutoMapper to copy the values for you.

EF SaveChanges() throws an exception 'The EntityKey property can only be set when the current value of the property is null'

I have been reading all similar posts I found regarding EF but I can't still manage to found a solution.
I'm very new to the EF and I have been reading some useful info about working with it but I think that I'm missing something.
The scenario is like this:
I want a user to be able to create an account in an ASP.NET webpage. So I have a table named 'Accounts'. The user must agree with the condition terms of the site, that may be updated in the futere, so I have also a table called 'ConditionTerms' that has 1 to many relation with the account (many accounts have an unique condition term).
I wanted to separete the specific personal user data from the data of the account so I also created a table called 'Persons' and I set the relation ship so that a person may have many accounts.
Now, when I want to save an account into the database, I retrieve the last conditionTerm available in the database and I attach it to the account entity. Then when I try to save the data via SaveChanges() I get the exception mentioned in the title of the post. The thing is that if all entities are new, when the associations are created, the EntityState for all the items is 'Detached' and it works, but when I retrieve the existing conditionTerm from the data base and I add it to the account.ConditionTerm, the account changes its state to 'Added' and then it throws the exception.
I read somewhere that when this happens, it means that all the entity tree is considered as already added by the context and I should only need to call SaveChanges() without the AddObject() method since it is already added. I tried this and then I get no exception and the code ends, but then if I check the database (SQL Server 2008 express) the account hasn't been added at all.
This is the code I'm trying and I think it should work but it's clear that I'm missing something:
[TestMethod]
public void TestCreateNewAccount()
{
try
{
AccountRepository accountRepository = new AccountRepository();
Account account = new Account()
{
Username = "TestUsername",
Password = "TestPassword",
Email = "TestEmail",
Nickname = "TestNickName",
Quote = "Test Quote",
AgreedToTermsDate = DateTime.Now,
CreationDate = DateTime.Now,
LastUpdateTime = DateTime.Now
};
// This works (all new entities!)
//ConditionTerm conditionTerm = new ConditionTerm()
//{
// Text = "This is some test condition term.",
// CreationDate = DateTime.Now,
// LastUpdateTime = DateTime.Now
//};
//This does not work (attaching an existing entity to a new one)
ConditionTerm conditionTerm = new ConditionTermsRepository().GetCurrentTerm();
Person person = new Person()
{
FirstName = "TestName",
Surname = "TestSurname",
CreationDate = DateTime.Now,
LastUpdateTime = DateTime.Now
};
account.ConditionTerm = conditionTerm;
account.Person = person;
using (ImproveEntities entities = Connection.GetModel())
{
if (account.ID > 0)
{
Account newAccount = new Account();
newAccount.ID = account.ID;
entities.Accounts.Attach(newAccount);
entities.Accounts.ApplyCurrentValues(account);
}
else
{
entities.Accounts.AddObject(account);
entities.SaveChanges();
}
}
}
catch (Exception)
{
}
}
Any help would be very much apreciated!
EDIT: This is the GetCurrentTerm() method:
public ConditionTerm GetCurrentTerm()
{
using (ImproveEntities entities = Connection.GetModel())
{
ConditionTerm conditionTerm = (from ct in entities.ConditionTerms
orderby ct.CreationDate descending
select ct).FirstOrDefault();
return conditionTerm;
}
}
If I understand correctly you want to insert a new account along with a new user into the database. But you don't want to create a new ConditionTerm but instead assign an existing ConditionTerm to the account.
The problem is that you fetch the existing ConditionTerm in another context (in your GetCurrentTerm) than the context you use for saving the new account. This second context doesn't know anything about the ConditionTerm, so you must EF explicitely tell that this conditionTerm already exists by attaching it to the second context:
// ...
using (ImproveEntities entities = Connection.GetModel())
{
entities.ConditionTerms.Attach(conditionTerm);
account.ConditionTerm = conditionTerm;
account.Person = person;
entities.Accounts.AddObject(account);
entities.SaveChanges();
}
// ...