开发者

WCF Entity Framework Concurrency

I've got a WCF service that is making calls to my Entity Framework Repository classes to access data. I'm using Entity Framework 4 CTP, and am using my own POCO objects rather than the auto generated entity objects.

The context lifetime is limited to the method call. For Select/Insert and Update methods I create the context and dispose of it in the same method returning disconnected entity objects.

I'm now trying to work out the best way to handle concurrency issues. For example this is what my update method looks like

public static Sale Update(Sale sale)
{
    using (var ctx = new DBContext())
    {
        var SaleToUpdate =
            (from t in ctx.Sales where t.ID == sale.ID select t).FirstOrDefault();
        if (SaleToUpdate == null) throw new EntityNotFoundException();
        ctx.Sales.ApplyCurrentValues(sale);
        ctx.SaveChanges();
        return sale;
    }
}

This works fine, but because I'm working in a disconnected way no exception is thrown if the record has been modified since you picked it up. This is going to cause concurrency issues.

What is the best way to solve this when your using the entity framework over WCF and are not keeping a global context?

The only method I can think of is to give my objects a version number and increment it each time a save is called. This would then allow me to check the version hasnt changed before I save. Not the neatest solution I know and would still allow the client to change their version number which I really don't want them to be able to do.

EDIT : Using Ladislav Mrnka's suggestion of RowVersion fields in my entities, each of my entities now has a field called Version of type RowVersion. I then changed my Update method to look like this.

public static Sale Update(Sale sale)
{
    using (var ctx = new DBContext())
    {
        var SaleToUpdate =
            (from t in ctx.Sales where t.ID == sale.ID select t).FirstOrDefault()开发者_开发知识库;
        if (SaleToUpdate == null) throw new EntityNotFoundException();
        if (!sale.Version.SequenceEqual(SaleToUpdate .Version)) 
            throw new OptimisticConcurrencyException("Record is out of date");
        ctx.Sales.ApplyCurrentValues(sale);
        ctx.SaveChanges();
        return sale;
    }
}

It seems to work but if I should be doing it differently please let me know. I tried to use Entity Frameworks built in concurrency control by setting the version fields concurrency mode to fixed, unfortunately this didn't work as when I did the query to get the unchanged SaleToUpdate it picked up its version and used that to do its concurrency check which is obviously current. It feels like the entity framework might be missing something here.


Like it mentioned, the best practice is to use a column of row version type in your DB table for concurrency checking, but how it is implemented with Code First:
When using Code First in CTP3, you would need to use the fluent API to describe which properties needs concurrency checking but in CTP4 this can be done declaratively as part of the class definition using data annotation attributes:

ConcurrencyCheckAttribute:

ConcurrencyCheckAttribute is used to specify that a property has a concurrency mode of “fixed” in the model. A fixed concurrency mode means that this property is part of the concurrency check of the entity during save operations and applies to scalar properties only:

public class Sale
{
    public int SaleId { get; set; }

    [ConcurrencyCheck]
    public string SalesPersonName { get; set; }    
}

Here, ConcurrencyCheck will be turn on for SalesPersonName property. However, if you decide to include a dedicated Timestamp property of type byte[] in your class then TimestampAttribute will definitely be a better choice to go for:

TimestampAttribute:

TimestampAttribute is used to specify that a byte[] property has a concurrency mode of “fixed” in the model and that it should be treated as a timestamp column on the store model (non-nullable byte[] in the CLR type). This attribute applies to scalar properties of type byte[] only and only one TimestampAttribute can be present on an entity.

public class Sale
{
    public int SaleId { get; set; }

    1674977160
    public byte[] Timestamp { get; set; }
}

Here, not only Timestamp property will be taken as concurrency token, but also EF Code First learn that this property has store type of timestamp and also that this is a computed column and we will not be inserting values into this property but rather, the value will be computed on the SQL Server itself.


Don't use custom version number. Use build in row version data type of your DB. Row version data type is automatically modified each time you change the record. For example MSSQL has Timestamp data type. You can use the timestamp column in EF and set it as Fixed concurrency handler (not sure how to do it with EF Code First but I believe that fluent API has this possibility). The timestamp column has to be mapped to POCO entity as byte array (8 bytes). When you call your update method you can check timestamp of loaded object with timestamp of incomming object by yourselves to avoid unnecessary call to DB. If you do not make the check by yourselves it will be handled in EF by setting where condition in update statement.


Take a look at Saving Changes and Managing Concurrency

from the article:

try
{
    // Try to save changes, which may cause a conflict.
    int num = context.SaveChanges();
    Console.WriteLine("No conflicts. " +
    num.ToString() + " updates saved.");
}
catch (OptimisticConcurrencyException)
{
    // Resolve the concurrency conflict by refreshing the 
    // object context before re-saving changes. 
    context.Refresh(RefreshMode.ClientWins, orders);

    // Save changes.
    context.SaveChanges();
    Console.WriteLine("OptimisticConcurrencyException "
    + "handled and changes saved");
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