ExecuteReader requires the command to have a transaction - entity-framework

We get a strange exception when passing a transaction to EF:
Exception thrown: 'System.InvalidOperationException' in
System.Data.dll
Additional information: ExecuteReader requires the command to have a
transaction when the connection assigned to the command is in a
pending local transaction. The Transaction property of the command
has not been initialized.
this.DbContext = this.DbContextFactory.CreateContext<TContext>(connection);
this.DbContext.Database.UseTransaction(transaction);
This exception is caught by EF because it is shown only when 'Break when thrown' is on. Is it expected behavior or are we doing something potentially wrong?
Here is how call stack looks like:
System.Data.dll!System.Data.SqlClient.SqlCommand.ValidateCommand(string method, bool async)
System.Data.dll!System.Data.SqlClient.SqlCommand.RunExecuteReader(System.Data.CommandBehavior cmdBehavior, System.Data.SqlClient.RunBehavior runBehavior, bool returnStream, string method, System.Threading.Tasks.TaskCompletionSource<object> completion, int timeout, out System.Threading.Tasks.Task task, bool asyncWrite)
System.Data.dll!System.Data.SqlClient.SqlCommand.RunExecuteReader(System.Data.CommandBehavior cmdBehavior, System.Data.SqlClient.RunBehavior runBehavior, bool returnStream, string method)
System.Data.dll!System.Data.SqlClient.SqlCommand.ExecuteReader(System.Data.CommandBehavior behavior, string method)
EntityFramework.dll!System.Data.Entity.Infrastructure.Interception.InternalDispatcher<System.Data.Entity.Infrastructure.Interception.IDbCommandInterceptor>.Dispatch<System.Data.Common.DbCommand, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader>, System.Data.Common.DbDataReader>(System.Data.Common.DbCommand target, System.Func<System.Data.Common.DbCommand, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader>, System.Data.Common.DbDataReader> operation, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader> interceptionContext, System.Action<System.Data.Entity.Infrastructure.Interception.IDbCommandInterceptor, System.Data.Common.DbCommand, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader>> executing, System.Action<System.Data.Entity.Infrastructure.Interception.IDbCommandInterceptor, System.Data.Common.DbCommand, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader>> executed)
EntityFramework.dll!System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.Reader(System.Data.Common.DbCommand command, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext interceptionContext)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlVersionUtils.GetServerType(System.Data.Common.DbConnection connection)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlProviderServices.QueryForManifestToken(System.Data.Common.DbConnection conn)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlProviderServices.GetDbProviderManifestToken.AnonymousMethod__9(System.Data.Common.DbConnection conn)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlProviderServices.UsingConnection.AnonymousMethod__32()
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute.AnonymousMethod__0()
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute<object>(System.Func<object> operation)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlProviderServices.GetDbProviderManifestToken(System.Data.Common.DbConnection connection)
EntityFramework.dll!System.Data.Entity.Core.Common.DbProviderServices.GetProviderManifestToken(System.Data.Common.DbConnection connection)
EntityFramework.dll!System.Data.Entity.Utilities.DbProviderServicesExtensions.GetProviderManifestTokenChecked(System.Data.Entity.Core.Common.DbProviderServices providerServices, System.Data.Common.DbConnection connection)
mscorlib.dll!System.Collections.Concurrent.ConcurrentDictionary<System.Tuple<System.Type, string, string>, string>.GetOrAdd(System.Tuple<System.Type, string, string> key, System.Func<System.Tuple<System.Type, string, string>, string> valueFactory)
EntityFramework.dll!System.Data.Entity.Utilities.DbConnectionExtensions.GetProviderInfo(System.Data.Common.DbConnection connection, out System.Data.Entity.Core.Common.DbProviderManifest providerManifest)
EntityFramework.dll!System.Data.Entity.DbModelBuilder.Build(System.Data.Common.DbConnection providerConnection)
EntityFramework.dll!System.Data.Entity.Internal.LazyInternalContext.CreateModel(System.Data.Entity.Internal.LazyInternalContext internalContext)
EntityFramework.dll!System.Data.Entity.Internal.RetryLazy<System.Data.Entity.Internal.LazyInternalContext, System.Data.Entity.Infrastructure.DbCompiledModel>.GetValue(System.Data.Entity.Internal.LazyInternalContext input)
EntityFramework.dll!System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
EntityFramework.dll!System.Data.Entity.Internal.LazyInternalContext.GetObjectContextWithoutDatabaseInitialization()
EntityFramework.dll!System.Data.Entity.Database.UseTransaction(System.Data.Common.DbTransaction transaction)
Transaction is created within Open method of the session:
public virtual void Open(IsolationLevel isolation, string user)
{
ValidateState(false);
this.m_user = user;
this.m_connection = this.m_database.CreateConnection();
this.m_connection.Open();
if (IsolationLevel.Unspecified != isolation)
{
this.m_transaction = this.m_connection.BeginTransaction(isolation);
}
}
Then this method is overreiden in the class which supports EF:
public override void Open(System.Data.IsolationLevel isolation, string user)
{
if (isolation == System.Data.IsolationLevel.Unspecified)
{
throw new InvalidOperationException("Isolation level 'Unspecified' is not supported");
}
base.Open(isolation, user);
this.DbContext = this.DbContextFactory.CreateContext<TContext>(this.Connection);
this.DbContext.Database.UseTransaction(this.Transaction);
}

