IMemoryCache.GetOrCreateAsync returns null - c#

I have a lot of NullReferenceException exceptions on the line if (foosDictionary.TryGetValue(id, out var foo)) and I cannot in any way reproduce the issue.
Stack trace:
System.NullReferenceException: Object reference not set to an instance of an object.
at MyProject.Service.Baz(Guid id) in CustomClass.cs:line 900
at MyProject.Service.BazFeature(Guid id) in CustomClassCaller.cs:line 288
line 900 is the line in question. It is a direct reference to the dictionary: if (foosDictionary.TryGetValue(id, out var foo)).
Below is the code that leads to the problem line:
public Foo Baz(Guid id)
{
Func<Task<Dictionary<Guid, Foo>>> createDictionary =
async () => (await repository.GetFoos()).ToDictionary(sm => sm.Id);
var foosDictionary = await _cache.GetOrCreateAsync<Dictionary<Guid, Foo>>(
"cacheKey",
async cacheEntry =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(15);
return await createDictionary();
});
if (foosDictionary.TryGetValue(id, out var foo))
{
return foo;
}
}
// also there's the remaining code, but it shouldn't execute and it is lower than line 900. Anyway, here it is.
var newDictionary = await createDictionary();
if (newDictionary.TryGetValue(id, out var newFoo))
{
fooDictionary = newDictionary;
return newFoo;
}
throw new Exception("Error");
The Usual Suspects are:
await repository.GetFoos() - it uses Dapper.QueryAsync method to get IEnumerable<Foo> and it can't return null. Anyway, I tried hardcoding null return - and goes the next step
.ToDictionary() - it will throw on null. And it does if I hardcode the null return. Also it has different stacktrace and points to GetOrCreateAsync, not the line of code in question.
GetOrCreateAsync - all the other suspects will throw and give different stack trace so this must be it. This method returns null, but I can't figure out how and why.
Like, if there is null on the cache key, it should go and create the value. createDictionary can't return null so it's out of the question. So...how does that even happen? Why?
Memory cache is registered in Startup like this: services.AddMemoryCache(). I don't know whether it's Scoped or Singleton and the Microsoft docs don't address it in a straightforward way.
The app is ASP.NET Core 3.1, GetOrCreateAsync extension method is from v6.0.0, but the actual implementation (or so it seems) gets passed as v3.1.0.
How can I resolve the issue? Can GetOrCreateAsync really return null in some cases? If so, how to reproduce them?
PS. I've thought about thread 1 getting the entry and then thread 2 somehow nullifying it, but that shouldn't work since thread 1 already has the reference to the dictionary. Right?

Related

Trivial exit from an method that may perform an asynchronous call

I've got an issue calling a method that may return a Task<T> or null depending on the result of an initial synchronous lookup call (this itself might be an anti-pattern so please let me know).
I kind of want to return null if a trivial exit condition occurs but this is causing the calling (outer) method to fail because the outer call expects a Task<T> response (trivial exit) which gets pushed through to ConfigureAwait(true) which subsequently produces a NullReferenceException.
Outer calling method:
var res = await MyService.GetUserCourseStatusAsync(userID, productID).ConfigureAwait(true);
Intermediate method:
public Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// Look up User's ID (if exists)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return null; // Trivial return of null ("User not registered"). *** This causes exception on ConfigureAwait(true) above ***
// Another synchronous call
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"Product #{productID} is not a course");
// Asynchronous call to inner method (this bit works fine)
return GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}
So my thought then is that we should always return a Task<T> instead of null.
However, all of these cause compile errors:
//return null; // Trivial return of null ("User not registered"). *** This causes exception
// Compile error: CS0029: Cannot implicitly convert type 'GetUserCourseInner' to 'System.Threading.Tasks.Task<IGetUserCourseResponse>'
return new GetUserCourseInner(); // Not registered
// Compile error: CS1503 Argument 1: cannot convert from 'GetUserCourseInner' to 'System.Func<IGetUserCourseResponse>'
return new Task<IGetUserCourseResponse>(new GetUserCourseInner()); // Not registered
How do I return a dummy Task<T> that isn't a result of a async call?
Is this even the correct approach?
It would be better, as you suggested, to return a Task<IGetUserCourseResponse> which contains null (or some other sentinel value). You can create such a completed Task with Task.FromResult((IGetUserCourseResponse)null):
public Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// Look up User's ID (if exists)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return Task.FromResult((IGetUserCourseResponse)null);
// Another synchronous call
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"Product #{productID} is not a course");
// Asynchronous call to inner method (this bit works fine)
return GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}
Alternatively, you could make your outer method async. Note however that this changes its behaviour in the case where it throws an InvalidOperationException: instead of the method throwing this exception directly, it will instead return a Task which contains this exception. This may or may not be what you want:
public async Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// Look up User's ID (if exists)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return null;
// Another synchronous call
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"Product #{productID} is not a course");
// Asynchronous call to inner method (this bit works fine)
return await GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}
Just return a Task that holds a null value as result
return Task.FromResult<IGetUserCourseResponse>(null);

