My Issue
I have a simple WebForms project for testing concurrency.
I am using:
1. Entity Framework 4.3.1 code first approach.
2. DevExpress ASP.net controls to visualize my data. Specifically an ASPXGridView control.
3. MySQL as database backend.
Now I am having an issue with the concurrency check.
Even if I am the only user editing the data, if I edit the same record twice using the DevExpress ASPXGridView I get a concurrency exception!
The exception I get is :
System.Data.OptimisticConcurrencyException
My Setup
** Simplified here for brevity
My code first entity is defined something like this:
public class Country
{
//Some constructors here
[Required, ConcurrencyCheck]
public virtual string LastUpdate { get; set; }
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public virtual int CountryID { get; set; }
//Various other data fields here
}
You can see I have added a single field called LastUpdate which the concurrecny check is being tested against due to setting the [ConcurrencyCheck] attribute.
On my web page with the DevExpress ASPXGridView I am using an EntityDataSource to make the binding between the grid view and the entity framework. The grid view is using a popup editor. I have the following events hooked:
protected void Page_Load(object sender, EventArgs e)
{
//Hook entity datasource to grid view
dbgCountries.DataSource = CountriesEntityDataSource;
dbgCountries.DataBind();
}
protected void CountriesEntityDataSource_ContextCreating(object sender, EntityDataSourceContextCreatingEventArgs e)
{
//Create and hook my DBContext class to the entity
//datasources ObjectContext property.
var context = new MyDBContextClass();
e.Context = ((IObjectContextAdapter)context ).ObjectContext;
}
protected void dbgCountries_InitNewRow(object sender, DevExpress.Web.Data.ASPxDataInitNewRowEventArgs e)
{
//I create a new MyDBContextClass here and use it
//to get the next free id for the new record
}
protected void dbgCountries_CustomErrorText(object sender, DevExpress.Web.ASPxGridView.ASPxGridViewCustomErrorTextEventArgs e)
{
//My code to catch the System.Data.OptimisticConcurrencyException
//excpetion is in here.
//I try to rtefresh the entity here to get the latest data from
//database but I get an exception saying the entity is not being
//tracked
}
protected void dbgCountries_RowValidating(object sender, DevExpress.Web.Data.ASPxDataValidationEventArgs e)
{
//Basic validation of record update in here
}
protected void dbgCountries_RowUpdating(object sender, DevExpress.Web.Data.ASPxDataUpdatingEventArgs e)
{
//I set the LastUpdate field (my concurrency field)
//to the current time here
}
I also have some button events hooked to test a direct concurrecny test.
eg
- Get Entity
- Update Entity
- Update DB directly with sql
- Save Entity
- Get concurrency exception as expected
eg
- Get Entity
- Update Entity
- Save Entity
- No issue.
- Get Entity again.
- Update Entity again.
- Save Entity again.
- No issue.
These buttons work as expected. Only ther grid updates seem to have an issue.
Maybe it is because the grid needs to use ObjectContect and my entity framework classes are using DBContext?
My Attempted Fixes
I have scoured the internet trying to find a solution. Checked DevExpress forums, checked other posts here on StackOverflow, various posts on the internet, Microsoft MSDN articles on concurrency and I just can not work this out.
None of the posts were as 'simple' as mine. They all had other data involved. eg a master/detail
relashionship. custom editors. etc. I am using all inbuild DevExpress controls and just display a
single grid view on my db table / entity.
Some posts suggest refreshing the entities. I tried this but get an exception saying the entity is
not being tracked in the object state manager.
I tried refreshing the entity framework by destroying and recreating my object context / db
context but somehow I still get the concurrency issue.
I tried refreshing using the DBContexct and also the ObjectContext. Neither worked.
(objContext.Refresh(RefreshMode.StoreWins, entity). I either get an exception as stated]
earlier sayign the entity is not being tracked, or if I tell it to refresh only non modifed
entities then nothing happens at all (no refresh, no excpetion)
I tried making my DBContext global but this is no good as WebForms appears to want to recreate its
entire state and rehook its grids data context etc after every web refresh. (page loads, user
clicks edit, user clicks ok to update)
Now all of these solutions seem to takle what to do AFTER the concurrency exception. Seeing that I should not even be getting the exception in the first place I guess they would not help.
Suggestions
Do any of you have suggestions on how to make this work?
Do I have to maybe force the entity framework to refresh manually after posting data from the grid?
(I only just thought of this one now)
It seems a pretty simple setup I have. Maybe I am missing something very obvious. I have not worked with WebForms or EntityFramework much yet so there could be simple (and perhaps obvious) solutions I am missing?
Any help appreciated.
Thanks
Peter Mayes
I have managed to solve my issue.
It may not be the most correct solution but it is working and any progress at this point is much appreciated.
Approach
I tried refreshing Entity Framework after posting data in the ASPXGridView.
Many attempts. None worked.
I tried using a TimeStamp attribute on my Country entity but this did
not seem to map very well to MySQL. (However I might try this again now
I have solved the issue)
I then thought maybe my DevArt MySQL dot connector and MySQL was at fault.
So I switched over to MSSQL and its standard connector. This showed the same
issue am having with MySQL & co.
Finally I was mucking around with various attempts and noticed that if I go to a different
page on my web site, then back again the issue does not occur.
E.g.:
Edit Country and Save. No Issues.
Switch to other site page.
Switch back to Countries.
Edit Country and Save. No Issues.
The difference being, if I do not switch pages the second edit creates a concurrency exception.
With some more testing with co-workers I got a hunch that maybe the viewstate for the
entity datasource was not being refreshed after a post/update on the ASPGridView.
So what I did was:
> Set EntityDataSource.StoreOriginalValuesInViewState = FALSE
This stopped all concurrency working as no old/pre edit values were being stored and so
were not available for the concurrecny check.
I then thought I would force the oldvalues to be what was in the editor before I edited.
I was using ASPXGridView.RowUpdating to do this.
I thought thats ok, I can just use the OldValues passed to ASPXGridView.RowUpdating to
ensure entity framework is good to go.
Doing this I found some very odd behaviour...
If I:
- open edit form in browser A
- open edit form in browser B
- save changes in browser B (DB updates with new values here)
- save changes in browser A (DB updated here too. but should have been a
concurrency exception!)
The reason post from A was succeeding was that OldValues on A had been magically updated
to the new values B had posted!
Remember the edit form on A was open the whole time so it should not have updated its OldValues underneath. I have no idea why this occurs. Very odd.
Maybe OldValues are not retrieved by the DevExpress ASPXGridView until the
edit form is closing?
Anyway, then I thought. Fine, I will just work around that oddity. So to do so I created
a static member on the web page to store a copy of my Country entity.
When the user goes to open the editor I get the current values and store them.
Then when ASPXGridView.RowUpdating fires I push the stored old values back into the
OldValues data. (I also update my timstamp/concurrency field here too in the NewValues
data)
With this approach my concurrency now works. Hurah!.
I can edit locally as much as I want and get no conflicts. If I edit in two browsers at once the second one to post raises concurrency exception.
I can also switch between MySQL and MSSQL and both work correctly now.
Solution Summary
Set EntityDataSource.StoreOriginalValuesInViewState = FALSE. (I did this in the designer.)
Create private member to hold pre-edit country values
private static Country editCountry = null;
Hook StartRowEditing on ASPXGridView. In here I get the current country values and store them as 'pre edit' values. Note that CopyFrom is just a helper method on my entity.
protected void dbgCountries_StartRowEditing(object sender, DevExpress.Web.Data.ASPxStartRowEditingEventArgs e)
{
if (editCountry == null)
{
editCountry = new Country();
}
var context = new MyDBContext();
var currCountry = context.Countries.Where(c => c.CountryID == (int)(e.EditingKeyValue)).Single();
editCountry.CopyFrom(currCountry);
}
Hook RowUpdating on ASPXGridView. Here is where I make sure old values are correct before update goes ahead. This ensures concurrency will work as expected.
protected void dbgCountries_RowUpdating(object sender, DevExpress.Web.Data.ASPxDataUpdatingEventArgs e)
{
//Ensure old values are populated
e.OldValues["RowVersion"] = editCountry.RowVersion;
//No need to set other old values as I am only testing against
//the one field for concurrency.
//On row updating ensure RowVersion is set.
//This is the field being used for the concurrency check.
DateTime date = DateTime.Now;
var s = date.ToString("yyyy-MM-dd HH:mm:ss");
e.NewValues["RowVersion"] = s;
}
Related
I am in the begining of making a simple website using ASP.NET MVC4 CodeFirst Approach. The web site is built around users who are able to register, make posts etc. The existing UserProfile class was modified for the accommodation other fields (ex: FirstName, LastName etc).
When I ran the website I got a similar error:
Invalid column name 'FirstName'.
Invalid column name 'LastName'.
Invalid column name 'Phone'.
I red that this is because the Database is not updated as the model is updated. So I set the following on the Application_Start() in Global.asax with the intention of droppnng the database always (at least until I get the hang of it).
protected void Application_Start()
{
Database.SetInitializer(new DropCreateDatabaseAlways<UsersContext>());
//Other default generated stuff below...
}
I also tryed the DropCreateDatabaseIfModelChanges, but both methods didn't drop the database.
Isn't this possible? Why is it? Am I doing some thing wrong?
I believe it would be possible to store the info in a different table and link it to this, but I prefer to keep it in one table.
Also is it a bad idea to add UserProfiles to the websites context (lets say: DatabaseContext (which has other entities like Posts, Comments etc) and change the AccountsController to use DatabaseContext instead of UsersContext?
Thanks in advance.
Have you run Enable-Migrations in the Package Manager Console? You should see a migration folder in your project, with a configuration.cs file, ensure you have enabled automatic migrations.
public Configuration(){
AutomaticMigrationsEnabled = true;
//if you drop columns - consider this carefully...
AutomaticMigrationDataLossAllowed = true;
}
I have a frustrating issue when trying to pass a custom object from 1 page of a web site to another. When I try to use the object from session on the page 2 it is null. I believe that my syntax is correct as when I take the code out of my larger solution and run it on it's own it works fine.
I am wondering if there are any settings in visual studio, aspx files, project properties that may be set on my project that may be causing this session object not to work?
Simplied syntax that I am using is;
on page1
Person p = new Person;
p.name = "john";
p.secondName = "doe";
Session["person"] = p.
Response.Redirect("Page2.aspx")
Page 2 on page load method
Person p = (person)Session["person"]
textbox1.Text = p.name;
textbox2.Text = p.Secondname;
As I said this code works fine on it's own but not as part of my larger works project. Any ideas on why this may not be working would be greatly appreciated
There are three possible options.
First one is that inside your larger project session state is disabled. Please visit Turn Off ASP Session State in Active Server Pages and IIS for more details.
Second option would be that exception occurs somewhere inside your application (separate thread) resulting in application restart and session state loose. You can check this by hooking to Application error inside Global.asax like this:
void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
}
The last option would be that session["person"] value is changed in other module or page.
Try code with,
Response.Redirect("Page2.aspx", false);
See as per your code if there is database connected then i suggest you to do whatever you want to on page1 and save it to db in single or multiple tables, depending upon the requirements. Now paas the id to page2 as querystring. Format looks like
Response.Redirect("~/page2.aspx?id=67");
where is the dynamic unique id from database. Now on page 2.aspx query on the id you have passed.
Currently i am using Devexpress XtraReports V11.2.8. My problem is i am able to populate report in V11.1.6 but i am unable to populate report in V11.2.8 it showing only scroll bar in the reportviewer when i debug it is properly mapping and binding the data. FYI: I need to bind/Initialize reportviewer in button click only. Devexpress people said that i need to initialize reportviewer with the report in page_load it self but we can't do that because we have large amount of data exist and we need to populate report with particular input criteria matched result only. Please advice.
FYI: refer this link for devexpress response on the above issue http://www.devexpress.com/Support/Center/Issues/ViewIssue.aspx?issueid=Q362696
I can't find any solution about version problem without additional information.
but, the 2nd problem you told, i can give a possible solution to that. You can take necessary input you need for the report and then click on the button to preview the report regarding on inputs. It is better to create a stored procedure in your database for this report.
Suppose, you need to show a report for specific 'Cust_Id'. In the 'Form1' put a text field for 'Cust_Id' and a 'button'. On button click event put the following code,
private void simpleButton1_Click(object sender, EventArgs e)
{
Portfolio_XtraReport port = new Portfolio_XtraReport();
string custID = textEdit1.Text.ToString();
port.Portfolio(custID);
port.ShowPreview();
}
now in the 'Portfolio_XtraReport.cs' put the following code,
public Portfolio_XtraReport()
{
Portfolio("N");
}
public void Portfolio(string custId)
{
if (custId != "N")
{
InitializeComponent();
sP_PORTFOLIOTableAdapter.GetData(custId);
}
}
Here, sP_PORTFOLIOTableAdapter is the table adapter for the dataset, I used a stored procedure which takes 'Cust_Id' as input.
I created a table in sql database which has list of prices and Items Name....
I wrote a small coding to get the values of item names into my dropdownlist....
Now,
If i select an item in the dropdownlist, I need the price to displayed in the textbox or label... How can I do this? help me out!
There are several different ways of doing that.
You could
- load price/product as JS name value collection and do it client side - most efficient way
- populate dropdown with productId (value) and description and handle itemindexchanged event and do everything server side - not recommended for a public facing, busy web apps.
- send ajax call to web service to get price (client side) - that when you have a bit more complicated model.
It's up to you to decide.
Hope that helps.
Set the AutoPostback property of the DropDownBox to True
Then use something like this:
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
Label1.Text = DropDownList1.SelectedItem.Value;
}
I recently wrote an implementation of a VirtualizingWrapPanel that recycles containers as they scroll into and out of view.
On occasion I've noticed that the content rendered by the control is actually the previously contained data, not the current data. Performing any action on the control that forces a new render call updates the control such that it displays the correct data.
Could this a bug in the ItemContainerGenerator recycling or is it more likely in my recycling code? Is there a way I can force all my bindings to update (after updating the control with new content) without explicitly writing each binding expression in code behind?
I've seen problems very like this in the past when using virtualization when we were using custom controls that really weren't expecting their DataContexts to be changed once they were displayed.
If your panel is correctly (as it sounds) handing new DataContexts to the reused objects then it does sound like the reused objects aren't processing that DataContext change correctly. (This 'render' call you talk about would then pick up the new DataContext and display that.)
If you're using plain data binding in your control then I'm slightly stumped. (Is your panel re-Measure/Arranging the controls after they've got their new DataContext?)
The fix for us was to have our controls listen out for when their DataContext changed. (This is also useful for debugging virtualizing panels to test that the DataContext is coming in correctly.)
Sadly the OnDataContextChanged method isn't public in Silverlight but you can still find out about DC changes by binding to them.
public MyClass()
{
InitializeComponent();
SetBinding(MyDataContextProperty, new Binding());
}
private static readonly DependencyProperty MyDataContextProperty =
DependencyProperty.Register("MyDataContext",
typeof(object),
typeof(MyClass),
new PropertyMetadata(DataContextChanged));
private static void DataContextChanged(
object sender,
DependencyPropertyChangedEventArgs e)
{
MyClass source = (MyClass)sender;
source.OnDataContextChanged();
}
private void OnDataContextChanged()
{
// My DataContext has changed; do whatever is needed.
// re 'render' in your case?
}