Domanda

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

È stato utile?

Soluzione

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

  1. Set EntityDataSource.StoreOriginalValuesInViewState = FALSE. (I did this in the designer.)
  2. Create private member to hold pre-edit country values

     private static Country editCountry = null;  
    
  3. 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);
    }
    
  4. 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;
    }
    
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top