About EntityFramework AsNoTracking - c#

I am using EntityFramework v6.1.2.
I read some articles and know about AsNoTracking extension.
When AsNoTracking is called, it means that if the entity is not attached, the context and the entity updated "should fail".
But I have tried and updated successfully, my code is in below:
private readonly DemoObjectContext _objectContext = new DemoObjectContext();
var order = _objectContext.Orders.AsNoTracking().FirstOrDefault(x => x.Id == 1);(1 is the primary key)
order.OrderStatus = OrderStatus.Processing; // change the orderstatus
//_objectContext.Set<Order>().Attach(order);
_objectContext.Entry(order).State = EntityState.Modified;
_objectContext.SaveChanges();
Is something wrong or did EntityFramework(6.1.2) changed something?
Please help me

No issues with EF. It works because Entry attaches the entity to the context.
See here for more details.
And on this SO specifically about Entry

Related

Auto detach entity from dbcontext

services.AddDbContextPool<SecurityDBContext>(options =>
options.UseSqlServer(GlobalConfig.Configuration["ConnectionStrings:DefaultConnection"],
b => b.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery))
);
This is how I added dbcontext,I used AddDbContextPool so the instance will be use over and over again for performance.
db.Entry(new AdminBlockClientConfig()
{
ActionId = input.aid.ToLongReturnZiro(),
MaxValue = input.value.ToIntReturnZiro(),
IsActive = input.isActive.ToBooleanReturnFalse(),
SiteSettingId = siteSettingId.ToIntReturnZiro()
}).State = EntityState.Added;
db.SaveChanges();
This is my code for adding new entity.
readonly SecurityDBContext db = null;
static List<AdminBlockClientConfig> AdminBlockClientConfigs = null;
public AdminBlockClientConfigService(SecurityDBContext db)
{
this.db = db;
}
This is my service constructor
services.AddScoped<IAdminBlockClientConfigService, AdminBlockClientConfigService>();
And this is my service injection config
The problem:
I don't have any validation for ActionId inside of my add new entity so if the user posts -1 for ActionId the entity will not be insert into SQL Server (relation problem) the system raises an exception and everything is as planed but the main problem is that one of the instance of SecurityDBContext become corrupted and I am no longer be able to call save change on that instance because the entity instance is still attached to dbcontext.
What is need:
It would be great if I can detach the entity after an error automatically so I can save the context.
I know how to detach entity from dbcontext, I need to its happen automatically (there is so many validation need to be added to project and I can not put time for those validation and if I put that time there will be high change of missing some place and its will be bug that can destroy my application and for performance I don't like to change the way I added dbcontext instance).
Thanks for your time.
edited: AddDbContextPool is not the problem, if one of my services add invalid data to dbcontext the other services can not use that dbcontext

EF Core Remove by ID

I was trying to delete a record - but I'm getting this error:
The instance of entity type 'Users' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values
I tried the following ways, but all failed with the same error.
var Users = context.Users.Where(x => x.Id.Equals(UsersId));
context.Users.RemoveRange(Users);
await context.SaveChangesAsync();
var Users = await context.Users.AsNoTracking().FirstOrDefaultAsync(x => x.Id.Equals(UsersId));
context.Users.Remove(Users);
await context.SaveChangesAsync();
var Users = new Users() {Id = UsersId};
context.Users.Attach(Users);
context.Users.Remove(Users);
await context.SaveChangesAsync();
The presented code is not enought for understanding the root cause.
If it is a web application you might have issue with sharing same database context accross all http requests. But db context should be uniq for each http request. Avoid to make it singleton.
If it is a console application - you might have an issue with a configuration.
Option 1:
Try to do this:
Context.Entry(entity).State = EntityState.Detached
This is not a solution, but just for troubleshooting.
Option 2:
Try to do a very simple Console application with your database context and run your first code snippet - it should work.
Can be usefull:
instance of entity type cannot be tracked because another instance with same key value is tracked

Attaching an entity of type "" failed because another entity of the same type already has the same primary key value

I have inherited some rather large code after a fellow employee left the company. Unfortunately the program broke the day after he left. Could anyone point me where to look with the following error?
Attaching an entity of type 'MasT.DB.jobqueue' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate. at
System.Data.Entity.Core.Objects.ObjectContext.VerifyRootForAdd(Boolean doAttach, String entitySetName, IEntityWrapper wrappedEntity, EntityEntry existingEntry, EntitySet& entitySet, Boolean& isNoOperation) at System.Data.Entity.Core.Objects.ObjectContext.AttachTo(String entitySetName, Object entity) at System.Data.Entity.Internal.Linq.InternalSet1.<>c__DisplayClassa.<Attach>b__9() at System.Data.Entity.Internal.Linq.InternalSet1.ActOnSet(Action action, EntityState newState, Object entity, String methodName) at System.Data.Entity.Internal.Linq.InternalSet1.Attach(Object entity) at System.Data.Entity.DbSet1.Attach(TEntity entity) at DT.ValidatorCore.JobQueue.MasTJobQueueMySql.<>c.b__23_7(jobqueue jq) at System.Collections.Generic.List1.ForEach(Action1 action) at DT.ValidatorCore.JobQueue.MasTJobQueueMySql.MaintainJobQueue() at DT.ValidatorCore.JobQueue.MasTJobQueueMySql.TakeJobsQueue(Boolean includeCompleted) at DT.ValidatorCore.JobQueue.MasTJobQueue.DoWork(String date, Boolean testRun, Int32 runId) at DT.ValidatorCore.Commands.TriggerCommand.QueueCommand.Process(CmdTrigger1 trigger) at DT.Common.Commands.BaseCommand1.TriggerSubCommand(CmdTrigger1 trigger) at DT.Common.Commands.Command1.Process(CmdTrigger1 trigger) at DT.Common.Commands.CommandMgr1.Execute(CmdTrigger1 trigger, BaseCommand1 cmd, Boolean silentFail)
I am very confused as this does not happen when running in debug, only when the program is running on the production server. While they connect to two separate databases, these are identical.
Initially I was only asked to update certain parts of the code so this is a big jump!
I am fairly certain the issue is with the DT.ValidatorCore.JobQueue.MasTJobQueueMySql.MaintainJobQueue, but I have very limited knowledge of entity framework
protected void MaintainJobQueue()
{
if (_jobQueueUnitOfWork != null)
_jobQueueUnitOfWork.Dispose();
_jobQueueUnitOfWork = new JobQueueUnitOfWork();
List<jobqueue> tempList = _jobQueueUnitOfWork.JobQueueRepository.GetAll();
if (tempList == null)
return;
tempList.RemoveAll(jqItem => jqItem == null);
tempList.RemoveAll(jqItem => jqItem.packageinfo == null);
tempList.RemoveAll(jqItem => jqItem.packageinfo.pkg_content_id == null);
if (!tempList.Any())
return;
var tempList2 = tempList.GroupBy(g => g.packageinfo.pkg_content_id + g.packageinfo.pkg_master_version + g.packageinfo.app_version).Select(x => x.ToList().OrderByDescending(m => m.packageinfo.app_revision).First()).ToList();
tempList.RemoveAll(i => tempList2.Contains(i));
tempList.ForEach(jq => context.jobqueue.Attach(jq));
var pkgInfoRemovals = tempList.Select(i => i.packageinfo);
_jobQueueUnitOfWork.PackageInfoRepository.DeleteRange(pkgInfoRemovals);
var submissionpathRemovals = tempList.Select(i => i.submissionpath);
context.submissionpath.RemoveRange(submissionpathRemovals);
_jobQueueUnitOfWork.SubmissionPathRepository.DeleteRange(submissionpathRemovals);
_jobQueueUnitOfWork.JobQueueRepository.DeleteRange(tempList);
}
protected override void SaveChanges()
{
_jobQueueUnitOfWork.Save();
}
Cheers!
It's hard to be sure as the code you've shared appears to be a wrapper built around Entity Framework and so obscures away some of the necessary detail but an educated guess says that you're dealing with Detached Entities.
The keyword to search for is DbContext (Database Context).
If you use Entity Framework (EF) to fetch some data from your database this data remains attached to the database or DbContext, this is an attached entity.
Any changes made to the data are now automatically tracked by EF so when you call SaveChanges() it knows to UPDATE the existing data.
In your case I suspect that _jobQueueUnitOfWork.JobQueueRepository.GetAll(); fetches data from somewhere else such as a Web API. As this data was created outside of DbContext then EF can't possibly know what state it's in, this is a detached entity.
The solution is to simply tell EF what state the data is in, in your case it's modified and requires an UPDATE over an INSERT.
tempList.ForEach(jq =>
{
context.jobqueue.Attach(jq); // Attach to `DbContext`
context.Entry(jq).State = System.Data.Entity.EntityState.Modified; // Modified
});
If you search for Entity Framework articles relating to dbcontext, change tracking and attached/detached entities it should answer a lot of your questions.

ASP.NET Entity Framework trying to update single column

I am using ASP.NET Entity Framework and I am trying to update a single column with the following code:
[HttpGet]
public void MarkOffline(string online)
{
Users user = new Users { email = online, isOnline = false };
db.Entry(user).Property("isOnline").IsModified = true;
db.SaveChanges();
}
But I get this error:
Member 'IsModified' cannot be called for property 'isOnline' because
the entity of type 'Users' does not exist in the context. To add an
entity to the context call the Add or Attach method of DbSet<Users>.
The part I don't know how to do:
To add an entity to the context call the Add or Attach method of
DbSet<Users>.
How do I fix my problem?
If you want to update like this, you'll need to Attach the entity where the entity has its primary key set.
Given you don't have its primary key but only one of its (unique, hopefully) fields, you need to query the record first and then update it:
var existing = db.Users.FirstOrDefault(u => u.email == email);
existing.IsOnline = true;
db.SaveChanges();
That being said, this is not how you record a user's online status in a web application. You rather update a user's LastAction timestamp with each action they perform, and mark a user as offline at runtime when their LastAction is more than N seconds or minutes ago.

How to use TryUpdateModel

I am readying this tutorial. I see from this tutorial that for update the author is using the following code:
....
var studentToUpdate = db.Students.Find(id);
if (TryUpdateModel(studentToUpdate, "",
new string[] { "LastName", "FirstMidName", "EnrollmentDate" }))
{
try
{
db.Entry(studentToUpdate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
...
}
But I don't understand why the following line is needed:
db.Entry(studentToUpdate).State = EntityState.Modified;
When I remove this line, the code still works well and update is done perfectly.
Can someone help me with whether that line is needed? If so, why when I remove it, the update works well.
It works well because you find the studentToUpdate from your context, that's way the entity is attached and the changes that are made by the TryUpdateModel method are saved when you call the SaveChanges method.
If you were working with a detached entity, for example doing this:
var studentToUpdate=new Student(){Id=id};
if (TryUpdateModel(studentToUpdate, "",
new string[] { "LastName", "FirstMidName", "EnrollmentDate" }))
{
try
{
db.Entry(studentToUpdate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
...
}
In this case you have to call the Entry method to attach the entity to your context and change its state.
That line is explicitly telling the EF context that the entity has been modified and needs to be updated the next time SaveChanges() is called. The reason everything still works when you remove that line is that the context usually tracks those changes automatically for you. I have yet to come across a situation where I have needed to fiddle with EF's automatic change tracking in production, it seems to work well.
See How change tracking works in Entity Framework for a bit more info.

Categories

Resources