Azure Search SearchParameters Object reference not set to an instance of an object error

I am trying to add search fields to my Azure Search query (see below instantiation of the SearchParameters object).
public async Task StartAsync(IDialogContext context)
{
ISearchIndexClient indexClient = CreateSearchIndexClient();
try
{
Trace.TraceInformation($"Starting StartAsync");
SearchParameters searchParameters = new SearchParameters();
searchParameters.SearchFields.Add("StoreNumber");
searchParameters.SearchFields.Add("StoreName");
Trace.TraceInformation($"Finished adding search fields");
// Trace.TraceInformation($"Search Parameters added = {searchParameters.SearchFields.Count}");
DocumentSearchResult results = await indexClient.Documents.SearchAsync(searchText, searchParameters);
Trace.TraceInformation($"results obtained");
List<SearchHit> searchHits = results.Results.Select(r => ResultMapper.ToSearchHit(r)).ToList();
Trace.TraceInformation($"search hits {searchHits.Count}");
await SendResultsOfSearch(context, results);
}
catch (Exception ex)
{
Trace.TraceError($"Exception {ex.ToString()}");
}
}
For some reason it's throwing the following exception but I have no idea why?
2018-09-03T00:47:39 PID[3268] Information Starting StartAsync
2018-09-03T00:47:39 PID[3268] Error Exception System.NullReferenceException: Object reference not set to an instance of an object.
at LuisBot.Dialogs.SearchRBMDialog.<StartAsync>d__2.MoveNext() in C:\Users\jmatson\Downloads\retail-info-bot-v2-src\Dialogs\SearchRBMDialog.cs:line 32
The code compiles fine? And there are no constructor arguments required as far as I know.
SearchFields is not initialized by the SearchParameters constructor (see the source code here), so calling Add on it will result in NullReferenceException. It's of type IList, so the easiest way to initialize it is by assigning an array to it, like this:
searchParameters.SearchFields = new[] { "StoreNumber", "StoreName" };
Several possibilities (depending on your objects implementation):
Here:
SearchParameters searchParameters = new SearchParameters();
searchParameters.SearchFields.Add("StoreNumber");
searchParameters.SearchFields.Add("StoreName");
Is SearchFields a List? Was it initialized during SearchParameters construction? If not, in that case you can have a NullReferenceException when you add items to something non instantiated.
Or here:
List<SearchHit> searchHits = results.Results.Select(r => ResultMapper.ToSearchHit(r)).ToList();
In your results object, are you sure that Results is instantiated? If not, NullReferenceException
To find which line throws the Exception, look at line 32 in your code (the line number is in your error log)

Nopcommerce Update entity issue

