I am trying to flush my cache after I update an item, and I have tried a few different options and none are working as expected
public class PostApiController : Controller
{
private readonly IPostService _postService;
private readonly IPostTagService _postTagService;
private IMemoryCache _cache;
private MemoryCacheEntryOptions cacheEntryOptions;
public PostApiController(IPostService postService, IPostTagService postTagService, IMemoryCache cache)
{
_postService = postService;
_postTagService = postTagService;
_cache = cache;
cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromDays(1));
}
[HttpGet("{url}", Name = "GetPost")]
public IActionResult GetById(string url, bool includeExcerpt)
{
Post cacheEntry;
if (!_cache.TryGetValue($"GetById{url}{includeExcerpt}", out cacheEntry))
{
cacheEntry = _postService.GetByUrl(url, includeExcerpt);
_cache.Set($"GetById{url}{includeExcerpt}", cacheEntry, cacheEntryOptions);
}
if (cacheEntry == null)
{
return NotFound();
}
return new ObjectResult(cacheEntry);
}
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody] Post item)
{
if (item == null)
{
return BadRequest();
}
var todo = _postService.GetById(id);
if (todo == null)
{
return NotFound();
}
_postService.Update(item);
_postTagService.Sync(item.Tags.Select(a => new PostTag { PostId = item.Id, TagId = a.Id }).ToList());
//Want to flush entire cache here
return new NoContentResult();
}
I have tried to Dispose() MemoryCache here but on next Api call, it is still disposed. Since the keys are somewhat dynamic, I can't just get the keys. How can I go about doing this?
You can store the dictionary instead. This way you can use the dynamic keys for entries and one single static key for the dictionary container, which can be stored in the cache instead of storing each entry separately.
Something like that:
private const string CachedEntriesKey = "SOME-STATIC-KEY";
[HttpGet("{url}", Name = "GetPost")]
public IActionResult GetById(string url, bool includeExcerpt)
{
Dictionary<string, Post> cacheEntries;
if (!_cache.TryGetValue(CachedEntriesKey, out cacheEntries))
{
cacheEntries = new Dictionary<string, Post>();
_cache.Set(CachedEntriesKey, cacheEntries);
}
var entryKey = $"GetById{url}{includeExcerpt}";
if (!cacheEntries.ContainsKey(entryKey))
{
return NotFound(); // by the way, why you do that instead of adding to the cache?
}
return new ObjectResult(cacheEntries[entryKey]);
}
Related
I am using .NET Core and SQLKata to access SQL Server database.
I have a method to get all records from the database using SQLKata.Execution.PaginationResult.
This is in Repository:
public class MyTableRepository : IMyTableRepository
{
private QueryFactory _db;
public MyTableRepository(IConfiguration configuration)
{
var conn = new
SqlConnection(configuration.GetConnectionString("MyTable"));
_db = new QueryFactory(conn, new SqlKata.Compilers.SqlServerCompiler());
}
public PaginationResult<MyModel> GetAll(int page = 0, int perPage = 25)
{
dbResult = _db.Query("MyTable").Paginate<MyModel>(page, perPage);
return dbResult;
}
The above is called from my Controller like so:
private readonly IMyTableRepository _MyTableRepository;
public MyTableController(IMyTableRepository MyTableRepository)
{
_MyTableRepository = MyTableRepository;
}
[HttpGet]
[Route("GetMyTable")]
public List<MyModel> GetMyTable()
{
PaginationResult<MyModel> dbResult = MyTableRepository.GetAll(1,
25);
List<MyModel> AccumResult = dbResult.List.ToList();
while(dbResult.HasNext)
{
dbResult = dbResult.Next();
AccumResult.AddRange(dbResult.List.ToList());
}
return AccumResult;
}
How do I get the Next set of result from dbResult ?
I tried below, after I execute GetMyTable, I execute GetNextMyTable, but in PaginationResult GetNext(), dbResult is always null.
In controller:
[HttpGet]
[Route("GetNextMyTable")]
public List<MyTable> GetNextMyTable()
{
var result = _MyTableRepository.GetNext().List;
return result.ToList();
}
In Repository:
public PaginationResult<MyTable> GetNext()
{
while(dbResult.HasNext) //--->> dbResult is null
{
dbResult = dbResult.Next();
return dbResult;
}
return null;
}
If I do the Next method inside the Controller, I am also getting an error
private readonly IMyTableRepository _MyTableRepository;
private PaginationResult<SP_SMA_Reporting_Accts> dbResult;
[HttpGet]
[Route("GetMyTable")]
public List<MyModel> GetMyTable()
{
var dbResult = _MyTableRepository.GetAll(1, 25).List;
return dbResult.ToList();
}
[HttpGet]
[Route("GetNextMyTable")]
public List<MyTable> GetNextMyTable()
{
var result = dbResult.Next().List;//->Error since dbResult is null
return result.ToList();
}
In short refactor your repository method GetAll to call the db.Query
use:
_db.Query("MyTable").Paginate<MyModel>(page, perPage);
instead of
_db.Paginate<MyModel>(new Query("MyTable"), page, perPage);
Explanation:
when calling the Next() method the PaginationResult delegate the pagination process to the holding query, in your case since your are passing new Query() thus no connection info, you are getting null.
I'm building GraphQL API in Asp.Net Core 2.2 using GraphQL.Net 2.4.0
I've created Controller to handle GraphQL Queries:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class GraphQLController : Controller
{
private readonly ISchema _schema;
private readonly IDocumentExecuter _executer;
private readonly ILogger _logger;
IEnumerable<IValidationRule> _validationRules;
private readonly DataLoaderDocumentListener _dataLoaderDocumentListener;
//private readonly IDocumentWriter _writer;
//private readonly IHttpContextAccessor _accessor;
public GraphQLController(
ILogger<GraphQLController> logger,
IEnumerable<IValidationRule> validationRules,
IDocumentExecuter executer,
DataLoaderDocumentListener dataLoaderDocumentListener,
//IDocumentWriter writer,
ISchema schema)
{
_executer = executer;
_schema = schema;
_logger = logger;
_validationRules = validationRules;
_dataLoaderDocumentListener = dataLoaderDocumentListener;
//_writer = writer;
}
[Route("graphql")]
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]GraphQLQuery query)
{
if (!ModelState.IsValid || query == null)
{
return Json("Проблем в формата на изпратената на заявка!");
}
var inputs = query.Variables.ToInputs();
var result = await _executer.ExecuteAsync(o =>
{
o.Schema = _schema;
o.Query = query.Query;
o.OperationName = query.OperationName;
o.Inputs = inputs;
o.ExposeExceptions = true;
o.EnableMetrics = true;
o.ComplexityConfiguration = new GraphQL.Validation.Complexity.ComplexityConfiguration { MaxDepth = 15 };
//o.FieldMiddleware.Use<InstrumentFieldsMiddleware>();
o.UserContext = new GraphQLUserContext
{
// this is the User on your controller
// which is populated from your jwt
User = User
};
o.ValidationRules = DocumentValidator.CoreRules().Concat(_validationRules).ToList();
o.Listeners.Add(_dataLoaderDocumentListener);
}).ConfigureAwait(false);
if (result.Errors?.Count > 0)
{
_logger.LogWarning($"Errors: {JsonConvert.SerializeObject(result.Errors)}");
return BadRequest(result);
}
return Ok(result);
}
}
The problem arises when i want to validate some of the Fields in my InputObjectGraphType , I've implemented IValidationRule interface.
Prior this im adding Metadata to the field i want to validate so i can easily find it. I;m getting the fieldType but cant fetch the Value to Validate it.
This is the Implementation of the IValidationRule i Use:
public class PhoneNumberValidationRule : IValidationRule
{
public INodeVisitor Validate(ValidationContext context)
{
return new EnterLeaveListener(_ =>
{
_.Match<Argument>(argAst =>
{
var inputType = context.TypeInfo.GetInputType().GetNamedType() as InputObjectGraphType;
var argDef = context.TypeInfo.GetArgument();
if (argDef == null) return;
var type = argDef.ResolvedType;
if (type.IsInputType())
{
var fields = ((type as NonNullGraphType)?.ResolvedType as IComplexGraphType)?.Fields;
if (fields != null)
{
foreach (var field in fields)
{
if (field.ResolvedType is NonNullGraphType)
{
if ((field.ResolvedType as NonNullGraphType).ResolvedType is IComplexGraphType)
{
fields = fields.Union(((field.ResolvedType as NonNullGraphType).ResolvedType as IComplexGraphType)?.Fields);
}
}
if (field.ResolvedType is IComplexGraphType)
{
fields = fields.Union((field.ResolvedType as IComplexGraphType)?.Fields);
}
}
//let's look for fields that have a specific metadata
foreach (var fieldType in fields.Where(f => f.HasMetadata(nameof(EmailAddressValidationRule))))
{
//now it's time to get the value
context.Inputs.GetValue(argAst.Name, fieldType.Name);
if (value != null)
{
if (!value.IsValidPhoneNumber())
{
context.ReportError(new ValidationError(context.OriginalQuery
, "Invalid Phone Number"
, "The supplied phone number is not valid."
, argAst
));
}
}
}
}
}
});
});
}
}
But here the context.Inputs property is always null.
In the controller this line var inputs = query.Variables.ToInputs();
also produces null. Is this query Variable field and Document Executer's Input Field anything to do with this ?
You probably figured it out by now, but for anyone finding this question in the future:
context.Inputs is only used with variables.
As far as I could understand argAst.GetValue could be either VariableReference or ObjectValue depending on whether the query was using a variable or not.
In case variables were not used context.Inputs.GetValue will return null.
I have two methods that use different viewmodels but are the same logic. At the moment I have copied and pasted them into their respective controllers. Any way to share these methods somehow?
Song Controller:
public JsonResult IncrementViews(int id)
{
using (ApplicationDbContext db = new ApplicationDbContext())
{
PublishedSongViewModel song = db.PublishedSongs.Single(x => x.Id == id);
song.UniquePlayCounts++;
db.SaveChanges();
return Json(new { UniquePlayCounts = song.UniquePlayCounts }, JsonRequestBehavior.AllowGet);
}
}
Station Controller:
public JsonResult IncrementViews(int id)
{
using (ApplicationDbContext db = new ApplicationDbContext())
{
RadioStationViewModel station = db.RadioStations.Single(x => x.Id == id);
station.UniquePlayCounts++;
db.SaveChanges();
return Json(new { UniquePlayCounts = station.UniquePlayCounts }, JsonRequestBehavior.AllowGet);
}
}
Edit:
class so far:
public static IEnumerable<Type> GetElements(ApplicationDbContext db, Type type)
{
if (type == typeof(SongsController))
return (IEnumerable<Type>)db.PublishedSongs;
else if (type == typeof(RadioStationsController))
return (IEnumerable<Type>)db.RadioStations;
else
throw new Exception("Controller not found, DBHelper");
}
Create a class called BasicController and add the method to it, like this:
public class BasicController {
public JsonResult IncrementViews(int id)
{
using (ApplicationDbContext db = new ApplicationDbContext())
{
var element = DBHelper.GetElements(db, this.GetType()).Single(x => x.Id == id);
element.UniquePlayCounts++;
db.SaveChanges();
return Json(new { UniquePlayCounts = song.UniquePlayCounts }, JsonRequestBehavior.AllowGet);
}
}
}
and modify your classes to inherit from BasicController. You will also have to create the DBHelper class with the GetElements method, which gathers the IEnumerable elements from db based on type.
EDIT: This is how you can create a helper:
public class DBHelper {
public static IEnumerable GetElements(ApplicationDbContext db, System.Type type) {
if (type == typeof(SongController)) {
return db.PublishedSongs;
} else if (type == typeof(StationController)) {
return db.RadioStations;
}
}
}
I send three http requests to a web service every 10th second. The reponses are handed off to three methods in a cache class (one for each http query / request) that checks whether the reponse content has changed since last time.
I convert the raw reponse content to a string and compare it to the old response, which is stored as a private string in the cache class. It works alright, but the approach has a lot of duplicate code, as you can see:
class Cache
{
private HubClient _hubClient;
private string oldIncidentAppointment;
private string oldIncidentGeneral;
private string oldIncidentUntreated;
public Cache(HubClient hubClient)
{
_hubClient = hubClient;
}
public bool IsIncidentAppointmentNew(string currentIncidentAppointment)
{
if (XElement.Equals(oldIncidentAppointment, currentIncidentAppointment))
{
return false;
}
else
{
oldIncidentAppointment = currentIncidentAppointment;
_hubClient.SendToHub();
return true;
}
}
public bool IsIncidentUntreatedNew(string currentIncidentUntreated)
{
if (XElement.Equals(oldIncidentUntreated, currentIncidentUntreated))
{
return false;
}
else
{
oldIncidentUntreated = currentIncidentUntreated;
_hubClient.SendToHub();
return true;
}
}
public bool IsIncidentGeneralNew(string currentIncidentGeneral)
{
if (XElement.Equals(oldIncidentGeneral, currentIncidentGeneral))
{
return false;
}
else
{
oldIncidentGeneral = currentIncidentGeneral;
_hubClient.SendToHub();
return true;
}
}
}
How can this be refactored into a generalized method that compares old and new content for all my current and future http query methods?
You can store them in a dictionary:
class Cache {
private HubClient _hubClient;
private Dictionary<string, string> _pages;
public Cache(HubClient hubClient)
{
_hubClient = hubClient;
_pages = new Dictionary<string, string>();
}
public bool isPageNew( string key, string content ) {
string current;
if (_pages.TryGetValue(key, out current) && XElement.Equals(current, content)) {
return false;
}
_pages[key] = content;
_hubClient.SendToHub(); //Why have side effect here? :P
return true;
}
}
Then:
Cache cache = new Cache( client );
if( cache.isPageNew( "untreated", pageContent ) ) {
}
This is quick and dirty, so if it's not 100% you'll have to fix it up; I don't have your tests to verify it's correctness. I'm also not sure you can just ask a dictionary for a key that doesn't exist without checking for it's existence, so you might have to handle that.
class Cache
{
private HubClient _hubClient;
private IDictionary<string, string> _oldIncidents;
public Cache(HubClient hubClient)
{
_hubClient = hubClient;
_oldIncidents = new Dictionary<string, string>();
}
public bool IsIncidentAppointmentNew(string currentIncidentAppointment)
{
return DoMagicWork(
incidentKey: "appointment",
currentIncident = currentIncidentAppointment
);
}
public bool IsIncidentUntreatedNew(string currentIncidentUntreated)
{
return DoMagicWork(
incidentKey: "untreated",
currentIncident = currentIncidentUntreated
);
}
public bool IsIncidentGeneralNew(string currentIncidentGeneral)
{
return DoMagicWork(
incidentKey: "general",
currentIncident = currentIncidentGeneral
);
}
private bool DoMagicWork(string incidentKey, string currentIncident)
{
var oldIncident = _oldIncidents[incidentKey];
if (XElement.Equals(oldIncident, currentIncident))
{
return false;
}
_oldIncidents[incidentKey] = currentIncident;
_hubClient.SendToHub();
return true;
}
}
I am trying to find out how to do paging in SS.Redis, I use:
var todos = RedisManager.ExecAs<Todo>(r => r.GetLatestFromRecentsList(skip,take));
it returns 0, but i am sure the database is not empty, because r.GetAll() returns a list of things. What is the correct way to do this?
EDIT: Here is the code:
public class ToDoRepository : IToDoRepository
{
public IRedisClientsManager RedisManager { get; set; } //Injected by IOC
public Todo GetById(long id) {
return RedisManager.ExecAs<Todo>(r => r.GetById(id));
}
public IList<Todo> GetAll() {
return RedisManager.ExecAs<Todo>(r => r.GetAll());
}
public IList<Todo> GetAll(int from, int to) {
var todos = RedisManager.ExecAs<Todo>(r => r.GetLatestFromRecentsList(from,to));
return todos;
}
public Todo NewOrUpdate(Todo todo) {
RedisManager.ExecAs<Todo>(r =>
{
if (todo.Id == default(long)) todo.Id = r.GetNextSequence(); //Get next id for new todos
r.Store(todo); //save new or update
});
return todo;
}
public void DeleteById(long id) {
RedisManager.ExecAs<Todo>(r => r.DeleteById(id));
}
public void DeleteAll() {
RedisManager.ExecAs<Todo>(r => r.DeleteAll());
}
}
As I don't see any code, I'm assuming you're not maintaining the Recents list when you're adding the entites. Here's the test case for GetLatestFromRecentsList:
var redisAnswers = Redis.As<Answer>();
redisAnswers.StoreAll(q1Answers);
q1Answers.ForEach(redisAnswers.AddToRecentsList); //Adds to the Recents List
var latest3Answers = redisAnswers.GetLatestFromRecentsList(0, 3);
var i = q1Answers.Count;
var expectedAnswers = new List<Answer>
{
q1Answers[--i], q1Answers[--i], q1Answers[--i],
};
Assert.That(expectedAnswers.EquivalentTo(latest3Answers));
Redis StackOverflow is another example that uses the Recents list feature to show the latest Questions added. It maintains the recent list of questions by calling AddToRecentsList whenever a new Question is created.