During its initialization, DdContext will determine the version of SQL Server. This process will attempt to connect to the underlying server and query select cast (serverproperty ('EngineEdition') as int) using the provided connection, or in the absence of that, creating a new one from the configured connectionstring.
This will happen only once, after its first initialization.
Therefore, if your first use of DbContext in your application life cycle is from within a transaction, this will cause DbContext to try to use this connection and result in the observed error.
If you assure a first non-transactional call to DbContext (DbContext construction and usage for querying), you will avoid this behavior.
private static object _syncRoot = new object();
private static bool _Initialized = false;
private MyDbContext(string connectionString) : base(connectionString)
{
Database.SetInitializer<MyDbContext>(null);
}
private MyDbContext(DbTransaction dbTransaction) : base(dbTransaction.Connection, false)
{
Database.SetInitializer<MyDbContext>(null);
Database.UseTransaction(dbTransaction);
}
public static MyDbContext Factory()
{
return new MyDbContext(Tools.GetDefaultConnectionString());
}
public static MyDbContext Factory(DbTransaction dbTransaction)
{
if(_Initialized==false)
{
lock(_syncRoot)
{
if(_Initialized==false)
{
using (MyDbContext tempDbContext = new MyDbContext(dbTransaction.Connection.ConnectionString))
using (System.Data.Common.DbTransaction temptransaction = tempDbContext.BeginTransaction())
{
var mySampleData = tempDbContext.OneRowTable.AsNoTracking().ToList();
temptransaction.Commit();
_Initialized = true;
}
}
}
}
MyDbContext finalContext = new MyDbContext(dbTransaction);
return finalContext;
}
This is a solution for a scenario where you can not or do not want to use a TransactionScope in your software.

The issue appears on first access to to the underling ObjectContext (attempting to set the transaction accesses the underlying context). Using a TransactionScope is fine; this issue is when using a connection that already has a local transaction associated with it.
While a bit ugly, and approach such as the follow does work around this issue. Specific implementation details such as determining notCreateYet are left as an exercise.
if (notCreatedYet)
{
// Create a second session/connection to not touch the incoming connection.
// Using a TransactionScope works and proper enlistment is done.
// Attempts to open a manual transaction prior to creation will fail.
using (new TransactionScope(TransactionScopeOption.RequiresNew))
using (var initConn = new SqlConnection(connection.ConnectionString))
{
initConn.Open();
var tempContext = this.DbContextFactory.CreateContext<TContext>(connection);
// Touch the object context.
if (tempContext is IObjectContextAdapter contextAdapter)
{
_ = contextAdapter.ObjectContext;
}
}
}
// Then later on this is fine.
this.DbContext = this.DbContextFactory.CreateContext<TContext>(connection);
this.DbContext.Database.UseTransaction(transaction);

Related

Dbcontext disposed exception EF core 3.1

