STE Update & Delete struggle in an N-TIER application - c#

We've been building an application which has 2 parts.
Server Side: A WCF service, Client Side: A WPF app following MVVM patterns
So we also use Self Tracking Entities to get some database job done but we're having struggles.
Here's an example code:
public bool UpdateUser(User userToUpdate)
{
using (DBContext _context = new DBContext())
{
try
{
userToUpdate.MarkAsModified();
_context.Users.ApplyChanges(userToUpdate);
_context.SaveChanges();
return true;
}
catch (Exception ex)
{
// LOGS etc.
return false;
}
}
}
So when I call this function from the client, it gives us this exception:
AcceptChanges cannot continue because the object's key values conflict
with another object in the ObjectStateManager. Make sure that the key
values are unique before calling AcceptChanges.
"User" entity has one "many-to-1..0 (UserType)" and five "0..1-to-many" associations.
And that "UserType" has a "many-to-many (Modules)" association.
When we send a User instance to this function, UserType is included with it's Modules.
If you can guide me through solving this problem, that'd be great.
Thank you.

This is how I resolved this issue as a reference for the others having the same issue with me.
public bool UpdateUser(User userToUpdate)
{
using (DBContext _context = new DBContext())
{
try
{
User outUser = usersModel.Users.Single(x => x.UserId == userToUpdate.UserId);
outUser = userToUpdate;
_context.ApplyCurrentValues("Users", outUser);
_context.SaveChanges();
return true;
}
catch (Exception ex)
{
// LOGS etc.
return false;
}
}
}

Related

Entity Framework 5 Update works only once per object / row

I'm using Entity Framework 5 with MySQL Database and just wanted to update a row attribute "user_loginstatus" between 0 and 1. The first time when I log in via client it updates just fine for the first attempt, after trying to update again it doesn't do anything with no exception.
I log in like this:
public async void LoginExecute()
{
// Checking Connection before etc...
if (await _dataService.IsLoginDataValidTask(UserObj.Username, md5))
{
Trace.WriteLine("LoginCommand Execute: Eingeloggt");
UserObj = await _dataService.GetUserDataTask(UserObj.Username);
await _dataService.SetUserStatusTask(UserObj.Id, 1);
await _dataService.WriteLog(UserObj.Id, "login", "Programm", GetLocalAdress());
Messenger.Default.Send(UserObj);
Messenger.Default.Send(new NotificationMessage("GoToMenuPage"));
}
else
{
// Error Stuff...
}
}
SetUserStatus Method in DataService Class
public Task SetUserStatusTask(int id, int status)
{
return Task.Factory.StartNew(() =>
{
try
{
var user = _entities.users.Find(id);
user.user_loginstatus = status;
_entities.SaveChanges();
}
catch (Exception ex)
{
Trace.WriteLine("DataService SetUserStatusTask: " + ex.Message);
}
});
}
GetUserData Method in DataService Class
public Task<User> GetUserDataTask(string username)
{
return Task.Factory.StartNew(() =>
{
try
{
var user = from us in _entities.users
where us.user_name.Equals(username)
select new User
{
Id = us.user_id,
Username = us.user_name,
FirstName = us.user_firstname,
LastName = us.user_lastname,
Gender = us.user_gender,
Email = us.user_mail,
Group = us.user_usergroup,
Avatar = us.user_avatar,
LoginStatus = 1
};
return user.FirstOrDefault();
}
catch (Exception ex)
{
Trace.WriteLine("DataService GetUserDataTask: " + ex);
return null;
}
});
}
So "users" is my table from the database and "User" / "UserObj" my custom Object.
With the Messenger (from MVVM Light) I just set via MainViewModel the Views, reset the unused ViewModels (ViewModel = new VieModel(...); or ViewModel = null;) and pass the current / logged in User Object.
With the same strategy I just Logout like this
public ICommand LogoutCommand
{
get
{
return new RelayCommand(async () =>
{
await _dataService.SetUserStatusTask(CurrentUser.Id, 0);
if(CurrentUser.Id > 0 && IsLoggedIn)
await _dataService.WriteLog(CurrentUser.Id, "logout", "Programm", GetLocalAdress());
IsLoggedIn = false;
CurrentUser = new User();
Messenger.Default.Send(new NotificationMessage("GoToLoginPage"));
});
}
}
So I can log in with my running Client so often I want, but the "user_loginStatus" only sets the changes the first login time to 1 and back to 0, but when I log out then and login back with the same user, it wont change it anymore. When I login (still same running Client) with another user it sets again the first time the "user_loginstatus" to 1 and back to 0 and then only again when I restart my Client..
What could I do wrong?
This is just basically from my comment regarding the original question:
I had similiar problems several times. Usually it is based on the fact that the entity you modified can't be validated properly and your dbContext fails without a proper exception because it still holds on to false entity. If this is the case you could circumvent this problem by using scoped contexts and embedding your data access operations in a using statement.
Alternatively you could try to explicitly tell EF that the entity has changes e.g.:
_entities.Entry(user).State = EntityState.Modified;
Regarding your other question:
In theory you shouldn't have to tell EF explicitly that the entity's values have changed. Change tracking should do that automatically. The only exception i could think of, is when you try to modify an entity that is explicitly not tracked anymore. When you call _entities.Find(id) it will look in the context if it finds the object with the matching primary key value and load it. Since you already modified this object before, the context will simply get the old object you already modified to set the login status the first time.
This "old" object is probably not tracked anymore and you have to tell EF explicitly that it has changed, by changing it's state from attached to modified.
in LoginExecute() you have UserObj, but in LogoutCommand() you have CurrentUser. Is it OK?

