ServiceStack Redis how to implement paging - c#

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.

Related

XUnit test for the response body content

I am really new to XUnit and I will appreciate some help.
Controller
I have a really simple API method that adds a bug comment
[HttpPost("{bugId}/comment")]
public async Task<IActionResult> AddComment(Guid bugId, [FromBody] AddCommentForm form)
{
return await _bugService.AddComment(bugId, form) switch
{
Option<BugViewModel>.Some(var bug) => Ok(bug),
Option<BugViewModel>.None => NotFound(),
_ => throw new InvalidOperationException()
};
}
Service
The BugService method AddComment looks like this
public async Task<Option<BugViewModel>> AddComment(Guid bugId, AddCommentForm form)
{
if (await DbBugs.SingleOrDefaultAsync(x => x.Id == bugId) is { } bug)
{
var bugComment = new BugComment
{
Text = form.Text,
Commenter = form.Commenter,
CommentedAt = DateTime.Now,
Bug = bug
};
await _dbContext.BugComments.AddAsync(bugComment);
await _dbContext.SaveChangesAsync();
return new Option<BugViewModel>.Some(BugViewModel.FromData(bug));
}
return new Option<BugViewModel>.None();
}
ViewModels
I am adding the implementation of BugViewModel and BugCommentViewModel with their static FromData methods
public class BugViewModel
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public List<BugCommentViewModel> Comments { get; set; }
public static BugViewModel FromData(Bug bug)
{
return new BugViewModel
{
Id = bug.Id,
Title = bug.Title,
Description = bug.Description,
Comments = bug.Comments.Select(BugCommentViewModel.FromData).ToList()
};
}
}
public class BugCommentViewModel
{
public Guid Id { get; set; }
public string Text { get; set; }
public string Commenter { get; set; }
public DateTime CommentedAt { get; set; }
public static BugCommentViewModel FromData(BugComment comment)
{
return new BugCommentViewModel
{
Id = comment.Id,
Text = comment.Text,
Commenter = comment.Commenter,
CommentedAt = comment.CommentedAt.ToLocalTime()
};
}
}
Testing
The Unit Tests look like this
public class BugContollerTests
{
private class Fixture
{
public IBugService BugService { get; } = Substitute.For<IBugService>();
public BugController GetSut() => new BugController(BugService);
}
private readonly Fixture _fixture = new Fixture();
[Fact]
public async Task Create_ResponseBodyIsCreatedBug()
{
var bug = BugViewModel.FromData(FakeBug.I.Generate());
_fixture.BugService.Create(Arg.Any<CreateBugForm>()).Returns(bug);
var sut = _fixture.GetSut();
var response = Assert.IsAssignableFrom<CreatedAtActionResult>(await sut.Create(new CreateBugForm()));
Assert.Same(bug, response.Value);
}
[Fact]
public async Task AddComment_CommentIsAddedtoViewModel()
{
_fixture.BugService.AddComment(Arg.Any<Guid>(), Arg.Any<AddCommentForm>()).Returns(new Option<BugViewModel>.Some(new BugViewModel()));
var sut = _fixture.GetSut();
var response = Assert.IsAssignableFrom<ObjectResult>(await sut.AddComment(Guid.Empty,new AddCommentForm()));
Assert.Single(((BugViewModel) response.Value).Comments);
}
}
So, the Test method I had as an example was Create_ResponseBodyIsCreatedBug but the question I have is regarding the second test method that I have created AddComment_CommentIsAddedtoViewModel().
What I am trying to do is:
you see that the BugService.AddComment -> it adds the comment with the Bug Guid we have provided and basically we return BugViewModel with that Comment added to the Comments collection.
test: that WHEN I add a BugComment it is returned in the ViewModel response and we have added exactly one Comment to the bug.
this line
var response = Assert.IsAssignableFrom<ObjectResult>(await sut.AddComment(Guid.Empty,new AddCommentForm()));
the response.Value is a BugViewModel with empty properties and the Comment property is empty without my inserted comment.
And on this line I get an exception as
Assert.Single(((BugViewModel) response.Value).Comments);
as the Comments property is null.
Look, at this line
_fixture.BugService.AddComment(Arg.Any<Guid>(), Arg.Any<AddCommentForm>())
.Returns(
new Option<BugViewModel>.Some(new BugViewModel()));
You setup your fake BugService to return an empty BugViewModel, which indeed would have Comments equal to null because you initialize it nowhere except FromData.
Use same approach you did in a first test
_fixture.BugService.AddComment(Arg.Any<Guid>(), Arg.Any<AddCommentForm>())
.Returns(
new Option<BugViewModel>.Some(
BugViewModel.FromData(FakeBug.I.Generate())));
Or introduce a constructor
public BugViewModel(Guid id, string title, string description, List<BugCommentViewModel> comments)
{
Id = id;
Title = title;
Description = description;
Comments = comments;
}
// Comments are not NULL now
public BugViewModel(): this(Guid.Empty, string.Empty, string.Empty, new List<BugCommentViewModel>())
{
}
In addition, I don't see any purpose of unit testing a mocked dependency of BugController .
Assert.Single(((BugViewModel) response.Value).Comments);
Better move this logic to a separate BugServiceTest which would validate the number of comments.
One more suggestion in regards to unit testing is instead of relying on ObjectResult, write better three tests:
[Fact]
public async Task AddComment_CommentIsAddedtoViewModel_Success()
{
_fixture.BugService.AddComment(Arg.Any<Guid>(), Arg.Any<AddCommentForm>())
.Returns(
new Option<BugViewModel>.Some(
BugViewModel.FromData(FakeBug.I.Generate())));
var sut = _fixture.GetSut();
Assert.IsAssignableFrom<OkResult>(
await sut.AddComment(Guid.Empty,new AddCommentForm()));
}
[Fact]
public async Task AddComment_BugNotFound()
{
_fixture.BugService.AddComment(Arg.Any<Guid>(), Arg.Any<AddCommentForm>())
.Returns(Option<BugViewModel>.None);
var sut = _fixture.GetSut();
Assert.IsAssignableFrom<NotFoundResult>(
await sut.AddComment(Guid.Empty,new AddCommentForm()));
}
public async Task AddComment_ThrowsForNonsenseData()
{
// Idk Option is value or reference type, so I use default(T)
_fixture.BugService.AddComment(Arg.Any<Guid>(), Arg.Any<AddCommentForm>())
.Returns(default(Option<BugViewModel>));
var sut = _fixture.GetSut();
await Assert.ThrowsAsync<InvalidOperationException>(
() => sut.AddComment(Guid.Empty, new AddCommentForm()));
}
Tell me if that helps
The problem is that you are mocking BugService.
You set mock to AddComment method and setup to return HARD CODED value.
That's why this your code is not called any more.
if (await DbBugs.SingleOrDefaultAsync(x => x.Id == bugId) is { } bug)
{
var bugComment = new BugComment
{
Text = form.Text,
Commenter = form.Commenter,
CommentedAt = DateTime.Now,
Bug = bug
};
await _dbContext.BugComments.AddAsync(bugComment);
await _dbContext.SaveChangesAsync();
return new Option<BugViewModel>.Some(BugViewModel.FromData(bug));
}
Your tests is written to check ONLY Controller.
if you want to check BugService too, you should NOT setup mocks for BugService and setup mocks to this:
DbBugs.SingleOrDefaultAsync(x => x.Id == bugId)
//and this
_dbContext.BugComments.AddAsync(bugComment);
//and this
_dbContext.SaveChangesAsync();

