Why am I getting a deadlock when my code is accessed multiple times? - c#

In my c# code I have the following method that creates a document in the database, adds metadata regarding the document to the database and then updates some information regarding the date the repository was last updated. This method is often called numerous times in quick succession as multiple file uploads are common. However I am having problems with the code failing due to deadlock in sql server.
private IEnumerable<DocumentMetadata> CreateDoc(int? jobId, int?repositoryId, int? folderId, string documentTypeString, IEnumerable<DocumentModel> files)
{
if ((jobId == null && repositoryId == null) || (jobId != null && repositoryId != null))
{
throw new InvalidOperationException("Either job id or repository id must be specified");
}
using (var tran = new TransactionScope())
{
List<DocumentMetadata> newDocuments = new List<DocumentMetadata>();
var documentType = GetDocumentTypeByPrefix(documentTypeString);
if (folderId == null)
{
// Find the root folder
var job = getJob(jobId);
var rootFolder = getRootFolder(job);
// If we can't find a root folder, create one
if (rootFolder == null)
{
rootFolder = CreateRootDirectory(job);
}
folderId = rootFolder.FolderId;
}
User currentUser = _userService.GetCurrentUser();
foreach (var file in files)
{
var document = new Document() { Document1 = file.Data };
var documentMetadata = new DocumentMetadata
{
Document = document,
CreatedDate = file.CreatedDate,
FileName = file.Filename,
FileSize = file.Data.Length,
FolderId = folderId,
DocumentType = documentType,
JobId = jobId,
RepositoryId = repositoryId,
User = currentUser
};
_unitOfWork.DocumentMetadata.Add(documentMetadata);
newDocuments.Add(documentMetadata);
}
// set repository updated date
if (repositoryId != null)
{
DocumentRepository repo = GetDocumentRepository(repositoryId);
if (repo != null)
{
repo.UpdatedDate = new DateTimeOffset(DateTime.Now);
}
}
_unitOfWork.SaveChanges();
tran.Complete();
return newDocuments;
}
}
After some debugging it would appear that the updating of the repository id is causing the deadlock problem. If I remove this code block outside of the transaction all files are saved with no errors.
Why would this code block
if (repositoryId != null)
{
DocumentRepository repo = GetDocumentRepository(repositoryId);
if (repo != null)
{
repo.UpdatedDate = new DateTimeOffset(DateTime.Now);
}
}
cause the deadlock? No other access is being made to the DocumentRepository table apart from in this method - as the locks are obtained in the same order surely there should be no deadlock?
What is it about this code that is leading to deadlock?
Updated: The code for GetDocumentRepository is:
public DocumentRepository GetDocumentRepository(int repositoryId)
{
var result = DocumentRepositories.SingleOrDefault(x => x.RepositoryId == repositoryId); return result;
}

Have you checked the code without defining a transaction explicitly? Based on your code I would say that you are trying to read something that has been modified but not commited.
Another test you could do is to try to add a breakpoint in your code and try to get the DocumentRepository using READ UNCOMMITTED.

Related

Creating a unique user session ID in ASP.NET C#

