How to implement Select For Update in EF Core

前端 未结 2 1029
鱼传尺愫
鱼传尺愫 2021-01-13 03:03

As far as I\'ve understood it, there is no option in EF (and EF Core) to explicitly lock resources which I\'m querying, but I\'ll need this functionality quite often and don

相关标签:
2条回答
  • 2021-01-13 03:42

    According to this issue there is no easy way to implement locks hints and other database oriented calls in ef core

    I implemented UPDLOCK with MsSQL and ef core in my project this way:

    public static class DbContextExtensions
    {
        public static string GetUpdLockSqlForEntity<T>(this DbContext dbContext, int entityPk, bool pkContainsTableName = true) where T : class
        {
            var mapping = dbContext.Model.FindEntityType(typeof(T)).Relational();
            var tableName = mapping.TableName;
            var entityPkString = entityPk.ToString();
            string idPrefix = pkContainsTableName ? tableName.Substring(0, tableName.Length - 1) : string.Empty;
            return $"Select 1 from {tableName} with (UPDLOCK) where {idPrefix}Id = {entityPkString}";
        }
    }
    

    We are using this method in database transaction as raw sql call(lock will be released after commit or rollback):

    using (var dbTran = await DataContext.Database.BeginTransactionAsync(IsolationLevel.ReadCommitted))
    {
        try
        {
            await DataContext.Database.ExecuteSqlCommandAsync(DataContext.GetUpdLockSqlForEntity<Deposit>(entityId));
            dbTran.Commit();
        }
        catch (Exception e)
        {
            dbTran.Rollback();
            throw;
        }
    }
    
    0 讨论(0)
  • 2021-01-13 03:51

    This work's for me using SQLServer (no tested async methods):

    First, create a DbCommandInterceptor (I called HintInterceptor.cs)

    using System;
    using System.Data.Common;
    using System.Data.Entity.Infrastructure.Interception;
    using System.Text.RegularExpressions;
    
    public class HintInterceptor : DbCommandInterceptor
    {
        private static readonly Regex _tableAliasRegex = new Regex(@"(?<tableAlias>FROM +(\[.*\]\.)?(\[.*\]) AS (\[.*\])(?! WITH \(*HINT*\)))", RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.Compiled);
    
        [ThreadStatic]
        public static string HintValue;
    
        private static string Replace(string input)
        {
            if (!String.IsNullOrWhiteSpace(HintValue))
            {
                if (!_tableAliasRegex.IsMatch(input))
                {
                    throw new InvalidProgramException("Não foi possível identificar uma tabela para ser marcada para atualização(forupdate)!", new Exception(input));
                }
                input = _tableAliasRegex.Replace(input, "${tableAlias} WITH (*HINT*)");
                input = input.Replace("*HINT*", HintValue);
            }
            HintValue = String.Empty;
            return input;
        }
    
        public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            command.CommandText = Replace(command.CommandText);
        }
    
        public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            command.CommandText = Replace(command.CommandText);
        }
    }
    

    So into Web.config register the your interceptor class

    <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
    <interceptors> 
      <interceptor type="Full.Path.Of.Class.HintInterceptor, Dll.Name" />
    </interceptors>
    </entityFramework>
    

    Now I create a static class called HintExtension

    public static class HintExtension
    {
        public static IQueryable<T> WithHint<T>(this IQueryable<T> set, string hint) where T : class
        {
            HintInterceptor.HintValue = hint;
            return set;
        }
        public static IQueryable<T> ForUpdate<T>(this IQueryable<T> set) where T : class
        {
            return set.WithHint("UPDLOCK");
        }
    }
    

    That's All, I can use inside a database transaction like:

    using(var trans = context.Database.BeginTransaction())
    {
            var query = context.mydbset.Where(a => a.name == "asd").ForUpdate();
            // not locked yet
            var mylist = query.ToList();
            // now are locked for update
            // update the props, call saveChanges() and finally call commit ( or rollback)
            trans.Commit();
            // now are unlocked
    }
    

    Sorry for my English, I hope my example will help.

    0 讨论(0)
提交回复
热议问题