I recently migrated my web application from ASP.net to.NETCore, I have already registered the DBcontext using DI in my startup.cs
services.AddDbContext<MyContextDB>
(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")),
....
public partial class MyContextDB: IdentityDbContext<USER>, IMyContextDB
...
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
}
I have also avoided the use of "using" to retrieve data through the context , and I verified that I don't have any call to manually "Dispose()" the context
However I keep getting this exception whenever I reach this part of the application :
public class Licence : ILicence
{
private static IOptionsMonitor<LicenceConfiguration> _appSettings;
private readonly MyContextDB _context;
public Licence(MyContextDB context, IOptionsMonitor<LicenceConfiguration> optionsAccessor)
{
_context = context;
_appSettings = optionsAccessor;
}
public LICENCECODE GetLicenceCode(string key)
{
LICENCECODE LicenceCode = null;
LicenceCode = _context.LICENCECODE.SingleOrDefault(l => l.LICENCEKEY == key);
return LicenceCode;
}
}
"Cannot access a disposed object. A common cause of this error is
disposing a context that was resolved from dependency injection and
then later trying to use the same context instance elsewhere in your
application. This may occur if you are calling Dispose() on the
context, or wrapping the context in a using statement. If you are
using dependency injection, you should let the dependency injection
container take care of disposing context instances.\r\nObject name:
'MyContextDB'."
I've been through every article available on the internet about this exception but yet I can't identify the actual cause of it.
Could you please help me resolve it
The exception is raised specifically when this call is made :
public async Task<LICENCECODE> GetLicenceCode(string key)
{
LICENCECODE LicenceCode = null;
LicenceCode = await _context.LICENCECODE.SingleOrDefaultAsync(l => l.LICENCEKEY == key);
return LicenceCode;
}
PS: I tried to change the method to async because I thought that could be the cause of the issue but unfortunately it's still happening.
the call to that method is from another Model class
validLicence = _licence.CheckLicence(type.Name, ref message, out maxCount);
....
and then inside
CheckLicence
LICENCECODE LicenceCode = GetLicenceCode(LicenceKey).Result;
I guess it depends on how you are using your Licence class, but it might have to do with the lifetime of the request ending before you complete your work in Licence. This could for example happen if your controller action is asynchronous.
Try registering the class with transient lifetime instead:
services.AddTransient<ILicence, Licence>();

AspNet Boilerplate Parallel DB Access through Entity Framework from an AppService

We are using ASP.NET Zero and are running into issues with parallel processing from an AppService. We know requests must be transactional, but unfortunately we need to break out to slow running APIs for numerous calls, so we have to do parallel processing.
As expected, we are running into a DbContext contingency issue on the second database call we make:
System.InvalidOperationException: A second operation started on this context
before a previous operation completed. This is usually caused by different
threads using the same instance of DbContext, however instance members are
not guaranteed to be thread safe. This could also be caused by a nested query
being evaluated on the client, if this is the case rewrite the query avoiding
nested invocations.
We read that a new UOW is required, so we tried using both the method attribute and the explicit UowManager, but neither of the two worked.
We also tried creating instances of the referenced AppServices using the IocResolver, but we are still not able to get a unique DbContext per thread (please see below).
public List<InvoiceDto> CreateInvoices(List<InvoiceTemplateLineItemDto> templateLineItems)
{
List<InvoiceDto> invoices = new InvoiceDto[templateLineItems.Count].ToList();
ConcurrentQueue<Exception> exceptions = new ConcurrentQueue<Exception>();
Parallel.ForEach(templateLineItems, async (templateLineItem) =>
{
try
{
XAppService xAppService = _iocResolver.Resolve<XAppService>();
InvoiceDto invoice = await xAppService
.CreateInvoiceInvoiceItem();
invoices.Insert(templateLineItems.IndexOf(templateLineItem), invoice);
}
catch (Exception e)
{
exceptions.Enqueue(e);
}
});
if (exceptions.Count > 0) throw new AggregateException(exceptions);
return invoices;
}
How can we ensure that a new DbContext is availble per thread?
I was able to replicate and resolve the problem with a generic version of ABP. I'm still experiencing the problem in my original solution, which is far more complex. I'll have to do some more digging to determine why it is failing there.
For others that come across this problem, which is exactly the same issue as reference here, you can simply disable the UnitOfWork through an attribute as illustrated in the code below.
public class InvoiceAppService : ApplicationService
{
private readonly InvoiceItemAppService _invoiceItemAppService;
public InvoiceAppService(InvoiceItemAppService invoiceItemAppService)
{
_invoiceItemAppService = invoiceItemAppService;
}
// Just add this attribute
[UnitOfWork(IsDisabled = true)]
public InvoiceDto GetInvoice(List<int> invoiceItemIds)
{
_invoiceItemAppService.Initialize();
ConcurrentQueue<InvoiceItemDto> invoiceItems =
new ConcurrentQueue<InvoiceItemDto>();
ConcurrentQueue<Exception> exceptions = new ConcurrentQueue<Exception>();
Parallel.ForEach(invoiceItemIds, (invoiceItemId) =>
{
try
{
InvoiceItemDto invoiceItemDto =
_invoiceItemAppService.CreateAsync(invoiceItemId).Result;
invoiceItems.Enqueue(invoiceItemDto);
}
catch (Exception e)
{
exceptions.Enqueue(e);
}
});
if (exceptions.Count > 0) {
AggregateException ex = new AggregateException(exceptions);
Logger.Error("Unable to get invoice", ex);
throw ex;
}
return new InvoiceDto {
Date = DateTime.Now,
InvoiceItems = invoiceItems.ToArray()
};
}
}
public class InvoiceItemAppService : ApplicationService
{
private readonly IRepository<InvoiceItem> _invoiceItemRepository;
private readonly IRepository<Token> _tokenRepository;
private readonly IRepository<Credential> _credentialRepository;
private Token _token;
private Credential _credential;
public InvoiceItemAppService(IRepository<InvoiceItem> invoiceItemRepository,
IRepository<Token> tokenRepository,
IRepository<Credential> credentialRepository)
{
_invoiceItemRepository = invoiceItemRepository;
_tokenRepository = tokenRepository;
_credentialRepository = credentialRepository;
}
public void Initialize()
{
_token = _tokenRepository.FirstOrDefault(x => x.Id == 1);
_credential = _credentialRepository.FirstOrDefault(x => x.Id == 1);
}
// Create an invoice item using info from an external API and some db records
public async Task<InvoiceItemDto> CreateAsync(int id)
{
// Get db record
InvoiceItem invoiceItem = await _invoiceItemRepository.GetAsync(id);
// Get price
decimal price = await GetPriceAsync(invoiceItem.Description);
return new InvoiceItemDto {
Id = id,
Description = invoiceItem.Description,
Amount = price
};
}
private async Task<decimal> GetPriceAsync(string description)
{
// Simulate a slow API call to get price using description
// We use the token and credentials here in the real deal
await Task.Delay(5000);
return 100.00M;
}
}

