Task.Factory.StarNew and ApplicationDbContext Update - c#

I need to update a table of my ApplicationDbContext within a Task; however, I'm not getting it done. Here is the error message I've got:
ex.Message = "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...
I understand it has something to do with threading which I'm not so familiar.
Here's the code:
[HttpPost]
public ActionResult WebHook([FromBody] BotRequest data)
{
Task.Factory.StartNew(() =>
{
//Read value from Table
ContextWatsonFB contextWatsonFB = _context.ContextWatsonFB.Where(m => m.RecipientId == recipientid).FirstOrDefault();
if (contextWatsonFB == null)
{
contextWatsonFB = new ContextWatsonFB()
{
RecipientId = recipientid
};
_context.Add(contextWatsonFB);
_context.SaveChanges();
}
else
{
if (!string.IsNullOrEmpty(contextWatsonFB.Context))
{
model = JsonConvert.DeserializeObject<Context>(contextWatsonFB.Context);
}
}
///DO SOME STUFF ////////////////
///Here I need to update my table using some values processed above in "some stuff"
ContextWatsonFB contextWatsonFB = _context.ContextWatsonFB.Where(m => m.RecipientId == recipientid).FirstOrDefault();
contextWatsonFB.Context = JsonConvert.SerializeObject(context);
_context.Update(contextWatsonFB);
_context.SaveChanges();
}
}
As you can figure it out, its a webhook connection for Facebook, which requires the process to be handled in a Task. Within "some stuff", basically I'm consuming IBM Watson Conversation service whom persist a conversation "context" that I'm not able to send back and forth to Facebook, that's why I figure it out to persist such data in a table to keep the differences among multiple request from facebook messenger.

Luckily, the following code did the trick:
private readonly IServiceProvider _provider;
public FacebookBotController(ApplicationDbContext context, IServiceProvider provider)
{
_provider = provider;
}
[HttpPost]
public ActionResult WebHook([FromBody] BotRequest data)
{
if (data == null || data?.entry?.Count == 0)
{
return new StatusCodeResult(StatusCodes.Status204NoContent);
}
try
{
var task = Task.Factory.StartNew(async () =>
{
using (IServiceScope scope = _provider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
ApplicationDbContext _contx = _provider.GetService<ApplicationDbContext>();
ContextWatsonFB contextWatsonFB = await _contx.ContextWatsonFB.Where(m => m.SenderId == senderId).FirstOrDefaultAsync();
if (contextWatsonFB == null)
{
context = null;
}
else
{
context = JsonConvert.DeserializeObject<Context>(contextWatsonFB.Context);
}
}
}
}

Related

How to retrieve a message from a repository with C# WebApi

I'm still learning how to use properly Dependencies with C# and WebApi and I came up with a problem. I have a repository where I register a user but since is a async task void method it doesn't return nothing. So the question is, what is the best way to do this? It would be with Task<T> and handling the result in the Controller ?
The Classes :
public interface IGeneral
{
Task RegisterAsync(UserModel model);
}
public class General : BaseRepository, IGeneral
{
public General(Context context) : base(context)
{
}
public async Task RegisterAsync(UserModel model)
{
var result = await Context.User.Where(a => a.Email == model.Email).FirstOrDefaultAsync();
if(result != null)
{
await Context.User.AddAsync(new Data.Access.Models.User
{ Date = DateTime.Now, Email = model.Email, Name = model.Name, Password = model.Password });
await Context.SaveChangesAsync();
}
}
}
public abstract class BaseRepository
{
protected readonly Context Context;
public BaseRepository(Context context)
{
Context = context;
}
}
The possible solution that you have suggested in your question - would be the approach I would use.
public async Task<bool> RegisterAsync(UserModel model)
{
bool operationResult = false;
var result = await Context.User.Where(a => a.Email == model.Email).FirstOrDefaultAsync();
if (result != null)
{
await Context.User.AddAsync(new Data.Access.Models.User
{ Date = DateTime.Now, Email = model.Email, Name = model.Name, Password = model.Password });
if(await Context.SaveChangesAsync() > 0)
{
operationResult = true;
}
}
return operationResult;
}
From MSDN documentation we know that SaveChangesAsync() signature
public virtual System.Threading.Tasks.Task<int> SaveChangesAsync ();
returns an int value.
Checking for a result greater than zero assert that changes have occurred and they have being made persistent.
You should not use Task<T> return type of RegisterAsync(UserModel model) method to handle result in the controller because
you have to handle exception in RegisterAsync method and you can return bool return type in result but in production, you have to write log for these exceptions when you catch it.
It's not a good way to do this in repository methods.
Repostiory should have a single responsibility.
You can keep this method as it is and can use try catch block in your services or in controller appropriately, depends on what you want to send at the time of exception by considering types of clients(Users)*

EF Core 3.1 context being disposed after first use

I am having an issue where while looping through a data set and looking in the db to see if it already exists, it works the first run but the second item causes an error.
The following method builds up a List from a raw data file
private async Task<List<Vehicle>> CreateListOfVehiclesFromAuctionDataFile(IEnumerable<string> rows)
{
var cars = new List<Vehicle>();
var vinList = new List<string>();
foreach (var dataRow in rows)
{
var data = dataRow.Split(",");
var dto = GetCarModelInfoFromAuctionData(dataRow);
if (vinList.Contains(data[14]))
{
continue;
}
vinList.Add(data[14]);
var car = new Vehicle
{
Vin = data[14],
InteriorColor = data[15],
ExteriorColor = data[16],
VehicleImageUrl = data[17],
Notes = data[18],
ModelId = await GetModelIdFromCarModelDto(dto)
};
cars.Add(car);
}
return cars;
}
That method calls this method within it
private async Task<int> GetModelIdFromCarModelDto(CarModelDto dto)
{
var modelId =0;
try
{
modelId = await _context.Models.Where(u => u.ModelYear == dto.ModelYear)
.Where(u => u.ModelType == dto.ModelType)
.Where(u => u.BodyStyle.Contains(dto.BodyStyle))
.Select(u => u.ModelId)
.FirstOrDefaultAsync();
}
catch (Exception ex)
{
//log error here
var errorMessage = $"Model id not found: {ex.Message}- Exception: {ex.InnerException}";
}
return modelId;
}
Those private method calls come from this public method
public async Task<int> AddVehicleDataFromAuctionFileAsync()
{
var currentRecords = _context.AuctionDatum.Count();
var data = await AzureDataRetrieval.GetDataAsStreamAsync(AzureService.AzureContainer,
AzureFilePathsFromMain.VehicleAuctionData);
var rows = ConvertAuctionDataStreamToDataArray(data);
**var cars = await CreateListOfVehiclesFromAuctionDataFile(rows);**
var datum = CreateListOfVehicleAuctionData(rows);
await _context.AuctionDatum.AddRangeAsync(datum);
await _context.Vehicles.AddRangeAsync(cars);
await _context.SaveChangesAsync();
return await _context.AuctionDatum.CountAsync() - currentRecords;
}
The main method call comes from the controller
public VehiclesController(CarCollectionDataContext context, IConfiguration configuration)
{
_context = context;
_configuration = configuration;
}
public async Task<IActionResult> Test()
{
var vm = new VehicleDataViewModel(VehicleView.Models);
var mgr = new VehicleDataManager(_context, _configuration);
**var datum = mgr.AddVehicleDataFromAuctionFileAsync();**
vm.Models = await mgr.GetModelListAsync();
return View(vm);
}
The first time through this method it works fine and retrieve's the ModelId. The second time through it fails with the following error message. The where clause parameters, when checked manually, show there is an item that should be retrieved.
Model id not found: 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.
Object name: 'CarCollectionDataContext'.- Exception:
I am not disposing the context anywhere in the code. The context IS brought in via DI
private readonly CarCollectionDataContext _context;
private readonly IConfiguration _configuration;
public VehicleDataManager(CarCollectionDataContext context, IConfiguration configuration)
{
_context = context;
_configuration = configuration;
AzureService = new AzureService.AzureService(_configuration);
}
I am at the end of my knowledge base on how to resolve this, any help appreciated.

The instance of entity type 'Bot' cannot be tracked because another instance with the same key value for {'Id'}

The instance of entity type 'Bot' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider us...
I know what this problem means. It is happening right here _context.Bots.Update(bot);.
The question is: is that a good way to solve this by adding .AsNoTracking() to all GetByXXX methods? Any suggestions?
[HttpPut("{id}")]
public async Task<ActionResult> UpdateAsync([FromRoute] int id, [FromBody] BotCreateUpdateDto botCreateUpdateDto)
{
if (id != botCreateUpdateDto.Id)
{
return BadRequest(new { Error = "Invalid ID." });
}
var user = await _userService.GetByEmailAsync(botCreateUpdateDto.Email);
if (user == null)
{
return BadRequest(new { Error = "Invalid e-mail." });
}
var cryptoPair = await _cryptoPairService.GetBySymbolAsync(botCreateUpdateDto.Symbol);
if (cryptoPair == null)
{
return BadRequest(new { Error = "Invalid crypto pair." });
}
var timeInterval = await _timeIntervalService.GetByIntervalAsync(botCreateUpdateDto.Interval);
if (timeInterval == null)
{
return BadRequest(new { Error = "Invalid time interval." });
}
var bot = new Bot
{
Id = botCreateUpdateDto.Id,
Name = botCreateUpdateDto.Name,
Status = botCreateUpdateDto.Status,
UserId = user.Id,
CryptoPairId = cryptoPair.Id,
TimeIntervalId = timeInterval.Id
};
bool updated;
try
{
updated = await _botService.UpdateAsync(bot);
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
if (updated)
{
return NoContent();
}
return NotFound();
}
public async Task<User> GetByEmailAsync(string email)
{
return await _context.Users
.Include(e => e.UserRoles)
.ThenInclude(e => e.Role)
.Include(e => e.Bots)
.SingleOrDefaultAsync(e => e.Email == email);
}
public async Task<CryptoPair> GetBySymbolAsync(string symbol)
{
return await _context.CryptoPairs
.Include(e => e.Bots)
.AsNoTracking()
.SingleOrDefaultAsync(e => e.Symbol == symbol);
}
public async Task<TimeInterval> GetByIntervalAsync(KlineInterval interval)
{
return await _context.TimeIntervals
.Include(e => e.Bots)
.AsNoTracking()
.SingleOrDefaultAsync(e => e.Interval == interval);
}
public async Task<bool> UpdateAsync(Bot bot)
{
_context.Bots.Update(bot);
var updated = await _context.SaveChangesAsync();
return updated > 0;
}
Sorry, I really didn't realise that I was creating a new Bot object instead of just getting it by id. There is also no point of .AsNoTracking.
var bot = await _botService.GetByIdAsync(id);
bot.Name = botCreateUpdateDto.Name;
bot.Status = botCreateUpdateDto.Status;
bot.CryptoPairId = cryptoPair.Id;
bot.TimeIntervalId = timeInterval.Id;
This solves the problem for anyone having same problem as me. Keep in mind that my all services are scoped as well as the DbContext. If yours are singleton, that will cause this problem as well.
I got same issue like :
The instance of entity type 'ClassName' cannot be tracked because another instance with the key value '{Id: 1}'
is already being tracked. When attaching existing entities, ensure that only one entity instance
with a given key value is attached.
I resolved with remove this .AsNoTracking() when fetch data from DB.

How to avoid not-safe context operations in EF Core? [duplicate]

This question already has answers here:
Entity Framework Core: A second operation started on this context before a previous operation completed
(20 answers)
Closed 4 years ago.
I'd want to know how why creating instances of other classes with current database context instances as a parameter and using that db context causes this exception to be raised
'A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.'
Imma use this sample code to show the problem
public class TestController : Controller
{
private readonly DbContext dbContext;
public Controller(DbContext ctx)
{
dbContext = ctx;
}
public async Task<IActionResult> Test(string id)
{
var isValid = new otherClass(dbContext).Validate(id);
if (!isValid)
{
return View("error");
}
var user = dbContext.Users.FirstOrDefault(x => x.Id == id);
user.Age++;
dbContext.SaveChanges(); // exception is being raised here. It is second .SaveChanges() here
return View();
}
}
public class otherClass
{
private readonly DbContext dbContext;
public otherClass(DbContext ctx)
{
dbContext = ctx;
}
public bool Validate(string id)
{
var user = dbContext.Users.FirstOrDefault(x => x.Id == id);
user.ValidationAttempt = DateTime.Now;
dbContext.SaveChanges();
return user.HasConfirmedEmail;
}
}
Generally in an MVC fashion youre going to want a DbContext on a per request basis but when using threading more control through using blocks can be beneficial, an easy way to set that up would be something along the lines of
public class TestController : Controller
{
private readonly Func<DbContext> dbContext;
public Controller(Func<DbContext> ctx)
{
dbContext = ctx;
}
public async Task<IActionResult> Test(string id)
{
using(var cntx = dbContext())
{
var isValid = new otherClass(cntx).Validate(id);
if (!isValid)
{
return View("error");
}
var user = cntx.Users.FirstOrDefault(x => x.Id == id);
user.Age++;
cntx.SaveChanges();
return View();
}
}
}
that essentially resolves a new DbContext per using block - and since each thread is then handling its own DbContext - shouldnt have any issues

my MVC3/EF6 Webapi throwing intermittent 500 errors, possible database connection issue?

I have an (inherited from previous coder) ASP.NET MVC3 webapi that utilises Entity Framework(6.1.3) and I'm not convinced it is performing correctly. Mostly through seeming lack of the USING method.
The Problem
The reason I don't think it is performing correctly is because this put Method gets called a lot (via a scheduled task on various external servers). and quite often an id will return a 500 internal server error. and then will run again and process correctly the next time the scheduled task is run.
The Code
The Controller MyApiController is as follows :
public class MyApiController : ApiController
{
static readonly IMyApiRepository myApiRepository = new MyApiRepository();
public HttpResponseMessage PutData(int id, int id2)
{
if(!myApiRespository.Update(id, id2))
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Error:Unable to Update");
}
else
{
return Request.CreateResponse(HttpStatusCode.OK);
}
}
}
Now the Repository MyApiRepository is as follows
public class MyApiRepository : IMyApiRepository
{
MyDBContext myContext;
public MyApiRepository()
{
myContext = new MyDBContext();
}
public bool Update(int id,int id2)
{
var myData = myContext.MyData.Where(x => x.id == id);
if (myData.Count() > 0)
{
MyData temp = myData.SingleOrDefault();
temp.Processed = true;
myContext.SaveChanges();
return true;
}
else
{
return false;
}
}
}
It looks like to me that the Database connection is permanently open, and that too many things are trying to use it all at once.
Should the ApiRepository constructor actually be empty
public MyApiRepository()
{
}
and the Put Method be
using (var myContext = new MyDBContext())
{
var myData = myContext.MyData.Where(x => x.id == id);
if (myData.Count() > 0)
{
//blah;
return true;
}
else
{
return false;
}
}
thereby opening a single connection per request?

Categories

Resources