EF EntityState.Modified Try Catch Issue

I have a code like this:
try
{
Member member = database.Members.Where(m=>m.ID=1).FirstOrDefault();
member.Name = "NewMemberName";
database.Entry(member).State = EntityState.Modified;
database.SaveChanges();
}
catch (Exception ex)
{
database.Logs.Add(new Log() { Value=ex.ToString() });
database.SaveChanges();
}
And Entity:
[StringLength(5)]
public string Name { get; set; }
If the Name String more than 5 it would be error and catch the exception ,but when I add a log then save ,the exception from SaveChange(); still remains,how should I do?(Can't change the schema)
the exception from SaveChange(); still remains
Well, if this throws an exception:
database.SaveChanges();
Then there's a pretty good chance that this will also throw an exception:
database.SaveChanges();
Basically, in your catch block you shouldn't be immediately re-trying the operation that just failed a millisecond ago. Instead, log the failure and handle the exception:
catch (Exception ex)
{
// DO NOT call SaveChanges() here.
}
Of course, if writing to the database is failing, then logging to the database is also likely to fail. Suppose for example that the connection string is wrong or the database is down or timing out. You can't log that.
I recommend using a logging framework (log4net, NLog, etc.) as a separate dependency from your Entity Framework data access layer. It's a small learning curve, but you end up with a pretty robust logging system that can much more effectively handle problems. And can be easily configured to log to multiple places, so if writing to one error log (the database) fails then you still have another one (a file, for example).
At the very least, if persisting your data context fails, you'll need to log to a new data context. Otherwise the part that failed is still there.
Something structurally more like this:
try
{
using (var database = new DbContext())
{
Member member = database.Members.Where(m=>m.ID=1).FirstOrDefault();
member.Name = "NewMemberName";
database.Entry(member).State = EntityState.Modified;
database.SaveChanges();
}
}
catch (Exception ex)
{
using (var database = new DbContext())
{
database.Logs.Add(new Log() { Value=ex.ToString() });
database.SaveChanges();
}
}

.Net mvc EF codefirst how to hanle concurrent update requests to database

I've got a table in database:
USERID MONEY
______________
1 500
The money value could be changed only by logged in user that owns account. I've got a function like:
bool buy(int moneyToSpend)
{
var moneyRow = db.UserMoney.Find(loggedinUserID);
if(moneyRow.MONEY < moneyToSpend)
return false;
//code for placing order
moneyRow.MONEY -= moneyToSpend;
return true;
}
I know that mvc sessions are always synchronous, so there will never be 2 symulateous calls to this function in one user session. But what if user logs in to the site 2 times from different browsers? Will it be still single threaded session or I can get 2 concurrent requests to this function?
And if there will be concurrency then how should I handle it with EF? Normally in ADO I would use MSSQL's "BEGIN WORK" for this type of situation, but I have no idea on how to make it with EF.
Thank you for your time!
I would suggest you to use RowVersion to handle concurrent requests.
Good reference here: http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application
// in UserMoney.cs
[Timestamp]
public byte[] RowVersion { get; set; }
// in model builder
modelBuilder.Entity<UserMoney>().Property(p => p.RowVersion).IsConcurrencyToken();
// The update logic
public bool Buy(int moneyToSpend, byte[] rowVersion)
{
try
{
var moneyRow = db.UserMoney.Find(loggedinUserID);
if(moneyRow.MONEY < moneyToSpend)
{
return false;
}
//code for placing order
moneyRow.MONEY -= moneyToSpend;
return true;
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var submittedUserMoney = (UserMoney) entry.Entity;
var databaseValue = entry.GetDatabaseValues();
if (databaseValue == null)
{
// this entry is no longer existed in db
}
else
{
// this entry is existed and have newer version in db
var userMoneyInDb = (UserMoney) databaseValue.ToObject();
}
}
catch (RetryLimitExceededException)
{
// probably put some logs here
}
}
I do not think it would be a major problem for you since the idea is that MSSQL as far as i know will not allow asyncroneus data commits to the same user from the same thread it has to finish one process before moving to the next one but you can try something like this
using (var db = new YourContext())
{
var moneyRow = db.UserMoney.Find(loggedinUserID);
moneyRow.MONEY -= moneyToSpend;
bool saveFailed;
do
{
saveFailed = false;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Update original values from the database
var entry = ex.Entries.Single();
entry.OriginalValues.SetValues(entry.GetDatabaseValues());
}
} while (saveFailed);
}
More can be found here Optimistic Concurrency Patterns