An exception of type 'System.InvalidOperationException' occurred in Microsoft.EntityFrameworkCore.dll but was not handled in user code

I'm trying to call a method from IActionResult using Task.Run. This is the Exception I am getting.
An exception of type 'System.InvalidOperationException' occurred in
Microsoft.EntityFrameworkCore.dll but was not handled in user code:
'An exception was thrown while attempting to evaluate a LINQ query
parameter expression. To show additional information call
EnableSensitiveDataLogging() when overriding DbContext.OnConfiguring.'
Inner exceptions found, see $exception in variables window for more
details. Innermost exception System.NullReferenceException : Object
reference not set to an instance of an object.
Initialization of EntityContext instance:
private readonly EntityContext _context;
public ApiController (
UserManager<User> userManager,
SignInManager<User> signInManager,
ILoggerFactory loggerFactory,
IMemoryCache memoryCache,
EntityContext context,
IRepository repository,
Context session,
IEmailService emailService,
IHostingEnvironment environment,
IHttpContextAccessor contextAccessor, ViewRender view, IStringLocalizer<SharedResources> localizer) : base (userManager, signInManager, loggerFactory, memoryCache, context, repository, session, contextAccessor) {
//_view = view;
_emailService = emailService;
this.environment = environment;
_localizer = localizer;
this._context = context;
}
Startup.cs
services.AddEntityFrameworkNpgsql ()
.AddDbContext<EntityContext> (
options => options.UseNpgsql (connectionString)
);
Calling method from controller:
if(updated){
Task t1 = Task.Run(()=>SendEmailAsync(entity,true,responsible,_context));
}else{
Task t1 = Task.Run(()=>SendEmailAsync(entity,false,responsible,_context));
}
Method I am calling:
public void SendEmailAsync (Activity entity, bool updated, User responsible, EntityContext ctx) {
List<string> emailList = new List<string> ();
var mail = new MailComposer (_emailService, environment, _localizer);
if (responsible.IsSubscriber) {
emailList.Add (responsible.Email);
}
if (entity.Participants.Count > 0) {
foreach (var item in entity.Participants) {
var p = ctx.Users.Where(c=>c.Id==item.Participant.Id).FirstOrDefault(); //This is where I am getting an exception.
if (p.IsSubscriber) {
emailList.Add (p.Email);
}
}
}
if (emailList.Count != 0) {
var emailArray = emailList.ToArray ();
if (updated) {
mail.SendActivityUpdate (entity, emailArray);
} else {
mail.SendActivityCreated (entity, emailArray);
}
}
}
For your issue, this is caused by that you are reference a scoped service EntityContext from another thread. For EntityContext, it will be disposed when the request returned from Controller.
As the suggestion from Chris, you may call t1.Wait(); to complete the t1 task before the request return back to client. By calling t1.Wait();, the EntityContext _context will not be disposed and then you won't get any error.
For another option, you may try pass IServiceProvider to create a new EntityContext instead of referencing the existing EntityContext which is created by Controller
public class HomeController : Controller
{
private readonly ApplicationDbContext _context;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<HomeController> _logger;
public HomeController(ApplicationDbContext context
, IServiceProvider serviceProvider
, ILogger<HomeController> logger)
{
_context = context;
_serviceProvider = serviceProvider;
_logger = logger;
}
public IActionResult TestTask()
{
Task t1 = Task.Run(() => SendEmailAsync(_serviceProvider));
//t1.Wait();
return Ok();
}
private void SendEmailAsync(IServiceProvider serviceProvider)
{
var context = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService<ApplicationDbContext>();
var result = context.Student.ToList();
_logger.LogInformation(JsonConvert.SerializeObject(result));
}
}
Task.Run will start a new thread, and unless you await it, the existing thread where the action is running will keep going, eventually returning and taking the context with it, which your method running in the new thread depends on. If you do await it, then there's no point in running in a separate thread; you're just consuming additional resources for no good reason.
In short, you should not be using Task.Run for this at all. It's not the same as "running the background". Instead, you should schedule the email to be sent on different process or at the very least an IHostedService. You can use QueuedBackgroundService. There's an implementation available at https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2&tabs=visual-studio#queued-background-tasks.