I'm not very experienced when it comes to development and I'm trying to secure an application so please bear with me. At the moment, the user is being authenticated and a new session is created using the following code:
public static void NewSession(Account account)
{
var redirectUrl = "Login.aspx";
if (account == null)
{
var sessionCookie = HttpContext.Current.Request.Cookies["test-app-session"];
if (sessionCookie != null)
ExpireCookie(sessionCookie);
}
else
{
var sessionCookie = new HttpCookie("test-app-session");
sessionCookie.Values["account-id"] = account.Id.ToString();
sessionCookie.Expires = DateTime.Now.AddHours(12);
HttpContext.Current.Response.Cookies.Add(sessionCookie);
var redirectCookie = HttpContext.Current.Request.Cookies["test-app-redirect"];
if (redirectCookie != null)
{
redirectUrl = redirectCookie.Values["url"];
ExpireCookie(redirectCookie);
}
if (string.IsNullOrWhiteSpace(redirectUrl))
redirectUrl = "Default.aspx";
}
HttpContext.Current.Response.Redirect(redirectUrl);
}
When the App validates the session, it then uses the below code:
public static Account FromSession()
{
var sessionCookie = HttpContext.Current.Request.Cookies["test-app-session"];
if (sessionCookie != null && long.TryParse(sessionCookie.Values["account-id"], out long accountId))
{
using (var db = Database.Connect())
{
using (var cmd = db.Command("SELECT * FROM Account WHERE id=#id").Parameter("#id", accountId, DbType.Int64))
using (var reader = cmd.ExecuteReader())
if (reader.Read())
return new Account(reader);
}
}
if (!Path.GetFileName(HttpContext.Current.Request.Path).Equals("Login.aspx", StringComparison.OrdinalIgnoreCase))
{
var redirectCookie = new HttpCookie("test-app-redirect");
redirectCookie.Values["url"] = HttpContext.Current.Request.Url.ToString();
redirectCookie.Expires = DateTime.Now.AddHours(1);
HttpContext.Current.Response.Cookies.Add(redirectCookie);
HttpContext.Current.Response.Redirect("Login.aspx");
}
return null;
}
The problem is that the account-id value can be easily guessed, so I want to use a unique value for this. I don't really know how I'd implement this, as I'm not sure how the value would then be tied to the users session if there isn't an identifier I can check against. Obviously I'm missing something fundamental in how session management is supposed to work, but I can't figure out what it is. If I create a GUID to store in the cookie, the browser saves it and knows what it is, but how does the server know what this ID is and link it to the user?

MongoDB Change Stream

I try to code a MongoDB change stream. But if I do some changes, my code doesn't detect the changes. If the code detects the changes I have to get the operation type - according to my understanding. Moreover, how can I insert the changes in the other database? Is it so "easy", that I code a new database connection and update/insert/delete the collection/database?
What did I Code?
`
lang-cs
string mongo_db_client = "mongodb://localhost:27017";
string m_database = "myDataBase";
string m_collection ="myCollection";
MongoClient dbClient = new MongoClient(mongo_db_client);
var database = dbClient.GetDatabase(m_database);
var collection = database.GetCollection<BsonDocument>(m_collection);
var cursor = collection.Watch();
var the_operation_type = null;
if(cursor != null)
{
using(cursor)
{
var curr = cursor.Current;
if (curr != null)
{
foreach (var change in curr)
{
try
{
the_operation_type= change.OperationType;
}
catch(NullReferenceException e)
{
Log(e.ToString());
}
}
}
}
}
`

ASP.NET Core error : database operation expected to affect 1 row(s) but actually affected 0 row(s)