Using NopCommerce 3.8, Visual Studio 2015 proff.
I have created a plugin that is responsible for making restful calls to my Web API that exposes a different DB to that of Nop.
The process is run via a nop Task, it successfully pulls the data back and i can step through and manipulate as i see fit, no issues so far.
Issue comes when i try to update a record on the product table, i perform the update... but nothing happens no change, no error.
I believe this is due to the Context having no idea about my newly instantiated product object, however I'm drawing a blank on what i need to do in relation to my particular example.
Similar questions usually reference a "model" object that is part of the parameter of the method call, "model" has the method ToEntity which seems to be the answer in similar question in stack.
However my example doesn't have the ToEntity class/method possibly because my parameter is actually a list of products. To Clarify here my code.
Method in RestClient.cs
public async Task<List<T>> GetAsync()
{
try
{
var httpClient = new HttpClient();
var json = await httpClient.GetStringAsync(ApiControllerURL);
var taskModels = JsonConvert.DeserializeObject<List<T>>(json);
return taskModels;
}
catch (Exception e)
{
return null;
}
}
Method in my Service Class
public async Task<List<MWProduct>> GetProductsAsync()
{
RestClient<MWProduct> restClient = new RestClient<MWProduct>(ApiConst.Products);
var productsList = await restClient.GetAsync();
InsertSyncProd(productsList.Select(x => x).ToList());
return productsList;
}
private void InsertSyncProd(List<MWProduct> inserted)
{
var model = inserted.Select(x =>
{
switch (x.AD_Action)
{
case "I":
//_productService.InsertProduct(row);
break;
case "U":
UpdateSyncProd(inserted);
.....
Then the method to bind and update
private void UpdateSyncProd(List<MWProduct> inserted)
{
var me = inserted.Select(x =>
{
var productEnt = _productRepos.Table.FirstOrDefault(ent => ent.Sku == x.Sku.ToString());
if(productEnt != null)
{
productEnt.Sku = x.Sku.ToString();
productEnt.ShortDescription = x.ShortDescription;
productEnt.FullDescription = x.FullDescription;
productEnt.Name = x.Name;
productEnt.Height = x.Pd_height != null ? Convert.ToDecimal(x.Pd_height) : 0;
productEnt.Width = x.Pd_width != null ? Convert.ToDecimal(x.Pd_width) : 0;
productEnt.Length = x.Pd_depth != null ? Convert.ToDecimal(x.Pd_depth) : 0;
productEnt.UpdatedOnUtc = DateTime.UtcNow;
}
//TODO: set to entity so context nows and can update
_productService.UpdateProduct(productEnt);
return productEnt;
});
}
So as you can see, I get the data and pass data through to certain method based on a result. From that list in the method I iterate over, and pull the the entity from the table, then update via the product service using that manipulated entity.
So what am I missing here, I'm sure its 1 step, and i think it may be either be because 1) The context still has no idea about the entity in question, or 2) Its Incorrect calls.
Summary
Update is not updating, possibly due to context having no knowledge OR my methodology is wrong. (probably both).
UPDATE:
I added some logger.inertlog all around my service, it runs through fine, all to the point of the call of update. But again I check the product and nothing has changed in the admin section.
plugin
I have provided the full source as i think maybe this has something to do with the rest of the code setup possibly?
UPDATE:
Added the following for testin on my execute method.
var myprod = _productRepos.GetById(4852);
myprod.ShortDescription = "db test";
productRepos.Update(myprod);
This successfully updates the product description. I moved my methods from my service into the task class but still no luck. The more i look at it the more im thinking that my async is killing off the db context somehow.
Turned of async and bound the getbyid to a new product, also removed the lambda for the switch and changed it to a foreach loop. Seems to finally update the results.
Cannot confirm if async is the culprit, currently the web api seems to be returning the same result even though the data has changed (some wierd caching by deafult in .net core? ) so im creating a new question for that.
UPDATE: It appears that the issue stems from poor debugging of async. Each instance I am trying to iterate over an await call, simply put im trying to iterate over a collection that technically may or may not be completed yet. And probably due to poor debugging, I was not aware.
So answer await your collection Then iterate after.

Return (RecordNotFound) Exception or null if record is not found in the database?

I'm not really sure what is the prefered way when dealing with record not found in the database. Is it better to write Find method which returns null or Get method which returns RecordNotFoundException?
[AuthenticateFilter(UsernameAndSecretKey)]
[Route("api/v1/activities/emails/{id}")]
[HttpGet]
public IHttpActionResult GetEmailActivity(int id)
{
try
{
// business logic service, could use only db service but this way we can do unit tests (just fill bl service method with fake objects)
var service = new EmailActivityBlService();
// 1. use Find method which returns null in case record with provided id does not exist in db
var model = service.FindActivity(id);
if( model != null )
return Ok(model);
return NotFound();
// 2. or is this approach better
// throws RecordNotFoundException in case row by id is not found in database
return Ok(service.GetActivity(id));
}
catch(RecordNotFoundException e) { return NotFound(); }
catch(Exception e) { return InternalServerError(e); }
}
EmailActivityBlService has next code in case anyone interested (showing only the important part):
private EmailActivityDbService _dbService;
public EmailActivityModel GetActivity(int id)
{
var model = this._dbService.GetActivity(id);
if( model == null )
throw new RecordNotFoundException(); // I suppose System.Data.ObjectNotFound is also suitable
return model;
}
public EmailActivityModel FindActivity(int id)
{
// typical entity framework query
// using(var context = new ..) { return contect.EmailActivity.Where()..SingleOrDefault().ConvertToModel();
return this._dbService.GetActivity(id);
}
UPDATE
Talked with my colleagues, we decided to go with this solution. As why GetActivity returns null instead of throwing Exception, I prefer answer from rboe:
So return null if it is can happen in your domain, that records do not exist (in my experience this is most often the case). If you expect a record to exist and it is not there, then it is valid to throw an exception.
[AuthenticateFilter(UsernameAndSecretKey)]
[Route("api/v1/activities/emails/{id}")]
[HttpGet]
public IHttpActionResult GetEmailActivity(int id)
{
var service = new EmailActivityBlService();
var model = service.GetActivity(id); // returns null in case activity is not found
if( model != null )
return Ok(model);
return NotFound();
}
We avoided any try-catch in the methods and put global filter when Exception occurs:
File: App_Start\WebApiConfig.cs
public class WebApiExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
actionExecutedContext.Response = actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, actionExecutedContext.Exception.Message, actionExecutedContext.Exception);
}
}
Both ways are valid ones.
It is a different emphasis whether you use exceptions or the return value null to indicate non existing records.
Exceptions exist to signal an error state (something happened that is abnormal). The code in the catch-handler is focused on how to deal with an error and not to contain business logic.
If you return null then it will be a normal and 'non exceptional' state in your model.
So return null if it is can happen in your domain, that records do not exist (in my experience this is most often the case). If you expect a record to exist and it is not there, then it is valid to throw an exception.
I disagree with the other answer. In the case of a GetyById method, I wouldn't say to return null instead of throwing because you could argue it was "expected" that there might not be a record with the requested id. This "exceptions for exceptional situations," while often stated, I don't really think is the best way to think about the method's contracts. APIs should make semantic sense, ideally.
Instead, I suggest to throw exceptions whenever the method cannot do what it was told to do. So GetById methods should thrown an exception in the event there is no such record with the requested id in the system. The Find method should probably return an enumerable, which of course could be empty in the event no records match the criteria given.
An API which has a FindById method strikes me as odd; if you are giving the API an ID, that implies the caller could somehow have learned the ID in a previous API call, and so the API shouldn't need to "find" an already known to exist record. It should provide a way to get the record directly by its id. Instead Find should be for locating records when you aren't sure they exist, and using some other criteria.
Given the web service call, I would go with the service calling the GetById method, as the web service caller also learned the id somehow. If the id turns out not to exist, the library can throw the RecordNotFoundException, which causes the service call to return 404.

NullReferenceException when nothing is null?

I am testing (integration) my repository layer and I keep getting a null reference error from EF. My concern is, There is nothing in the object null when I run the debugger. In fact there is nothing in the entire method that is null. All of the properties are set up properly in Automapper and I am using real (not mocked) values for everything. They are all there and use the real version of the code.
Test:
[Test]
public void Clone_ShouldCloneUser()
{
using (var scope = new TransactionScope())
{
//arrange
var request = Builder<CloneUserRequest>.CreateNew()
.With(x => x.KeyToClone = 29)
.With(x => x.User = Builder<User>.CreateNew().With(y => y.Key = 0)
.Build())
.Build();
//act
_sut.Clone(request);
//assert
Assert.DoesNotThrow(() => _sut.Clone(request));
}
}
Method:
public void Clone(CloneUserRequest request)
{
var usersGroupsToBeCloned = _context.WebUserGroups.Where(x => x.UserKey == request.KeyToClone).ToList();
var webUser = _mappingService.Map(request.User, new WebUser());
webUser.WebUserGroups = usersGroupsToBeCloned;
//On this line I receive a NullReferenceException, but nothing is null
_context.WebUsers.Add(webUser);
_context.SaveChanges();
}
Stack Trace:
System.NullReferenceException : Object reference not set to an instance of an object.
at System.Data.Entity.Core.Objects.DataClasses.RelatedEnd.MarkForeignKeyPropertiesModified()
at System.Data.Entity.Core.Objects.DataClasses.EntityReference.AddToNavigationPropertyIfCompatible(RelatedEnd otherRelatedEnd)
at System.Data.Entity.Core.Objects.DataClasses.RelatedEnd.IncludeEntity(IEntityWrapper wrappedEntity, Boolean addRelationshipAsUnchanged, Boolean doAttach)
at System.Data.Entity.Core.Objects.DataClasses.EntityCollection`1.Include(Boolean addRelationshipAsUnchanged, Boolean doAttach)
at System.Data.Entity.Core.Objects.DataClasses.RelationshipManager.AddRelatedEntitiesToObjectStateManager(Boolean doAttach)
at System.Data.Entity.Core.Objects.ObjectContext.AddObject(String entitySetName, Object entity)
at System.Data.Entity.Internal.Linq.InternalSet`1.<>c__DisplayClassd.<Add>b__c()
at System.Data.Entity.Internal.Linq.InternalSet`1.ActOnSet(Action action, EntityState newState, Object entity, String methodName)
at System.Data.Entity.Internal.Linq.InternalSet`1.Add(Object entity)
at System.Data.Entity.DbSet`1.Add(TEntity entity)
at Repositories.UserRepository.Clone(CloneUserRequest request) in UserRepository.cs: line 39
at Repositories.UserRepositoryIntegrationTests.Clone_ShouldCloneUser() in UserRepositoryIntegrationTests.cs: line 50
Is there something obvious that I am missing or a bug in EF perhaps?
I figured out what was wrong. Our DB structure is terrible and there is actually a circular reference between WebUserGroup (it has a child WebUser) and WebUser(It has collection of WebUserGroups). So when I got all of the UserGroups to clone for the user, It had a reference to the user that I was initially cloning. Somehow EF decided to send me a nullReferenceException. When I changed the WebUser Property on the Groups to the new one it works great. Now its time to talk to my DBA's about updating this...

Categories