EF6 Code First Migrations: migrating events

I have a code-first scenario with migrations enabled, AutomaticMigrationsEnabled is disabled, and the DB initializer is set to MigrateDatabaseToLatestVersion. I'd like to "catch" Migration events for logging purposes.
I tried to do it in the Seed() but it's called every single run, regardless of whether the underlying database needs a migration to match the model or not.
Is there a proper way to do this?
Solution 1)
Check if you need migration:
var migrator = new DbMigrator(new DbMigrationsConfiguration());
// If any migration is required then Count will be greater than 0
// 0 means no migration required
if (migrator.GetPendingMigrations().Count() > 0)
{
// Fire your event here!
}
Soultion 2)
Use a logging decorator to log progress, in this use case you do not need the event.
public class MyLogger : System.Data.Entity.Migrations.Infrastructure.MigrationsLogger
{
public override void Info(string message)
{
// Short status messages come here
}
public override void Verbose(string message)
{
// The SQL text and other info comes here
}
public override void Warning(string message)
{
// Warnings and other bad messages come here
}
}
To migrate to latest version, you have to call it like that:
DbMigrator migrator = new DbMigrator(new MyConfiguration());
MigratorLoggingDecorator logger = new MigratorLoggingDecorator(migrator, new MyLogger());
// This line will call the migration + logging
logger.Update();
Extra info:
You can create your custom MigratorLoggingDecorator decroator like that:
MyMigratorLoggingDecorator: MigratorLoggingDecorator {
internal override Upgrade(IEnumerable<string> pendingMigrations, string targetMigrationId, string lastMigrationId)
{
// Fire your event here!
base.Upgrade(..)
}
..}

How to persist JobDataMap changes from remote client

I'm working on a basic web client for Quartz.NET that among other things supports the modification of a job's JobDataMap at runtime.
My job is decorated with the following attributes which I believe is all that is necessary to make the job stateful:
[PersistJobDataAfterExecution]
[DisallowConcurrentExecution]
public class SampleTenantJob : IJob { ... }
At runtime, I execute the following code but the JobDataMap is not persisted:
public void UpdateJobProperties(string jobName, string groupName, IDictionary<string, object> properties)
{
IJobDetail job;
if (!TryGetJob(jobName, groupName, out job))
return;
foreach (var property in properties)
{
if (job.JobDataMap.ContainsKey(property.Key) && property.Value != null)
{
job.JobDataMap.Put(property.Key, property.Value);
}
}
}
I thought initially this was because I was using the XMLSchedulingDataProcessorPlugin for jobs but I've tried both the in memory (RAMStore) and AdoJobStore and still can not persist JobDataMap changes made by the remote client.
PersistJobDataAfterExecution (as the name implies) only applies when the job has finished executing, so the following job will track the number of times it is executed
[PersistJobDataAfterExecution]
public class HelloJob : IJob
{
public void Execute(IJobExecutionContext context)
{
int count = (int?) context.MergedJobDataMap["Count"] ?? 1;
Console.WriteLine("hello " + count.ToString() );
context.JobDetail.JobDataMap.Put("Count" , ++count);
}
}
Without the PersistJobDataAfterExecution attributes, count is always the same.
Since you aren't running the job, this doesn't help you, and I think you have to delete and re-create the job with the new JobDataMap.
Of course, you aren't forced to use JobDataMap and can always to read and store information for your job from somewhere else.