I have a problem and I tried to solve it a lot, which is when I want to modify the user data by the function EditOrCreateInformation() this error exception message appears:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 'Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.
Screenshot of the error is display here
Controller code:
[HttpPost]
public IActionResult EditOrCreateInformation(ApplicationUserVm model, ApplicationUser user)
{
if (ModelState.IsValid)
{
var olddata = context.Users.Where(a => a.Id == user.Id).AsNoTracking().FirstOrDefault();
string oldfilename = olddata.PhotoUrl;
if (model.Photo == null)
{
model.PhotoUrl = oldfilename;
}
if (oldfilename != null)
{
if (model.Photo != null && System.IO.File.Exists(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "PhotoFiles/PhotoProfile", oldfilename)))
{
System.IO.File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "PhotoFiles/PhotoProfile", oldfilename));
string PhysicalPath = Path.Combine(Directory.GetCurrentDirectory() + "/wwwroot", "PhotoFiles/PhotoProfile/");
// 2) Get File Name
string FileName = Guid.NewGuid() + Path.GetFileName(model.Photo.FileName);
// 3) Merge Physical Path + File Name
string FinalPath = Path.Combine(PhysicalPath, FileName);
// 4) Save The File As Streams "Data Over Time"
using(var stream = new FileStream(FinalPath, FileMode.Create))
{
model.Photo.CopyTo(stream);
}
model.PhotoUrl = FileName;
}
}
else
{
string PhysicalPath = Path.Combine(Directory.GetCurrentDirectory() + "/wwwroot", "PhotoFiles/PhotoProfile/");
// 2) Get File Name
string FileName = Guid.NewGuid() + Path.GetFileName(model.Photo.FileName);
// 3) Merge Physical Path + File Name
string FinalPath = Path.Combine(PhysicalPath, FileName);
// 4) Save The File As Streams "Data Over Time"
using(var stream = new FileStream(FinalPath, FileMode.Create))
{
model.Photo.CopyTo(stream);
}
model.PhotoUrl = FileName;
}
var obj = mapper.Map < ApplicationUser > (model);
applicationUser.Update(obj);
toastNotification.AddSuccessToastMessage("Your Information Updated successfully");
return RedirectToAction("MyProfile", "Profile", new
{
Area = "Identity"
});
}
return View(model);
}
Repository code:
public ApplicationUser Update(ApplicationUser obj)
{
db.Entry(obj).State = EntityState.Modified;
db.SaveChanges();
return db.Users.Where(a => a.Id == obj.Id).FirstOrDefault();
}
How can I fix this?
I will be very grateful to help me solve this problem.
You have two issues here.
using AsNoTracking
using different DbContext
First, The AsNoTracking should be only used on read-only queries aka SELECT. If you need to UPDATE the same object, then you should select the object without using AsNoTracking.
Secondly, Update method you pass the modified User object to a different context that does not have the current object's change tracking history. So, when SaveChanges executed, it won't see any changes; because the object's changes were made in a different context.
so what you need to do is just remove AsNoTracking and replace these lines :
var obj = mapper.Map < ApplicationUser > (model);
applicationUser.Update(obj);
with this :
olddata.PhotoUrl = model.PhotoUrl;
context.Entry(olddata).State = EntityState.Modified;
db.SaveChanges();

dotnet Core - Getting the correct location of a csv file in my project for streamreader

I thought this would be easy but I am having trouble knowing how to reference a csv file in one of my projects.
Here it is in the solutions display.
The code that is referencing it gets an invalid operations error meaning it wasnt able to find the file and it wasnt.. checked via stepping and its null.
public class CATALOGStateInitialiser : ICATALOGStateInitialiser
{
public void CATALOGInitialiseStates(CATALOGContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.States.Any()) return; //DB table data already created.
using var resourceStream = GetType().Assembly
.GetManifestResourceStream(
"JobsLedger.INITIALISATION.SUBURB.Initialisations.AddressCSV.states.csv");
var reader = new StreamReader(resourceStream ?? throw new InvalidOperationException(), Encoding.UTF8);
reader.Dispose();
var stringStates = reader.ReadToEnd();
var stringSeparators = new[] {"\r\n"};
var states = stringStates.Split(stringSeparators, StringSplitOptions.None)
.Select(FromCsv)
.Where(a => a != null)
.ToList();
context.SaveChanges();
}
public static State FromCsv(string csvLine)
{
if (csvLine == null) throw new ArgumentNullException(nameof(csvLine));
var values = csvLine.Split(',');
if (values.Length <= 1) return null;
var state = new State
{
StateName = values[0],
StateShortName = values[1]
};
return state;
}
}
This is the line that tries to locate the actual csv file:
using var resourceStream = GetType().Assembly
.GetManifestResourceStream(
"JobsLedger.INITIALISATION.SUBURB.Initialisations.AddressCSV.states.csv");
..and this is the line that is throwing the error having not found the file:
var reader = new StreamReader(resourceStream ?? throw new InvalidOperationException(), Encoding.UTF8);
so I have clearly not put the right path or syntax or both.
Given states.csv location in the INITIALISATION library project how do I reference this correctly?
UPDATE
Code was slightly incorrect in that I disposed of the Reader before reading it as well as having the wrong path. I also did what dimlucas said although in the end I changed it to embeddedResource and "copy if newer" in the "Copy to" option.
Here is the code that now works.
using System;
using System.IO;
using System.Linq;
using System.Text;
using JobsLedger.CATALOG;
using JobsLedger.CATALOG.ENTITIES;
using JobsLedger.INITIALISATION.CATALOG.Initialisations.Interfaces;
namespace JobsLedger.INITIALISATION.CATALOG.Initialisations
{
public class CATALOGStateInitialiser : ICATALOGStateInitialiser
{
public void CATALOGInitialiseStates(CATALOGContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.States.Any()) return; //DB table data already created.
using var resourceStream = GetType().Assembly
.GetManifestResourceStream(
"JobsLedger.INITIALISATION.AddressCSV.states.csv");
var reader = new StreamReader(resourceStream ?? throw new InvalidOperationException(), Encoding.UTF8);
var stringStates = reader.ReadToEnd();
reader.Dispose();
var stringSeparators = new[] {"\r\n"};
var states = stringStates.Split(stringSeparators, StringSplitOptions.None)
.Select(FromCsv)
.Where(a => a != null)
.ToList();
context.SaveChanges();
}
public static State FromCsv(string csvLine)
{
if (csvLine == null) throw new ArgumentNullException(nameof(csvLine));
var values = csvLine.Split(',');
if (values.Length <= 1) return null;
var state = new State
{
StateName = values[0],
StateShortName = values[1]
};
return state;
}
}
}
If you are using Visual Studio, click on the file and go to the properties Window
In the Build Action option choose either Resource or Content.
In the Copy to Output Directory option choose Copy always
This ensures that the file is moved to the bin folder when you build your project. Right now, the .csv file is only part of your source code, it's not part of the final binary that is eventually run by .NET Core. The first option ensures that the file is included with the binaries and the second option ensures that the file is copied over on every subsequent build operation to avoid syncing issues.

