I'm getting the following error in my web API application.
"An entity object cannot be referenced by multiple instances of IEntityChangeTracker"
Step 1: Browser posts an edited Release to API controller, which in turn calls update on a Service.
ReleaseController.cs
public Guid Update(EditReleaseViewModel release)
{
if (!ModelState.IsValid) throw new Exception("Invalid model");
var updatedRelease = _releaseService.Update(release);
return updatedRelease.ID;
}
Step 2: Service retrieves and modifies the Release. (The service gets the DbContext injected per request with Ninject)
ReleaseService.cs
public Release Update(EditReleaseViewModel release)
{
var existingRelease = _context.Releases.Single(x => x.ID == release.ID);
existingRelease.Name = release.Name;
existingRelease.CatalogNumber = release.CatalogNumber;
_context.Entry(existingRelease).State = EntityState.Modified; // ERROR HAPPENS HERE!
_context.SaveChanges();
return existingRelease;
}
I read that this happens when an entity is still in memory (wasn't detached before disposing previous DbContext).
Could the problem be that I'm using the same Service to retrieve the Release before trying to update it again in that same service?
And if yes, what's the best way around this? Thanks!
When you retrieve existingRelease from the dbcontext one of the benefits is your context is already attached and your state tracking is provided for free. All you need to do is update your name/catalognumber and call SaveChanges.
public Release Update(EditReleaseViewModel release)
{
var existingRelease = _context.Releases.Single(x => x.ID == release.ID);
existingRelease.Name = release.Name;
existingRelease.CatalogNumber = release.CatalogNumber;
_context.SaveChanges();
return existingRelease;
}
When you manually set the state of the entity like so:
_context.Entry(existingRelease).State = EntityState.Modified;
Two things happen here: 1) it tries to attach existingRelease to _context (which fails here because it's already attached to it from the retrieval) and 2) marks ALL properties as modified -- rather than only the 2 you've updated.
Hope that helps.
Related
I'm developing a multitenant application and would like to have to option to remove a tenant. This however seems to be less trivial than one would assume.
My goal is to delete all references to the tenant everywhere in the database. I understand that Tenant is Soft-Delete, but I since I don't want my database to fill up with old meaningless data I've tried disabling the soft-delete filter.
Here is some code that I've tried:
using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete))
{
await TenantRepository.DeleteAsync(x => x.Id == tenantId);
}
This did not work. The tenant is marked as "IsDeleted" but not removed.
Then I figured that maybe it has something to do with UnitOfWork so I made sure no UnitOfWork was active and then manually controlled it:
using (var unitOfWork = _unitOfWorkManager.Begin())
{
// the codeblock above went here
unitOfWork.Complete();
}
This did not work, same result. And this is just the AbpTenant table. I'm also trying to delete from all other tables. For example AbpSettings and AbpLanguages. It's very unclear to me how to do that at all - the "managers" doesn't contain any Delete functions.
I tried creating IRepository for these entities but it does not work. The error reads
The type Abo.Configuration.Setting cannot be used as a type parameter TEntity in the generic type or method IRepository. There is no implicit reference conversion from Abp.Configuration.Setting to Abo.Domain.Entities.IEntity.
That leaves me with the option to use the DataContext directly:
using (EntityFramework.MyDbContext db = new EntityFramework.MyDbContext())
{
List<PermissionSetting> perms = await db.Permissions.Where(x => x.TenantId == tenantId).ToListAsync();
for (int i=0; i<perms.Count(); i++)
{
db.Permissions.Remove(perms[i]);
}
// I also tried deleting them in bulk at first
// ((DbSet<PermissionSetting>)db.Permissions).RemoveRange(db.Permissions.Where(x => x.TenantId == tenantId));
await db.SaveChangesAsync();
}
I tried that with and without UnitOfWork.
But it simply does not get deleted from the database. I'm getting no errors or Exceptions.
Why does it not get deleted? How can I delete it? Surely it must be possible?
since I don't want my database to fill up with old meaningless data I've tried disabling the soft-delete filter.
From the question on Disable SoftDelete for AbpUserRole:
protected override void CancelDeletionForSoftDelete(EntityEntry entry)
{
if (IsSoftDeleteFilterEnabled)
{
base.CancelDeletionForSoftDelete(entry);
}
}
The type Abo.Configuration.Setting cannot be used as a type parameter TEntity in the generic type or method IRepository. There is no implicit reference conversion from Abp.Configuration.Setting to Abo.Domain.Entities.IEntity.
Inject IRepository<Setting, long> instead of IRepository<Setting>.
That leaves me with the option to use the DataContext directly
...
But it simply does not get deleted from the database. I'm getting no errors or Exceptions.
From the documentation on Data Filters:
using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant))
{
using (var db = new ...)
{
// ...
}
}
That said, there is no way to easily delete related tenant data completely. Consider writing SQL.
I am building an ASP.NET MVC application using the Repository pattern. A typical method in my Repository is as follows
public IList<T> Select<T>(string cacheKey, string Sql, object filter) where T: new()
{
IList<T> items = MemoryCache.Default.Get(cacheKey) as IList<T>;
if (items == null || !items.Any())
{
items = Connection.Select<T>(Sql, filter);
MemoryCache.Default.Add(cacheKey, items, DateTime.Now.AddMinutes(120));
}
return items;
}
and it is being used as follows
IEnumerable<OSADCOL> osadcols = Repository.Select<OSADCOL>("OSADCOLS__TblId=" + Id, "TBLid = #id", new { id = Id });
In the above mentioned example OSADCOL is a model in my app and represents a Table with the same name in my Database. The Connection.Select function is ORMlite function. I don't use Entity Framework for performance issues. My problem is the following. I am currently cashing the result set for future relevance but I am doing it in a hard coded way. I am caching the result set for 2 hours. It is obvious that a proper implementation would be to discard my cashed data when the OSADCOL's table data changes. It seems that I have to use SQLDependency or SQLCacheDependency. The questions are the following:
How am I going to use SQLDependency or SQLCacheDependency with this Repository?
What is the actual difference between these 2?
It is mentioned in some forums that SQLDependency creates memory leaks? Is that true? And If yes is there an alternative approach?
Any Ideas?
Background:
From another question here at SO I have a Winforms solution (Finance) with many projects (fixed projects for the solution).
Now one of my customers asked me to "upgrade" the solution and add projects/modules that will come from another Winforms solution (HR).
I really don't want to keep these projects as fixed projects on the existing finance solution. For that I'm trying to create plugins that will load GUI, business logic and the data layer all using MEF.
Question:
I have a context (DbContext built to implment the Generic Repository Pattern) with a list of external contexts (loaded using MEF - these contexts represent the contexts from each plugin, also with the Generic Repository Pattern).
Let's say I have this:
public class MainContext : DbContext
{
public List<IPluginContext> ExternalContexts { get; set; }
// other stuff here
}
and
public class PluginContext_A : DbContext, IPluginContext
{ /* Items from this context */ }
public class PluginContext_B : DbContext, IPluginContext
{ /* Items from this context */ }
and within the MainContext class, already loaded, I have both external contexts (from plugins).
With that in mind, let's say I have a transaction that will impact both the MainContext and the PluginContext_B.
How to perform update/insert/delete on both contexts within one transaction (unity of work)?
Using the IUnityOfWork I can set the SaveChanges() for the last item but as far as I know I must have a single context for it to work as a single transaction.
There's a way using the MSDTC (TransactionScope) but this approach is terrible and I'd reather not use this at all (also because I need to enable MSDTC on clients and server and I've had crashes and leaks all the time).
Update:
Systems are using SQL 2008 R2. Never bellow.
If it's possible to use TransactionScope in a way that won't scale to MSDTC it's fine, but I've never achieved that. All the time I've used TransactionScope it goes into MSDTC. According to another post on SO, there are some cases where TS will not go into MSDTC: check here. But I'd really prefer to go into some other way instead of TransactionScope...
If you are using multiple contexts each using separate connection and you want to save data to those context in single transaction you must use TransactionScope with distributed transaction (MSDTC).
Your linked question is not that case because in that scenario first connection do not modify data so it can be closed prior to starting the connection where data are modified. In your case data are concurrently modified on multiple connection which requires two-phase commit and MSDTC.
You can try to solve it with sharing single connection among multiple contexts but that can be quite tricky. I'm not sure how reliable the following sample is but you can give it a try:
using (var connection = new SqlConnection(connnectionString))
{
var c1 = new Context(connection);
var c2 = new Context(connection);
c1.MyEntities.Add(new MyEntity() { Name = "A" });
c2.MyEntities.Add(new MyEntity() { Name = "B" });
connection.Open();
using (var scope = new TransactionScope())
{
// This is necessary because DbContext doesnt't contain necessary methods
ObjectContext obj1 = ((IObjectContextAdapter)c1).ObjectContext;
obj1.SaveChanges(SaveOptions.DetectChangesBeforeSave);
ObjectContext obj2 = ((IObjectContextAdapter)c2).ObjectContext;
obj2.SaveChanges(SaveOptions.DetectChangesBeforeSave);
scope.Complete();
// Only after successful commit of both save operations we can accept changes
// otherwise in rollback caused by second context the changes from the first
// context will be already accepted = lost
obj1.AcceptAllChanges();
obj2.AcceptAllChanges();
}
}
Context constructor is defined as:
public Context(DbConnection connection) : base(connection,false) { }
The sample itself worked for me but it has multiple problems:
First usage of contexts must be done with closed connection. That is the reason why I'm adding entities prior to opening the connection.
I rather open connection manually outside of the transaction but perhaps it is not needed.
Both save changes successfully run and Transaction.Current has empty distributed transaction Id so it should be still local.
The saving is much more complicated and you must use ObjectContext because DbContext doesn't have all necessary methods.
It doesn't have to work in every scenario. Even MSDN claims this:
Promotion of a transaction to a DTC may occur when a connection is
closed and reopened within a single transaction. Because the Entity
Framework opens and closes the connection automatically, you should
consider manually opening and closing the connection to avoid
transaction promotion.
The problem with DbContext API is that it closes and reopens connection even if you open it manually so it is a opened question if API always correctly identifies if it runs in the context of transaction and do not close connection.
#Ladislav Mrnka
You were right from the start: I have to use MSDTC.
I've tried multiple things here including the sample code I've provided.
I've tested it many times with changed hare and there but it won't work. The error goes deep into how EF and DbContext works and for that to change I'd finally find myself with my very own ORM tool. It's not the case.
I've also talked to a friend (MVP) that know a lot about EF too.
We have tested some other things here but it won't work the way I want it to. I'll end up with multiple isolated transactions (I was trying to get them together with my sample code) and with this approach I don't have any way to enforce a full rollback automatically and I'll have to create a lot of generic/custom code to manually rollback changes and here comes another question: what if this sort of rollback fails (it's not a rollback, just an update)?
So, the only way we found here is to use the MSDTC and build some tools to help debug/test if DTC is enabled, if client/server firewalls are ok and all that stuff.
Thanks anyway.
=)
So, any chance this has changed by October 19th? All over the intertubes, people suggest the following code, and it doesn't work:
(_contextA as IObjectContextAdapter).ObjectContext.Connection.Open();
(_contextB as IObjectContextAdapter).ObjectContext.Connection.Open();
using (var transaction = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions{IsolationLevel = IsolationLevel.ReadUncommitted, Timeout = TimeSpan.MaxValue}))
{
_contextA.SaveChanges();
_contextB.SaveChanges();
// Commit the transaction
transaction.Complete();
}
// Close open connections
(_contextA as IObjectContextAdapter).ObjectContext.Connection.Close();
(_contextB as IObjectContextAdapter).ObjectContext.Connection.Close();
This is a serious drag for implementing a single Unit of Work class across repositories. Any new way around this?
To avoid using MSDTC (distributed transaction):
This should force you to use one connection within the transaction as well as just one transaction. It should throw an exception otherwise.
Note: At least EF6 is required
class TransactionsExample
{
static void UsingExternalTransaction()
{
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
{
try
{
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.Transaction = sqlTxn;
sqlCommand.CommandText =
#"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
context.Database.UseTransaction(sqlTxn);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
sqlTxn.Commit();
}
catch (Exception)
{
sqlTxn.Rollback();
}
}
}
}
}
Source:
http://msdn.microsoft.com/en-us/data/dn456843.aspx#existing
Psssst...!
Read on, by all means. But I can tell you here that the problem had nothing to do with the DataContext, but with Dependency Injection. I have left the question up, as it documents one possible issue with the "row not found or changed error" that has nothing to do with real world concurrency conflicts.
It seems the problems have been caused by badly written dependency injection. Or rather, I am beginning to believe, by default lifecycle management by the DI container I used.
The problem was that I used a DataContext as a constructor argument that was supplied by Ninject. It seems that the default behaviour was to cache this DataContext, leading to all manner of unexpected behaviour. I will ask a separate question about this.
Anyway, what follows is my original question, which as you will see, misses the real cause of the issue by a mile...
The Problem
I am getting a number of errors that imply that the DataContext, or rather, the way I am using the DataContext is getting out of synch.
The error occurs on db.SubmitChanges() where db is my DataContext instance. The error is:
Row not found or changed.
The problem only occurs intermitently, for example, adding a row then deleting it. If I stop the dev server and restart, the added row is there and I can delete it no problem.
Ie, it seems that the problem is related to the DataContext losing track of the rows that have been added.
IMPORTANT:
Before anyone votes to close this thread, on the basis of it being a duplicate, I have checked the sql server profiler and there is no "Where 0 = 1" in the SQL.
I have also recreated the dbml file, so am satisfied that the database schema is in synch with the schema represented by the dbml file.
Ie, no cases of mismatched nullable/not nullable columns, etc.
My Diagnosis (for what it is worth):
The problem seems (to me) related to how I am using the DataContext. I am new to MVC, Repositories and Services patterns, so suspect that I have wired things up wrong.
The Setup
Simple eLearning app in its early stages. Pupils need to be able to add and delete courses (Courses table) to their UserCourses.
To do this, I have a service that gets a specific DataContext instance Dependency Injected into its constructor.
Service Class Constructor:
public class SqlPupilBlockService : IPupilBlockService
{
DataContext db;
public SqlPupilBlockService(DataContext db)
{
this.db = db;
CoursesRepository = new SqlRepository<Course>(db);
UserCoursesRepository = new SqlRepository<UserCourse>(db);
}
// Etc, etc
}
The CoursesRepository and UserCoursesRepository are both private properties of the service class that are of type IRepository (just a simple generic repository interface).
SqlRepository Code:
public class SqlRepository<T> : IRepository<T> where T : class
{
DataContext db;
public SqlRepository(DataContext db)
{
this.db = db;
}
#region IRepository<T> Members
public IQueryable<T> Query
{
get { return db.GetTable<T>(); }
}
public List<T> FetchAll()
{
return Query.ToList();
}
public void Add(T entity)
{
db.GetTable<T>().InsertOnSubmit(entity);
}
public void Delete(T entity)
{
db.GetTable<T>().DeleteOnSubmit(entity);
}
public void Save()
{
db.SubmitChanges();
}
#endregion
}
The two methods for adding and deleting UserCourses are:
Service Methods for Adding and Deleting UserCourses:
public void AddUserCourse(int courseId)
{
UserCourse uc = new UserCourse();
uc.IdCourse = courseId;
uc.IdUser = UserId;
uc.DateCreated = DateTime.Now;
uc.DateAmended = DateTime.Now;
uc.Role = "Pupil";
uc.CourseNotes = string.Empty;
uc.ActiveStepIndex = 0;
UserCoursesRepository.Add(uc);
UserCoursesRepository.Save();
}
public void DeleteUserCourse(int courseId)
{
var uc = (UserCoursesRepository.Query.Where(x => x.IdUser == UserId && x.IdCourse == courseId)).Single();
UserCoursesRepository.Delete(uc);
UserCoursesRepository.Save();
}
Ajax
I am using Ajax via Ajax.BeginForm
I don't think that is relevant.
ASP.NET MVC 3
I am using mvc3, but don't think that is relevant: the errors are related to model code.
The problem only occurs intermitently,
for example, adding a row then
deleting it. If I stop the dev server
and restart, the added row is there
and I can delete it no problem.
Your code does not show what the link is between the Added Row and the Delete/Update. Your Add() doesn't return an object reference.
I'm thinking you are missing a Refresh (ie reload the object after Insert). Is your IdCourse also the PK in the Table?
Edit:
Further research has revealed that the problem is with the dependency injection.
The problem was related to how Dependency Injection manages the items it creates. Google for 'lifecycle management' in IoC or DI. Essentially, DI cached a DataContext constructor argument that I injected.
For a way to solve this using the Factory Pattern, see this thread:
Ninject caching an injected DataContext? Lifecycle Management?
The accepted answer solved it all.
am getting this error on this code (this is an MVC project into which I am trying to integrate Entity Framework):
List<string> consultantSchoolList = new List<string>();
// districts managed by consultant
IQueryable<string> consultClients = rc.consultantDistrictsRepository.districtsForConsultant(userID);
// schools managed by consultant
if (consultClients != null)
{
consultantSchoolList = (from c in rc.clientsRepository.Clients
where (consultClients.Contains(c.cdsCode.Substring(0, 7)))
select c.cdsCode).ToList();
}
on the "consultantSchoolList = " line.
This is an MVC project and I am using a context object that is stored in the HttpContext.Current object. Each repository has a private variable that stores the context object, but each one should point to the same object in the HttpContext.Current Items collection. Would this be considered two different contexts even though they point to the same thing?
Stepping through the code in the debugger shows that the context objects for the two repositories, consultantDistrictsRepository and clientsRepository, do point to the same object in the HttpContext.Current object.
UPDATE Here's how I define the context objects in each repository:
private SchedulerContext context;
public EFConsultantDistricts()
{
context = ContextHelper.GetContext();
}
and GetContext is as follows:
public static SchedulerContext GetContext()
{
if (!HttpContext.Current.Items.Contains("_db_context"))
{
HttpContext.Current.Items.Add("_db_context", new SchedulerContext());
}
return (SchedulerContext)HttpContext.Current.Items["_db_context"];
}
I found the problem -- I was caching the frequently-requested clients list in a Session variable in the Clients repository:
if (HttpContext.Current.Session["clientList"] == null)
{
HttpContext.Current.Session["clientList"] = from c in context.Clients
where (c.Year == fiscalYear)
select c;
}
return (IQueryable<Client>)HttpContext.Current.Session["clientList"];
Since the Session object persists over requests, I guess it was using a previous context object. I thought the client list would be separated from the context object, but I guess not if I'm using IQueryable.
I hate to hit the database every time for this, but I guess I have no choice ... at least for the moment until I get everything straightened out.