Per quanto ho capito, non vi è alcuna opzione in EF (e EF Core) per bloccare esplicitamente le risorse che sto interrogando, ma ho bisogno di questa funzionalità abbastanza spesso e non mi sento davvero di tornare a scrivere selezionare le dichiarazioni ogni volta che ne avrò bisogno.
Dal momento che ne ho solo bisogno per postgres e secondo le specifiche FOR UPDATE
è l'ultimo elemento della query, il modo più semplice che ho pensato di implementarlo è stato quello di ottenere l'istruzione select come descritto qui: In Linq to Entities puoi convertire un IQueryable in un stringa di SQL? e aggiungere FOR UPDATE
e eseguirlo direttamente. Tuttavia questo mi darà una query con i segnaposto dei parametri o non una query preparata, il che significa che la memorizzazione nella cache per il piano di esecuzione non funzionerà realmente su Postgres, quindi in entrambi i casi è un no go.
Linq to SQL aveva il metodo DataContext.GetCommand
ma non sembra esserci nulla di equivalente in EF e in particolare in EF Core. Ho anche dato un'occhiata a EntityFramework.Extended e ai loro aggiornamenti / eliminazioni batch, ma dal momento che devono trasformare l'istruzione select in un'istruzione diversa devono affrontare una complessità molto maggiore di me e quindi spero in una soluzione più semplice.
Aggiornare:
Nel caso in cui non fosse chiaro dalla descrizione, voglio creare un metodo di estensione come questo:
public static IList<T> ForUpdate (this IQueryable<T> me)
{
// this line is obviously what is missing for me :)
var theUnderlyingCommand = me.GetTheUnderlyingDbCommandOrSimilar();
theUnderlyingCommand.Text += "FOR UPDATE";
return me.ToList();
}
In questo modo, altri sviluppatori possono utilizzare EF via Linq come con tutte le altre procedure e invece di eseguire .ToList()
eseguono .ForUpdate()
. (Per Update esegue la query di proposito per semplificare l'implementazione, e anche perché FOR UPDATE
è l'ultima opzione supportata da postgres, in seguito non dovrebbe esserci più nient'altro)
Questo lavoro è per me usando SQLServer (nessun metodo asincrono testato):
Innanzitutto, crea un DbCommandInterceptor (ho chiamato 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);
}
}
Quindi in Web.config registra la tua classe intercettore
<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>
Ora creo una classe statica chiamata 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");
}
}
Questo è tutto, posso usare all'interno di una transazione di database come:
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
}
Ci scusiamo per il mio inglese, spero che il mio esempio sia di aiuto.