ReactiveUI ObservableAsProperty not notifyin UI

I am learning to use ReactiveUI in WPF.
I have run into problem, as this ObesrvableProperty NavigationItems doesnt update UI, but WhenAnyValue fires if I set breakpoint and check the .Select
My DI:
Locator.CurrentMutable.RegisterConstant(new WindowsAuthenticationService(), typeof(IWindowsAuthenticationService));
Locator.CurrentMutable.RegisterConstant<INavigatorService>(new NavigatorService(Locator.Current.GetService<IWindowsAuthenticationService>()));
Locator.CurrentMutable.RegisterConstant(new MainWindowViewModel(Locator.Current.GetService<IWindowsAuthenticationService>(),
Locator.Current.GetService<INavigatorService>()));
My binding:
this.OneWayBind(ViewModel, vm => vm.NavigatorService.NavigationItems, v => v.SideNavigator.Items, s => s.ToList());
My MainViewModel
public MainWindowViewModel(IWindowsAuthenticationService windowsAuthenticationService, INavigatorService navigatorService)
{
WindowsAuthenticationService = windowsAuthenticationService;
NavigatorService = navigatorService;
try
{
WindowsAuthenticationService.AuthenticateUser();
}
catch (UserNotRegisteredException ex)
{
MessageBox.Show(ex.Message);
Environment.Exit(0);
}
}
Navigator service
public class NavigatorService : ReactiveObject, INavigatorService
{
public NavigatorService(IWindowsAuthenticationService windowsAuthenticationService)
{
WindowsAuthenticationService = windowsAuthenticationService;
this.WhenAnyValue(x => x.WindowsAuthenticationService.AccessibleComponents)
.SelectMany(s => s ?? new List<TPermissionComponents>())
.Select(s => new FirstLevelNavigationItemWithValue() { Icon = s.Icon, Label = s.Name, Value = s.Component })
.ToList()
.ToPropertyEx(this, x => x.NavigationItems, new List<FirstLevelNavigationItemWithValue>());
}
public IWindowsAuthenticationService WindowsAuthenticationService { get; }
[ObservableAsProperty]
public IEnumerable<BaseNavigationItem> NavigationItems { get; }
}
And finnaly auth service:
public class WindowsAuthenticationService : ReactiveObject, IWindowsAuthenticationService
{
public TUsers CurrentUser { get; set; }
[Reactive]
public IEnumerable<TPermissionComponents> AccessibleComponents { get; set; }
public void AuthenticateUser()
{
var user = GetCurrentUser();
if (user is null)
throw new UserNotRegisteredException(Environment.UserName);
CurrentUser = user;
AccessibleComponents = GetAccessibleComponents(user);
return;
}
public IEnumerable<TPermissionComponents> GetAccessibleComponents(TUsers user)
{
using AuthenticationContext ctx = new AuthenticationContext();
var components = from assigment in ctx.TPermissionAssignment
join permission in ctx.TPermissions
on assigment.RoleId equals permission.RoleId
join component in ctx.TPermissionComponents
on permission.ComponentId equals component.Id
where assigment.UserId == user.Id
select component;
return components.ToList();
}
public TUsers GetCurrentUser()
{
if (CurrentUser is null)
{
using AuthenticationContext ctx = new AuthenticationContext();
return ctx.TUsers.FirstOrDefault(f => f.Username.ToLower() == Environment.UserName.ToLower());
}
else
{
return CurrentUser;
}
}
I hope someone can help me, I am clueless for past 4 hours.
I will be glad for any ideas.
Thanks.
If someone comes to the same issue. This solved it to me:
public NavigatorService(IWindowsAuthenticationService windowsAuthenticationService)
{
WindowsAuthenticationService = windowsAuthenticationService;
this.WhenAnyValue(x => x.WindowsAuthenticationService.AccessibleComponents)
.Select(s => new ObservableCollection<FirstLevelNavigationItemWithValue>((s ?? new List<TPermissionComponents>()).Select(s => new FirstLevelNavigationItemWithValue() { Icon = Enum.Parse(typeof(PackIconKind), s.Icon, true), Label = s.Name, Value = s.Component })))
.ToPropertyEx(this, x => x.NavigationItems, new ObservableCollection<FirstLevelNavigationItemWithValue>());
}
public IWindowsAuthenticationService WindowsAuthenticationService { get; }
[ObservableAsProperty]
public IEnumerable<BaseNavigationItem> NavigationItems { get; }
Be noted, that if ObservableAsProperty is setup like this:
[ObservableAsProperty]
public ObservableCollection<BaseNavigationItem> NavigationItems { get; }
Then the ToPropertyEx will freak out and wont compile.
My initial thought was the .WhenAnyValue().ToPropertyEx() would spit out every time new collection and that would trigger the binding.
Now I understand it like the .WhenAnyValue().ToPropertyEx() creates sort of pipeline throught which each object (entity) goes only once.
I my assumption is correct, then I would love to know, if it is more profitable performance wise, than spitting new collection each time.
Anyway, I hope this will help someone.

MongoDB & Web API - can't understand how to use filters

I am trying to do basic CRUD on a MongoDB using C# Web API. All the examples online are for deprecated methods and I simply can't get the new methods to work.
the places where i need help in the code bellow:
Delete all items in collection - syntax to remove all.
get all items in collection - filter for get all
get by id = syntax to select by id
i've tried many examples online. most are deprecated for previous versions of MongoDB and the official Mongo docs are not helping, i suspect as my code is involving WebAPI classes and the examples are not for that.
thanks for your help!
This is the class:
public class template
{
[BsonId]
public string templateUniqueId { get; set; }
public string outsideClientId { get; set; }
public string ClientId { get; set; }
public string templateFieldsData { get; set; }
public bool isActive { get; set; }
}
And my repository implementation (part of if):
public class templateRepository : ItemplateRepository
{
public MongoClient client;
public IMongoDatabase database;
public IMongoCollection<template> templatesCollection;
public templateRepository()
{
string connectionString = ConfigurationManager.AppSettings["mongoconnection"];
if (string.IsNullOrWhiteSpace(connectionString))
{
connectionString = "mongodb://localhost:27017";
}
client = new MongoClient(connectionString);
database = client.GetDatabase(ConfigurationManager.AppSettings["mongo_personal"]);
templatesCollection = database.GetCollection<template>("templates");
//var persons = await collection.Find(new BsonDocument()).ToListAsync();
// Reset database and add some default entries
FilterDefinition<template> f;
var result = templatesCollection.DeleteMany(f); // this line should remove all items
for (int index = 1; index < 5; index++)
{
template template1 = new template
{
templateUniqueId = string.Format("t{0}", index.ToString()),
outsideClientId= string.Format("t{0}", index.ToString()),
ClientId = string.Format("t{0}", index.ToString()),
isActive = true,
templateFieldsData = "sharon"
};
AddTemplate(template1);
}
}
public void AddTemplate(template templateIn)
{
database.GetCollection<template>("templates").InsertOne(templateIn);
}
public IEnumerable<template> GetAllTemplates()
{
var templates = database.GetCollection<template>("templates").Find({ }); // get all templates
return templates;
}
public template GetTemplate(string id)
{
IMongoQuery query = Query.EQ("_id", id);
return templatesCollection.Find(query).FirstOrDefault();
}
public bool UpdateTemplate(string id, template item)
{
IMongoQuery query = Query.EQ("_id", id);
item.LastModified = DateTime.UtcNow;
IMongoUpdate update = Update
.Set("outsideClientId", item.outsideClientId)
.Set("ClientId", item.ClientId)
.Set("isActive", item.isActive)
.Set("templateFieldsData", item.templateFieldsData);
SafeModeResult result = templatesCollection.Update(query, update);
return result.UpdatedExisting;
}
}
You are using the legacy driver methods, here are some examples for MongoDB C# Driver 2.2:
Delete all items in collection, syntax to remove all.
coll.DeleteMany(FilterDefinition<template>.Empty);
Get all items in collection, filter for get all
var results = coll.Find(FilterDefinition<template>.Empty).ToList();
Get by id, syntax to select by id
var filter = Builders<template>.Filter.Eq(t=>t.templateUniqueId, id);
var result = coll.Find(filter).FirstOrDefault();
Also, for your update method, you can use the following syntax:
var filter = Builders<template>.Filter.Eq(t=>t.templateUniqueId, id);
var update = Builders<template>.Update
.Set(t=>t.outsideClientId, item.outsideClientId)
.Set(t=>t.ClientId, item.ClientId)
.Set(t=>t.isActive, item.isActive)
.Set(t=>t.templateFieldsData, item.templateFieldsData);
var updateResult = coll.UpdateOne(filter, update);
return result.ModifiedCount > 0;
More information about Definitions and Builders:
http://mongodb.github.io/mongo-csharp-driver/2.0/reference/driver/definitions/

Remove duplicate rows mvc

I have this method
Meeting is a class
Attendees is an ICollection in Meeting
Class
public partial class Meeting
{
public Meeting()
{
this.Attendees = new List<Attendees>();
}
public virtual ICollection<Attendees> Attendees{ get; set; }
[...]
Method Controller
private void RemoveRowsDuplicated(Meeting model)
{
if (model.Attendees != null)
{
foreach (var item in model.Attendees.GroupBy(x => x.UserName).Select(y => y.Last()))
{
context.Attendees.Remove(item);
}
}
}
The objective is remove duplicate Attendees with the same username in the table.
But the current method it deletes all records and keeps the duplicate
Where am I going wrong?
Correct version of your method will look like this:
private static void RemoveRowsDuplicated(Meeting model)
{
if (model.Attendees != null)
{
var duplicates = new List<Attendees>();
foreach (var item in model.Attendees.GroupBy(x => x.UserName).Where(x=>x.Count()>1))
{
duplicates.AddRange(item.Skip(1));
}
duplicates.ForEach(x=>context.Attendees.Remove(x));
}
}
You can try writing raw SQL and invoking via EF and return Attendees objects in a list.
var query = "Select * from Attendees group by username";
var attendeesList = dbContext.Database.SqlQuery<Attendees>(query).ToList<Attendees>();
As I can see you grouped elements by name and remove last item. So you remove unique elements.
Like this
private void RemoveRowsDuplicated(Meeting model)
{
if (model.Attendees != null)
{
var temporaryAtendees = new List<Attendees>();
foreach(var item in model.Attendees)
{
if (temporaryAtendees.Contains(item))
{
context.Attendees.Remove(item);
}
else
{
temporaryAtendees.Add(item);
}
}
}
}

Static Query Building with NEST

I'm playing around with Elasticsearch and NEST.
I do have some trouble understanding the various classes and interfaces which can be used to create and build static queries.
Here's a simplified example of what I want to achieve:
using Nest;
using System;
using System.Text;
namespace NestTest
{
public class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
public class ProductFilter
{
public string[] IncludeNames { get; set; }
public string[] ExcludeNames { get; set; }
public int MaxPrice { get; set; }
}
class Program
{
static void Main(string[] args)
{
var filter = new ProductFilter();
filter.MaxPrice = 100;
filter.IncludeNames = new[] { "Notebook", "Workstation" };
filter.ExcludeNames = new[] { "Router", "Modem" };
var query = CreateQueryFromFilter(filter);
var client = new ElasticClient();
// Test Serialization
var serialized = Encoding.UTF8.GetString(client.Serializer.Serialize(query));
Console.WriteLine(serialized);
// TODO: How to convert the IQuery to QueryContainer?
//client.Search<Product>(s => s.Query(q => query));
}
private static IQuery CreateQueryFromFilter(ProductFilter filter)
{
var baseBoolean = new BoolQueryDescriptor<Product>();
if (filter.IncludeNames != null && filter.IncludeNames.Length > 0)
{
foreach (var include in filter.IncludeNames)
{
// TODO: This overwrites the previous must
baseBoolean.Must(q => q.Term(t => t.Name, include));
}
}
if (filter.ExcludeNames != null && filter.ExcludeNames.Length > 0)
{
foreach (var exclude in filter.ExcludeNames)
{
// TODO: This overwrites the previous must
baseBoolean.MustNot(q => q.Term(t => t.Name, exclude));
}
}
if (filter.MaxPrice > 0)
{
// TODO: This overwrites the previous must
baseBoolean.Must(q => q.Range(r => r.LowerOrEquals(filter.MaxPrice).OnField(f => f.Price)));
}
return baseBoolean;
}
}
}
As you can see, I'd like to create some kind of query object (most likely BoolQuery) and then fill this object later on. I've added some TODOS in code where I have the actual problems. But in general, there are just too many possibilities (IQuery, QueryContainer, XXXQueryDescriptor, SearchDescriptor, SearchRequest) and I cannot figure out how to successfully "build" a query part by part.
Anybody who could enlighten me?
Combinding boolean queries is described in the documentation here:
http://nest.azurewebsites.net/nest/writing-queries.html
That page is slightly outdated and will be updated soon although most of it still applies I updated your CreateQueryFromFilter method to showcase the several ways you can formulate queries:
private static IQueryContainer CreateQueryFromFilter(ProductFilter filter)
{
QueryContainer queryContainer = null;
if (filter.IncludeNames != null && filter.IncludeNames.Length > 0)
{
foreach (var include in filter.IncludeNames)
{
//using object initializer syntax
queryContainer &= new TermQuery()
{
Field = Property.Path<Product>(p => p.Name),
Value = include
};
}
}
if (filter.ExcludeNames != null && filter.ExcludeNames.Length > 0)
{
foreach (var exclude in filter.ExcludeNames)
{
//using static Query<T> to dispatch fluent syntax
//note the ! support here to introduce a must_not clause
queryContainer &= !Query<Product>.Term(p => p.Name, exclude);
}
}
if (filter.MaxPrice > 0)
{
//fluent syntax through manually newing a descriptor
queryContainer &= new QueryDescriptor<Product>()
.Range(r => r.LowerOrEquals(filter.MaxPrice).OnField(f => f.Price)
);
}
return queryContainer;
}
Here's how you can pass that to a search operation:
static void Main(string[] args)
{
//using the object initializer syntax
client.Search<Product>(new SearchRequest()
{
Query = query
});
//using fluent syntax
client.Search<Product>(s => s.Query(query));
}

Categories