I have created service which communicates with my database. GetAvailableUserId service's method cannot be run simultaneously, because I don't want to return same user's id for two different calls. So far I have managed this:
public class UserService : IUserService
{
public int GetAvailableUserId()
{
using (var context = new UsersEntities())
{
using (var transaction = context.Database.BeginTransaction())
{
var availableUser = context.User
.Where(x => x.Available)
.FirstOrDefault();
if (availableUser == null)
{
throw new Exception("No available users.");
}
availableUser.Available = false;
context.SaveChanges();
transaction.Commit();
return availableUser.Id;
}
}
}
}
I wanted to test if service will work as intended, so I created simple console application to simulate synchronous requests:
Parallel.For(1, 100, (i, state) => {
var service = new UserServiceReference.UserServiceClient();
var id = service.GetAvailableUserId();
});
Unfortunately, It failed that simple test. I can see, that it returned same id for different for iterations.
Whats wrong there?
If I understood you correctly, you wan to lock method from other threads. If yesm then use lock:
static object lockObject = new object();
public class UserService : IUserService
{
public int GetAvailableUserId()
{
lock(lockObject )
{
// your code is omitted for the brevity
}
}
}
You need to spend some time and delve into the intricadies of SQL Server and EntityFramework.
Basically:
You need a database connection that handles repeatable results (which is a database connection string setting).
You need to wrap the interactions in EntityFramework within one transaction so that multiple instances do not possibly return the same result in the query and then make problems in the save.
Alternative method to achieve this is to catch DbUpdateConcurrencyException to check whether values in the row have changed since retrieving when you try to save.
So if e.g. the same record is retrieved twice. The first one to have the Available value updated in the database will cause the other one to thow concurrency exception when it tries to save because the value has changed since it was retrieved.
Microsoft - handling Concurrency Conflicts.
Add ConcurrencyCheck attribute above the Available property in your entity.
[ConcurrencyCheck]
public bool Available{ get; set; }
Then:
public int GetAvailableUserId()
{
using (var context = new UsersEntities())
{
try
{
var availableUser = context.User
.Where(x => x.Available)
.FirstOrDefault();
if (availableUser == null)
{
throw new Exception("No available users.");
}
availableUser.Available = false;
context.SaveChanges();
return availableUser.Id;
}
catch (DbUpdateConcurrencyException)
{
//If same row was already retrieved and updated to false, do not save, instead call the method again to get the next true row.
return GetAvailableUserId();
}
}
}
Related
What is a good way to bubble up a DbUpdateConcurrencyException to the view from the grain?
I'm currently working on an Orlean's prototype that has a custom state that I'm using Entity Framework Core to communicate with the DB and using the optimistic concurrency patterns built into EF Core to manage the concurrency issues.
Where I'm having an issue is that I want to bubble up my Exception from the grain to the view and am not receiving it on the view end.
I'm trying to accomplish this because I want to deal with some of the concurrency issues that are more pressing on the view so that the user can decide or at least be alerted to the issue.
I brought this up on the Orlean's Gitter, but didn't get many ideas from it.
Example of my code for updating:
public Task UpdateUser(User user)
{
//Reason for second try/catch is to bubble the error to controller
try
{
userState = new User
{
Username = this.GetPrimaryKeyString(),
accountType = user.accountType,
FName = user.FName,
LName = user.LName,
RowVersion = user.RowVersion,
CreatedDate = user.CreatedDate
};
UpdateState();
}
catch (DbUpdateConcurrencyException ex)
{
throw ex;
}
return Task.CompletedTask;
}
public Task UpdateState()
{
using (var context = new OrleansContext())
{
context.users.Update(userState);
try
{
context.SaveChanges();
}
catch ( DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var clientValues = (User)entry.Entity;
var databaseEntry = entry.GetDatabaseValues();
//Make sure the row wasn't deleted
if(databaseEntry != null)
{
var databaseValues = (User)databaseEntry.ToObject();
if(clientValues.accountType != databaseValues.accountType)
{
//Bubble up the exception to controller for proper handling
throw ex;
}
//Update Row Version to allow update
userState.RowVersion = databaseValues.RowVersion;
context.SaveChanges();
}
}
}
return Task.CompletedTask;
}
I'm open to any suggestions on this as long as it allows the user to be alerted to the Exception and can view their data and the current DB values.
There is a chance that the exception is not being serialized or deserialized correctly. The primary reasons for this could be:
The Exception class does not correctly implement the ISerializable pattern.
The assembly which contains the Exception class is not present on the client, so the client does not understand how to create the Exception type.
In this case, I would lean towards the second reason, because most (but not all!) Exception classes do correctly implement the ISerializable pattern.
In either case, you can catch your exception and turn it into a generic exception.
You could create a helper method to do this using the LogFormatter.PrintException(Exception) method from Orleans to format the exception as a string.
public static void ThrowPlainException(Exception e) =>
throw new Exception(Orleans.Runtime.LogFormatter.PrintException(e));
The solution I came to was to create a custom exception class that serializable add the database values object to it and bubble that up to the views.
[Serializable]
public class UpdateException : Exception
{
public object databaseValues { get; set; }
public UpdateException(object databaseValues)
{
this.databaseValues = databaseValues;
}
public UpdateException(string message, object databaseValues) :base(message)
{
this.databaseValues = databaseValues;
}
}
I have a scenario where it requires to add a record in to table, then - creating a resource on the cloud if record is added, then update the record in table with the resource identifier if resource is created on cloud. So, they are 3 operations and I want to revert all of it when any of them doesn't succeed.
We have TransactionScope for Multiple Db Operations in one go but I'm wondering how to achieve this? Appreciate your help!
Edit
PS: There could be any number of operations like that - say 10 or more in a sequence, and they may not even related to DB operations. They could just be creating 10 files in a sequence - so when any of the file creation fails - all the previous files should be deleted/undone.
How about going a command pattern way? It's may not be perfect command pattern implementation but something very close. See below:
public interface ICommand {
ICommandResult Execute();
ICommandResult Rollback();
}
public interface ICommandResult {
bool Success { get; set; }
object Data { get; set; }
Exception Error { get; set; }
}
public class CommandResult : ICommandResult {
public bool Success { get; set; }
public object Data { get; set; }
public Exception Error { get; set; }
}
public class AddToDBCommand : ICommand {
private ICommandResult result;
private int newRecordId;
public AddToDBCommand(<params_if_any>) {
result = new CommandResult();
}
public ICommandResult Execute() {
try {
// insert record into db
result.Success = true;
result.Data = 10; // new record id
}
catch (Exception ex) {
result.Success = false;
result.Error = ex;
}
return result;
}
public ICommandResult Rollback() {
try {
// delete record inserted by this command instance
// use ICommandResult.Data to get the 'record id' for deletion
Console.WriteLine("Rolling back insertion of record id: " + result.Data);
// set Success
}
catch(Exception ex) {
// set Success and Error
// I'm not sure what you want to do in such case
}
return result;
}
}
Similarly you would create commands for creating cloud resource and updating record in db. In main code you can hold collection of ICommand objects and execute each one.
var commands = new List<ICommand>
{
new AddToDBCommand(<params_if_any>),
new AddToCloudCommand(<params_if_any>),
new UpdateInDBCommand(<param_if_any>)
};
Then in the loop you can call Execute, if it returns Success = false then record the current command index in collection and loop backward whilst calling Rollback on each command.
I assume you are using Azure as cloud.
So to support transactions you need to have -
1. Elastic database on Azure which supports transactions.
2. You need to have .NET framework 4.6.1 or higher to utilize distributed transaction.
I encourage you to go through https://learn.microsoft.com/en-us/azure/sql-database/sql-database-elastic-transactions-overview
Now in your case lets break 3 steps considering transaction scope is applied.
Add record to table -
If this fails then no worries I guess.
Create resource in cloud-
If this fails then Added record will be rolled back.
Update record in table with resource id created.
If this fails then 1 step will be rolled back.
After transaction scope is finished you need to check that record added in 3rd step exists. If it does not then you need to manually rollback resource creation by deleting it.
I have a website that is using ASP.NET MVC 5 with EF6.1 and I'm having a problem with it showing the old data after an Edit procedure. The data is saved to the database properly, but when redirecting to the Index view it still shows old data.
If I go back to the Edit view, it still shows the old data also. The DBContext does not seem to be refreshing the data.
I have a base controller that holds the DBContext if that matters.
Here's the code for the Edit views in my controller:
public ActionResult FeaturedItemEdit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
using (nfmContext localContext = new nfmContext())
{
List<FeaturedItem> fi = localContext.FeaturedItems.AsNoTracking().ToList();
FeaturedItem featuredItem = fi.Find(x => x.ID.Equals(id));
if (featuredItem == null)
{
return HttpNotFound();
}
return View(featuredItem);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult FeaturedItemEditPost(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
using (nfmContext db = new nfmContext())
{
var item = db.FeaturedItems.Find(id);
if (TryUpdateModel(item, "", new string[] { "ID", "Title", "ImageAlt", "ImagePath", "Link", "Visible", "Content" }))
{
try
{
item.TimeStamp = DateTime.Now;
db.Entry(item).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("FeaturedItems");
}
catch (DataException ex)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
}
return View(item);
}
}
Once the edit is done, it goes back to the Featured Items view which just loads a list of items:
public ActionResult FeaturedItems()
{
using (nfmContext localContext = new nfmContext())
{
return View(localContext.FeaturedItems.OrderBy(x => x.Visible).ThenBy(x => x.Title).AsNoTracking().ToList());
}
}
Nothing fancy there. At first I thought it was because the calls were all async but in changing it back to non-asynchronous it didn't make much of a difference. I haven't posted the code to create an item, however it has the same issue.
I've even decorated my controller with [OutputCache(Location = System.Web.UI.OutputCacheLocation.None)] to make sure it wasn't a client side caching issue.
So, am I doing something wrong or is there a way to force the DBContext to pull fresh records from the database?
UPDATE 1:
Here's the line that initializes my DBContext in the base controller:
<strike>protected nfmContext db = new nfmContext();</strike>
This is no longer used - see update 2
And here's my context setup:
public class nfmContext : DbContext
{
public nfmContext() : base("connectionStringHere")
{
}
public DbSet<FeaturedItem> FeaturedItems { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
base.OnModelCreating(modelBuilder);
}
}
UPDATE 2: I've switched the DBContect to now be local to each method. The code above reflects the changes and I've removed the DBContect property from the base controller.
Always use your dbContext only as long as required. Usually use a using statement within a method.
Reopening a connection to the database doesn't take long due to connection pooling.
Found out the issue after digging through and searching the project for various caching implementations.
At some point, EntityFramework.Cache was setup in the project (or EFCache as it's referred). That was causing the caching issue even though it was set up based on their documentation.
SaveChanges is supposed to be done in a transaction (which I assume is the case) and EFCache is supposed to watch for transactions and refresh the cache. Somewhere with the two there is a break down where the cache is not getting expired.
For reference, here's what I was using to implement the Secondary caching for the framework:
public class Configuration : DbConfiguration
{
public Configuration()
{
var transactionHandler = new CacheTransactionHandler(new InMemoryCache());
AddInterceptor(transactionHandler);
var cachingPolicy = new CachingPolicy();
Loaded +=
(sender, args) => args.ReplaceService<DbProviderServices>(
(s, _) => new CachingProviderServices(s, transactionHandler,
cachingPolicy));
}
}
Once I've removed this code from the project, the system worked perfectly as expected, but that also removed the feature of having extra caching.
In order to get it to work and give me the ability to purge the DbSet on demand, I switched the declaration of the CacheTransactionHandler to use a static InMemoryCache. Then, once it was set that way, I could use InvalidateDbSets to remove the item from the Memory Cache.
Here's what I did specifically:
Added this to my DbContext:
public static readonly EFCache.InMemoryCache Cache = new EFCache.InMemoryCache();
Changed the transactionHandler declaration in the Configuration sub to:
var transactionHandler = new CacheTransactionHandler(nfmContext.Cache);
After any .SaveChanges calls, I added:
nfmContext.Cache.InvalidateSets(new List<string>() { "[insert entity name here]" });
Everything works now and it uses the cache whenever needed. After any time I make a change, I clear that cache item and it reloads the next time in. Works great.
I use EF6. In my BL-Layer I have following static class, working with my context, which implements DBContext:
public static class AppEnvironment
{
public static IUser CurrentUser { get; set; }
private static IKernel AppKernel { get; set; }
public static void InjectDependencies(params NinjectModule[] contextNinjectModule)
{
AppKernel = new StandardKernel(contextNinjectModule);
}
public static void Authorize(string login, string password)
{
using (var context = AppKernel.Get<ICaseContext>())
{
IUser userToBeAuthorized = context.GetAll<User>().FirstOrDefault(u => u.Login == login);
if (userToBeAuthorized != null && User.GetMD5Hash(password) == userToBeAuthorized.PasswordHash)
{
AppEnvironment.CurrentUser = userToBeAuthorized;
context.Insert(
LogRecord.CreateLogRecord(
userToBeAuthorized,
"Авторизация (успешно)",
LogAction.Read));
}
}
}
public static ICollection<CaseEntityType> GetCaseListTiny<CaseEntityType>(string queryComment) where CaseEntityType : CaseEntityBase
{
using (var context = AppKernel.Get<ICaseContext>())
{
var grantedCaseTypesIDs = AppEnvironment.CurrentUser.CaseTypesGranted.Select(casetype => casetype.ID).ToList();
var cases = context.GetAllIncluding<TaskEntityType>(AppEnvironment.CurrentUser, queryComment, LogAction.Read, t => t.CaseType
).Where(t => grantedTaskTypesIDs.Contains(t.CaseType.ID)).ToList();
return cases;
}
}
}
In my UI Layer I'm trying to use smth like that:
class Program
{
static void Main(string[] args)
{
AppEnvironment.InjectDependencies(new RealContextNinjectModule());
AppEnvironment.Authorize("UserName", "Password");
var caseList = AppEnvironment.GetCaseListTiny<RegularCase>("Get a list");
foreach (var item in caseList)
{
Console.WriteLine(item.Name);
}
}
}
But it throws ObjectContextDisposedException. Can anybody explain what's the right way of dealing with EF6 context class? How should I use it in my BLL or UIL? Why I'm not allowed to use my context class again, after it has been disposed once? I've read a lot of similar questions, but everybody says only smth about eager/lazy loading.
The reason that "everybody says only smth about eager/lazy loading" is because that is what is happening. The sequence of events is:
var caseList = AppEnvironment.GetCaseListTiny<RegularCase>("Get a list");
GetCaseTinyList then calls AppKernel.Get which returns the context into a local var context wrapped up in a using block. Within the block, it created a collection by issuing a call against the context. It does not access that list, and so the collection is not in fact populated; no SQL is run until the collection is accessed. This is lazy eveluation.
At the end of the using block, it disposes of the context. This closes the database connection and marks the context object as disposed and thus unusable.
GetCaseTinyList returns, passing the unpopulated collection back.
Finally, you run a foreach against the returned collection. At the first access the EF collection tries to run the SQL for the GetCaseTinyList against the context. Sadly, the context has been disposed (you did tell it to dispose it and so it did). This makes the system throw the error you are getting, exactly as it should.
One way you can get around this is to change GetCaseTinyCollection to access the collection; something like this:
public static ICollection<CaseEntityType> GetCaseListTiny<CaseEntityType>(string queryComment) where CaseEntityType : CaseEntityBase
{
using (var context = AppKernel.Get<ICaseContext>())
{
var grantedCaseTypesIDs = AppEnvironment.CurrentUser.CaseTypesGranted.Select(casetype => casetype.ID).ToList();
var cases = context.GetAllIncluding<TaskEntityType>(AppEnvironment.CurrentUser, queryComment, LogAction.Read, t => t.CaseType
).Where(t => grantedTaskTypesIDs.Contains(t.CaseType.ID)).ToList();
int count = cases.Count(); // or Count<T>();
return cases;
}
}
This will force the collection to be populated before the context is disposed. However it may have further shortcomings: if the entities in the collection have in turn collection of other objects then when accessed it will try to populate these sub-collections and you're back where you are now.
Another approach is not to use a using/dispose pattern. But at some point you are going to have to clean up the context so you'll have to think about how to do this.
Also, it's far easier when inserting/updating EF entities to keep the context around which you used to read the data. If you don't you have to transfer the updated entities to a new context which sounds like a pain (I've never done it that way).
Basically, you need to rethink your strategy for managing the database context.
I have this working in my controller, but I want to follow best practices and put my database logic in Model.
I want to put all database logic (select, update, delete, insert) to MODEL, therefore I create methods in my model.
My method for retrieving the data:
public IQueryable<ChatLogsNameViewModel> getChatLogWithName()
{
using (var db = new ChatLogContext())
{
var list = (from b in db.ChatLogs
select new ChatLogsNameViewModel()
{
UserProfile = b.UserProfile,
Message = b.Message,
Time = b.Time
});
return list;
}
}
This is my modelView:
public class ChatLogsNameViewModel
{
public UserProfile UserProfile { get; set; }
public string Message { get; set; }
public DateTime Time { get; set; }
}
I call my getChatLogWithName() method in my controller like this:
List<ChatLogsNameViewModel> items = null;
using (var dba = new ChatLogContext())
{
items = dba.getChatLogWithName().ToList();
return View(items);
}
Error I get is:
The operation cannot be completed because the DbContext has been disposed.
What is the proper way to do this? I just want to pass collection (all records from 2 tables via join) to controller.
To ensure that the DBContext is not getting referenced after disposal. How about returning a list so you dont have to call .ToList():
public List<ChatLogsNameViewModel> getChatLogWithName()
{
using (var db = new ChatLogContext())
{
var list = (from b in db.ChatLogs
select new ChatLogsNameViewModel()
{
UserProfile = b.UserProfile,
Message = b.Message,
Time = b.Time
});
return list.ToList();
}
}
and
items = dba.getChatLogWithName();
Or
Since it appears that dba is the same as db, couldn't you change your code to use the dba instance which won't get disposed until the end of your using statement within your controller.
public IQueryable<ChatLogsNameViewModel> getChatLogWithName()
{
var list = (from b in this.ChatLogs
select new ChatLogsNameViewModel()
{
UserProfile = b.UserProfile,
Message = b.Message,
Time = b.Time
});
return list;
}
Lifetime - DbContext
The lifetime of the context begins when the instance is created and
ends when the instance is either disposed or garbage-collected. Use
using if you want all the resources that the context controls to be
disposed at the end of the block. When you use using, the compiler
automatically creates a try/finally block and calls dispose in the
finally block.
The problem was when the inner using got disposed, it invalidated the DbContext. So you need to use .ToList() to save the query result in memory.
Suppose getChatLogWithName is defined in the class called Repo, you can change the controller logic to something like this:
var repo = new Repo();
var items = repo.getChatLogWithName().ToList();
Or move .ToList() to getChatLogWithName.
Btw, you should not use the nested DbContexts cope, in your controller, you don't have to wrap it using another DbContextscope.