The underlying provider failed on Open / The operation is not valid for the state of the transaction

Here is my code
public static string UpdateEmptyCaseRevierSet() {
string response = string.Empty;
using (System.Transactions.TransactionScope tran = new System.Transactions.TransactionScope()) {
using (var db = new Entities.WaveEntities()) {
var maxCaseReviewersSetID = db.CaseReviewerSets.Select(crs => crs.CaseReviewersSetId).Max();
var emptyCHList = db.CaseHistories.Where(ch => ch.CaseReviewersSetID == null && ch.IsLatest == true && ch.StatusID != 100).ToList();
for(int i=0; i < emptyCHList.Count; i++) {
var emptyCH = emptyCHList[i];
var newCaseReviewerSET = new Entities.CaseReviewerSet();
newCaseReviewerSET.CreationCHID = emptyCH.CHID;
db.CaseReviewerSets.Add(newCaseReviewerSET);
emptyCH.CaseReviewerSet = newCaseReviewerSET;
}
db.SaveChanges();
}
tran.Complete();
}
return response;
}
The exception occures on "db.SaveChanges()"
I saw in another post with the same error message something about "it seems I cannot have two connections opened to the same database with the TransactionScope block." but I dont think that this has anything to do with my case.
Additionally the number of records to insert and update in total are 2700, witch is not that many really. But it does take quite a lot of time to complete the for statement (10 minutes or so). Since everything happening within the for statement is actually happening in the memory can someone please explane why is this taking so long ?
You can try as shown below using latest db.Database.BeginTransaction API.
Note : use foreach instead of for
using (var db = new Entities.WaveEntities())
{
using (var dbContextTransaction = db.Database.BeginTransaction())
{
try
{
var maxCaseReviewersSetID = db.CaseReviewerSets.Select(crs => crs.CaseReviewersSetId).Max();
var emptyCHList = db.CaseHistories.Where(ch => ch.CaseReviewersSetID == null && ch.IsLatest == true && ch.StatusID != 100).ToList();
foreach(var ch in emptyCHList) {
var newCaseReviewerSET = new Entities.CaseReviewerSet();
newCaseReviewerSET.CreationCHID = ch.CHID;
db.CaseReviewerSets.Add(newCaseReviewerSET);
}
db.SaveChanges();
dbContextTransaction.Commit();
}
catch (Exception)
{
dbContextTransaction.Rollback();
}
}
}

Categories