aggregate exceptions with entity framework in dll

At work we are looking to move to an ORM (still using an access database with ADO!) I started building with entity framework and everything was going smoothly until I separated it off into it's own .dll (so we could have the website/crm/production/barcoding systems all using the same database logic).
The issue comes with handling the DbEntityValidationExceptions, my initial test code (which worked)
public override int SaveChanges(System.Data.Objects.SaveOptions options)
{
try{return base.SaveChanges(options);}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Class: {0}, Property: {1}, Error: {2}",
validationErrors.Entry.Entity.GetType().FullName,
validationError.PropertyName,
validationError.ErrorMessage);
}
}
//handle here
throw;
}
}
but this doesn't get caught now and i'm left handling a generic threadException. Is there any way to access the original data (cast the threadException?) or is there a different approach I should take. I think I just need a push in the right direction and I can figure something out.
Regards, Pete
UPDATE:
Hmm bit of an issue calling the GetValidationErrors. I notice that my database Context has the baseClass of ObjectContext and not DbContext (So I can't call the ValidateEntity). I'm using Entity Framework 5 with default code generation enabled - using a database first approach if that helps.
check for validation errors before calling SaveChanges. Something like...
var errors = context.GetValidationErrors();
if(errors.Any())
{
//handle validation errors
}
else
{
context.SaveChanges();
}
from memory, so the exact syntax may not be correct.
this is what I ended up going with in the end (i'll probably end up fleshing it out as I learn more about EF)
public List<DbEntityValidationException> vErrors = new List<DbEntityValidationException>();
public int DbChanges = 0;
public bool SaveChanges()
{
try
{
this.vErrors = (List<DbEntityValidationException>)base.GetValidationErrors();
if (this.vErrors.Count == 0)
{
this.DbChanges = base.SaveChanges();
return true;
}
}
catch (Exception Ex)
{
this.vErrors.Add(new DbEntityValidationException(string.Format("General Error: {0}", Ex.GetType().ToString())));
}
return false;
}
and from code
using(Db db = new Db())
{
//changes
if(db.SaveChanges)
{
//some message using db.DbChanges
}
else
{
//handle errors in db.vErrors
}

Annoying SQL exception, probably due to some code done wrong

I started working on this "already started" project, and I'm having a really annoying error when trying to execute some interactions with SQL Server 2008:
The server failed to resume the
transaction. Desc.:
One of these errors I get in this specific method call:
The aspx.cs Call:
busProcesso openProcess = new busProcesso(pProcessoId);
try
{
if (openProcess.GetDocument() == null)
{
//Irrelevant code.
}
}
catch{ //... }
The Business class (relevant part):
public class busProcesso : IbusProcesso
{
public Processo vProcesso { get; set; }
RENDBDataContext db;
public busProcesso()
{
vProcesso = new Processo();
}
public busProcesso(decimal pProcessoId)
{
db = new RENDBDataContext();
try
{
vProcesso = db.Processos.SingleOrDefault(x => x.Id == pProcessoId);
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
public string GetDocument()
{
try
{
string document = null;
foreach (Processo_has_Servico ps in ListaServicosProcesso())
{
if (ps.Servico.Document != null) //Get the error right at this line.
{
document = ps.Servico.Document;
}
}
return document ;
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
public IQueryable<Processo_has_Servico> ListaServicosProcesso()
{
db = new RENDBDataContext();
try
{
return from ps in db.Processo_has_Servicos
join s in db.Servicos on ps.Servico_Id equals s.Id
where ps.Processo_Id == vProcesso.Id
select ps;
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
}
As I said, the error occurs right at the line:
if (ps.Servico.Document != null) from the GetDocument() method.
Opening SQL Server Activity Monitor, I see there is a process for my database (.Net SqlClient Data Provider)
After some time/use (when I start to get the "server failed to resume the transaction" error), I go to the SQL Server Activity Monitor and there's around 5 or 6 more identical processes that weren't killed and (probably) should've been. When I manually kill them, the error stops for a while, until it starts again.
I'm not really good at working in OO and all, so I'm probably missing something, maybe some way to close one of these connections. Also, any help/tip about this structure will be welcome.
PS. The error doesn't happen everytime. Sometimes it runs just perfectly. Then it starts to give the error. Then it stops. Sometimes it happens just once.. pretty weird.
The code in ListaServicosProcesso is creating the context db. Then it is returning an IQueryable.
At this point no request has been sent to the database.
Then there is a for each in the code. At this point EF says "I need to get the data from the database". So it tries to get the data.
But the context db is now out of scope, so it crashes, on the first line that tries to use the data.
There are 2 ways to get around this:
return a list from ListaServicosProcesso, this will force the database call to execute
move the for each into ListaServicosProcesso
Edit
Pharabus is correct db is not out of scope. The problem is here:
db = new RENDBDataContext();
A new instance of the context is being created without the old one being disposed. Try Dispose of db at the end of ListaServicosProcesso. Even better place db in a using statement. But then the foreach must be moved inside the using statement.
Here's a couple of ideas to try.
1/ You can attach SQL server profiler to see the query that is being executed, which will allow you to copy and paste that query to see the data that is in the database. This might be help.
2/ You never check whether ps.Servico is null - you jump straight to ps.Servico.Document. If ps.Servico is null then you will get a null reference exception if you try to access any properties on that object.
I'm not sure of the exact cause of the error you're seeing (if you Google it, the references are all over the place...), but there are a few things you could improve in your code and I've found that just cleaning things up a bit often makes problems go away. Not always, but often.
I agree with the other answerers that it would help to keep better track of your DataContext(s). For example in you're creating it once in the constructor, then again in ListaServicosProcesso(). At that point vProcesso is on one DataContext and other entities will be on another, which gets messy.
I think you could simplify the whole thing a bit, for example you could combine GetDocument() and ListaServicosProcesso() like this:
public string GetDocument()
{
try
{
// Are you sure vProcesso is not null?
if (vProcesso == null)
return null;
// Only create the context if it wasn't already created,
if (db == null)
db = new RENDBDataContext();
return db.Processo_has_Servicos
.Where(ps => ps.Processo_Id == vProcesso.Id && ps.Servico.Document != null)
.Select(ps => ps.Servico.Document) // use an implicit join
.SingleOrDefault();
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}

Categories

Resources