如何确保WCF服务不会与数据库并行通信

时间:2019-05-14 10:48:53

标签: c# wcf

我有一个WCF服务,该服务应从数据库返回对象,但每个实体仅应返回一次。我想避免出现这样的情况:许多客户端正在使用服务,并且他们可以获得相同的Request实体。

public Request GetChangeRequest()
{
    using (var context = new Data.Core.Context())
    {
        var request = context.Requests
            .Where(r => r.IsAvaible)
            .FirstOrDefault();

        if (request != null)
        {
            request.IsAvaible = false;
            context.SaveChanges();
        }       
        return request;
    }
}

我真的很想知道是否有理由给予额外的安全性,例如锁定数据库。为此,我已经管理了以下内容:

public Request GetChangeRequest()
{
    using (var context = new Data.Core.Context())
    {
        context.OnLock<Request>(context.GetTableName<Request>(), () =>
        {
            var request = context.Requests
                .Where(r => r.IsAvaible)
                .FirstOrDefault();

            if (request != null)
            {
                request.IsAvaible = false;
                context.SaveChanges();
            }       
            return request; 
        });
    }
}

public static class DBContextExtensions
{
    public static string GetTableName<T>(this DbContext context) where T : class
    {
        var type = typeof(T);
        var entityName = (context as System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext.CreateObjectSet<T>().EntitySet.Name;
        var tableAttribute = type.GetCustomAttributes(false).OfType<System.ComponentModel.DataAnnotations.Schema.TableAttribute>().FirstOrDefault();

        return tableAttribute == null ? entityName : tableAttribute.Name;
    }

    public static T OnLock<T>(this DbContext context, string tableName, Func<T> action)
    {
        T res;
        using (DbContextTransaction scope = context.Database.BeginTransaction())
        {
            context.Database.ExecuteSqlCommand($"SELECT TOP 1 Id FROM {tableName} WITH (TABLOCKX, HOLDLOCK)");
            res = action.Invoke();
            scope.Commit();
        }
        return res;
    }
}

当两个请求实体返回给两个不同的客户端时,我无法重现Scenerio。这是否意味着WCF服务按顺序执行请求?

2 个答案:

答案 0 :(得分:3)

代替您自己实现锁定机制,一种可能的解决方案是将服务作为单例运行,并且不允许并行请求。

您可以通过将WCF服务属性 InstanceContextMode ConcurrencyMode 设置为 Single 来实现。

有关会话,实例化和并发性的更多信息,请参见here

答案 1 :(得分:0)

您应该能够使用并发检查来确保仅返回一个实体,而不会一次阻止WCF一次查询。

您在实体类中需要一个特殊的字段,该字段的属性为[Timestamp],然后在保存时为catch DbUpdateConcurrencyException,这将使您知道其他人已经返回了该记录,所以你应该再买一个。

public class Request
{
    ...
    [Timestamp]
    public byte[] RowVersion { get; set; }
}

public Request GetChangeRequest()
{
    using (var context = new Data.Core.Context())
    {
        while (true)
        {
            try
            {
                var request = context.Requests
                    .Where(r => r.IsAvaible)
                    .FirstOrDefault();

                request.IsAvaible = false;
                context.SaveChanges();
                return request;
            }
            catch (DbUpdateConcurrencyException)
            {
            }
        }
    }
}

有关更多详细信息,请参见here