i am trying to update record with nHibernate. I tried several solutions, but none of them work (shows no error, bu data is alsno not updated).
First code:
MyRepository rep = new MyRepository(GetCurrentSession());
UserPost post = rep.GetById(id);
post.ValidTo = date;
rep.Update(post);
Second code:
ISession session = GetCurrentSession();
MyRepository rep = new MyRepository(GetCurrentSession());
UserPost post = rep.GetById(id);
post.ValidTo = date;
rep.Update(post);
session.Update(post);
session.Transaction.Commit();
session = null;
Maybe somedy has a suggestion?
1) You need to flush the session if you are not using a transaction`:
var post = _session.Load<Post>(id); //assumes this record exists in the db
post.SomeAttribute=somenewvalue;
_session.SaveOrUpdate(post);
_session.Flush;
2) I don't see a transaction being started? You need to start a transaction to commit.
using(var transaction = _session.BeginTransaction()){
_session.SaveOrUpdate(post);
transaction.commit();
}
using(var transaction = _session.BeginTransaction()){
_session.SaveOrUpdate(post);
transaction.commit();
}
I had this Batch Update returns rowcount = 0 but expected is 1 exception. But this works
Is UserPost mapped correctly? Are you using .hbm.xml files (note the hbm) and the xml file is marked as an embedded resource?
In my experience if an entity is not mapped NHibernate doesn't complain nor throw an error.
Actually looking at your code in more detail you are not calling session.Save
If you want to update some persist entity's fields you shouldn't call session.Update() or session.SaveOrUpdate(), you can use session.Flush() or transactions:
MyRepository rep = new MyRepository(GetCurrentSession());
UserPost post = rep.GetById(id);
post.ValidTo = date;
rep.Flush(); // session.Flush()
OR
using(var transaction = _session.BeginTransaction()){
UserPost post = rep.GetById(id);
post.ValidTo = date;
transaction.commit();
}
Related
To handle concurrency in my database:
Client A updates a row
Client B tries to update the same row
Client B needs to wait for Client A to commit his updates
Both Client A & B instance are simulated and using this code:
using (myEntities db = new myEntities ())
{
db.Database.Connection.Open();
try
{
using (var scope = db .Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
{
{
var test = db.customer_table.Where(x => x.id == 38).FirstOrDefault();
test.bank_holder_name = "CLIENT NAME XXXX";
db.SaveChanges(); <=== CLIENT B stop here while client A still in progress. After CLIENT A finish commit, here will throw *Deadlock found error*"
scope.Commit();
}
}
}
catch (Exception ex)
{
throw;
}
}
This is not what I expected where Client B should wait and not allowed to query any data about row id=38, but somehow it can proceed until SaveChanges and throws an error in the end.
Thus, I suspected this might caused by linq (incorrect row/ table lock)
I edited my code as below:
using (myEntities db = new myEntities ())
{
db.Database.Connection.Open();
try
{
using (var scope = db .Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
{
{
var test = db.Database.ExecuteSqlCommand("Update customer_table set bank_holder_name = 'CLIENT XXXXX' where pu_id = 38"); <===== Client B is stop here and proceed after Client A is completed
db.SaveChanges();
scope.Commit();
}
}
}
catch (Exception ex)
{
throw;
}
}
Finally, the transaction is working with code above (not linq function). This is so confusing, what linq have done in behind making Transaction working inconsistent behavior?
This is due to the EF code generating two SQL statements: a SELECT for the line:
var test = db.customer_table.Where(x => x.id == 38).FirstOrDefault();
...and a subsequent UPDATE for the SaveChanges() call.
With a serializable isolation level both client A and client B take a shared lock for the duration of the transaction on the record when the SELECT statement is run. Then when one or other of them first tries to perform the UPDATE they cannot get the requisite exclusive lock because the other client has a shared lock on it. The other client itself then tries to obtain an exclusive lock and you have a deadlock scenario.
The ExecuteSqlCommand only requires a single update statement and thus a deadlock does not occur.
The Serializable isolation level can massively reduce concurrency and this example shows exactly why. You'll find that less stringent isolation levels will allow the EF code to work, but at the risk of phantom records, non-repeatable reads etc. These may well however be risks you are willing to take and/or mitigate against in order to improve concurrency.
Don't fetch the entity first. Instead create a "stub entity" and update that, eg
var test = new Customer() { id = 38 };
test.bank_holder_name = "CLIENT NAME XXXX";
db.Entry(test).Property(nameof(Customer.bank_holder_name)).IsModified = true;
db.SaveChanges();
Which translates to
SET NOCOUNT ON;
UPDATE [Customers] SET [bank_holder_name] = #p0
WHERE [id] = #p1;
SELECT ##ROWCOUNT;
_context.Update(v) ;
_context.SaveChanges();
When I use this code then SQL Server adds a new record instead of updating the
current context
[HttpPost]
public IActionResult PageVote(List<string> Sar)
{
string name_voter = ViewBag.getValue = TempData["Namevalue"];
int count = 0;
foreach (var item in Sar)
{
count = count + 1;
}
if (count == 6)
{
Vote v = new Vote()
{
VoteSarparast1 = Sar[0],
VoteSarparast2 = Sar[1],
VoteSarparast3 = Sar[2],
VoteSarparast4 = Sar[3],
VoteSarparast5 = Sar[4],
VoteSarparast6 = Sar[5],
};
var voter = _context.Votes.FirstOrDefault(u => u.Voter == name_voter && u.IsVoted == true);
if (voter == null)
{
v.IsVoted = true;
v.Voter = name_voter;
_context.Add(v);
_context.SaveChanges();
ViewBag.Greeting = "رای شما با موفقیت ثبت شد";
return RedirectToAction(nameof(end));
}
v.IsVoted = true;
v.Voter = name_voter;
_context.Update(v);
_context.SaveChanges();
return RedirectToAction(nameof(end));
}
else
{
return View(_context.Applicants.ToList());
}
}
You need to tell the DbContext about your entity. If you do var vote = new Vote() vote has no Id. The DbContext see this and thinks you want to Add a new entity, so it simply does that. The DbContext tracks all the entities that you load from it, but since this is just a new instance, it has no idea about it.
To actually perform an update, you have two options:
1 - Load the Vote from the database in some way; If you get an Id, use that to find it.
// Loads the current vote by its id (or whatever other field..)
var existingVote = context.Votes.Single(p => p.Id == id_from_param);
// Perform the changes you want..
existingVote.SomeField = "NewValue";
// Then call save normally.
context.SaveChanges();
2 - Or if you don't want to load it from Db, you have to manually tell the DbContext what to do:
// create a new "vote"...
var vote = new Vote
{
// Since it's an update, you must have the Id somehow.. so you must set it manually
Id = id_from_param,
// do the changes you want. Be careful, because this can cause data loss!
SomeField = "NewValue"
};
// This is you telling the DbContext: Hey, I control this entity.
// I know it exists in the DB and it's modified
context.Entry(vote).State = EntityState.Modified;
// Then call save normally.
context.SaveChanges();
Either of those two approaches should fix your issue, but I suggest you read a little bit more about how Entity Framework works. This is crucial for the success (and performance) of your apps. Especially option 2 above can cause many many issues. There's a reason why the DbContext keep track of entities, so you don't have to. It's very complicated and things can go south fast.
Some links for you:
ChangeTracker in Entity Framework Core
Working with Disconnected Entity Graph in Entity Framework Core
When SaveChanges() is called on the context, all insert/delete/update operations are executed in a single transaction. It is also possible to use DbContextTransaction for transactions. I am trying to simulate deadlock using both of these approaches. When I use DbContextTransaction, I get the deadlock exception right away but SaveChanges() alone does not throw any deadlock exceptions even after an hour. Am I doing something wrong?
Here is the code with DbContextTransaction. I try to update the first row and then the second row in the main thread. I also start another task which tries to update the second row first and then the first row.
while (true)
{
using (var context = new SchoolDBEntities())
{
using (System.Data.Entity.DbContextTransaction dbTran = context.Database.BeginTransaction())
{
Random r = new Random();
int r1 = r.Next();
int r2 = r.Next();
Student std1 = context.Students.First();
std1.StudentName = "test"+r1;
context.SaveChanges();
Student std2 = context.Students.Find(2);
std2.StudentName = "test"+r2;
context.SaveChanges();
dbTran.Commit();
}
}
}
But when I try it with just SaveChanges() it does not generate deadlock:
while (true)
{
using (var context = new SchoolDBEntities())
{
try
{
Random r = new Random();
int r1 = r.Next();
int r2 = r.Next();
Student std1 = context.Students.First();
std1.StudentName = "test" + r1;
Student std2 = context.Students.Find(2);
std2.StudentName = "test" + r2;
context.SaveChanges();
}
}
}
I am using SQL Profiler to trace the transactions. I even added more updates to the second approach just to make that transaction's duration equal to the DbContextTransaction case thinking it might be the reason but still no luck! When I look at the trace, I see that updates belonging to a particular transaction start only after the previous transaction is committed. What could be the reason?
Upon further investigation, I found out that regadless of the order of changes I have made in the context, the order in which SaveChanges() method always sends update queries to the SQL Server is based on the primary key of the table. In other words, even though I try to reverse the order of update request by first changing row 2 and then row 1, SaveChanges() first executes the update query for row 1 and then for row 2. That's why I don't get a deadlock by using just SaveChanges() method. It does not reverse the order of the queries.
I have the following method which is called from Ajax:
[Authorize]
[ValidateInput(false)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public JsonNetResult CreateOrUpdateTimeRecord(TimeRecord tr)
{
TimeRecord trLocal;
if (tr.Id == -1 || tr.Id == 0)
{
trLocal = new TimeRecord
{
Description = tr.Description,
StartTime = tr.StartTime,
EndTime = tr.EndTime,
User =new myTimeMvc.Models.NHibernate.Models.User {Id = tr.User.Id},// _userRepo.Get(tr.User.Id),
Hdt = new Hdt {Id = tr.Hdt.Id}//_hdtRepo.Get(tr.Hdt.Id)
};
_timeRepo.Insert(trLocal);
}
else
{
trLocal = _timeRepo.Get(tr.Id);
trLocal.Description = tr.Description;
trLocal.StartTime = tr.StartTime;
trLocal.EndTime = tr.EndTime;
_timeRepo.Update(trLocal);
}
...
}
As you can see my TimeRecord has a reference to User and Hdt. Now I started to work with NHibernate Profiler which complains when I resolve my properties by loading them from their coresponding repositories. Which is clear to me since I actually don't need to query the database for that since I have the ID's for this objects.
User = _userRepo.Get(tr.User.Id),
Hdt = _hdtRepo.Get(tr.Hdt.Id)
But I'm not 100% sure if I can use this instead:
User =new myTimeMvc.Models.NHibernate.Models.User {Id = tr.User.Id},,
Hdt = new Hdt {Id = tr.Hdt.Id}
I guess NHibernate lazy proxies work the same way since they only contain just the ID of the related object and load the rest when it is needed. Do I have to attach this "new" oject anyway to my session?
Can someone tell me what is the correct way to do this?
Cheers,
Stefan
There are a few ways how to achieve that. One of them could be using the Load() method. Check Ayendes post: NHibernate – The difference between Get, Load and querying by id, an extract:
Load will never return null. It will always return an entity or throw an exception. Because that is the contract that we have we it, it is permissible for Load to not hit the database when you call it, it is free to return a proxy instead.
Other words, we can do something like this
User = _userRepo.Load(tr.User.Id),
Hdt = _hdtRepo.Load(tr.Hdt.Id)
Where the Load would be encapsulating the session.Load()
I have meeting a problem, the code look like simple, but exception:
DDS.Model.ATest atest = new DDS.Model.ATest();
atest.AID = Guid.NewGuid();
ISession session = SessionProvider.GetNewSession();
using (ITransaction transaction = session.BeginTransaction())
{
session.SaveOrUpdate(atest);
int count = session.CreateQuery("from ATest").List().Count;
//Above row throw a exception:
//Batch update returned unexpected row count from update; actual row count: 0; expected: 1
transaction.Commit();
}
You are trying to load items before saving. Commit the transaction first, and then execute the query.
DDS.Model.ATest atest = new DDS.Model.ATest();
//atest.AID = Guid.NewGuid(); // You should not assign IDs by yourself
ISession session = SessionProvider.GetNewSession();
using (ITransaction transaction = session.BeginTransaction())
{
session.SaveOrUpdate(atest);
transaction.Commit();
}
int count = session.CreateQuery("from ATest").List().Count;
But that doesn't seem to be the problem in your case. I believe you have ID mapped as Guid or Guid.comb. You should not assign the value to ID. NHibernate will take care of that.
When you assign the value and call session.SaveOrUpdate(), it will try to do update since ID value is not Guid.Empty. The update method will fail with the exception: Batch update returned unexpected row count from update; actual row count: 0; expected: 1, since UPDATE ... WHERE AID = <some guid> will be executed.