I need to perform a search to one of my Realm table based on a keyword. The search function is done in an async function but it is quite slow: 3-5 seconds to search and show the results for 9000 data.
The search is done in three steps:
Search based on the query and return the list of Guid
Return the list of objects based on list of Guid
Use the result value to update the UI
When I try to return List<IssueTable> directly from SearchIssueInProject, I can't access its property later because "this realm instance has been closed" (something like that).
This is my search functions:
public async Task<List<IssueTable>> GetFilteredIssuesInProject(string query)
{
if (!string.IsNullOrEmpty(query))
{
var searchResults = await Task.Run(() => SearchIssueInProject(query));
return searchResults.Select(i => RealmConnection.Find<IssueTable>(i)).ToList();
}
return this.AllIssuesInProject;
}
List<string> SearchIssueInProject(string query)
{
using (var realm = Realm.GetInstance(RealmConfiguration))
{
Func<IssueTable, bool> searchIssue = d =>
string.IsNullOrEmpty(query) ? true :
Contains(d.Id.ToString(), query) ||
Contains(d.Status.DisplayName, query) ||
Contains(d.Status.Value, query) ||
Contains(d.Team.Name, query) ||
Contains(d.Team.Initial, query) ||
Contains(d.StandardIssueCategory.Title, query) ||
Contains(d.StandardIssueType.Title, query) ||
Contains(d.Drawing.Title, query) ||
Contains(d.Location.Title, query) ||
Contains(d.CreatedBy.DisplayName, query) ||
Contains(d.UpdatedBy.DisplayName, query);
var result = realm.All<IssueTable>().Where(searchIssue)
.OrderByDescending(i => i.UpdatedDate)
.Select(i => i.Guid)
.ToList();
return result;
}
}
public List<IssueTable> GetAllIssues()
{
return RealmConnection.All<IssueTable>()
.OrderByDescending(i => i.UpdatedDate)
.ToList();
}
Contains function:
public static bool Contains(string source, string filter)
{
return source.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0;
}
And this is how I use the search function:
this.WhenAnyValue(x => x.ViewModel.IssueSearchQuery)
.Throttle(TimeSpan.FromMilliseconds(1000), RxApp.MainThreadScheduler)
.DistinctUntilChanged()
.Do(_ =>
{
this.IssueAdapter.Issues.Clear();
})
.Select(searchTerm =>
{
if (SearchingProgressDialog == null && Activity != null)
{
ShowLoadingProgress();
}
var result = this.ViewModel.GetFilteredIssuesInProject(searchTerm);
return result
.ToObservable()
.ObserveOn(RxApp.MainThreadScheduler);
})
.Switch()
.Subscribe(searchResult =>
{
this.IssueAdapter.Issues.AddRange(searchResult);
this.IssueAdapter.NotifyDataSetChanged();
if (SearchingProgressDialog != null)
{
SearchingProgressDialog.Dismiss();
SearchingProgressDialog = null;
}
});
How to improve the search function?
Because you're creating a function and not an expression, the query isn't evaluated by Realm, but rather by LINQ to objects which means every object needs to be fetched by Realm, then the accessed properties will be fetched one by one to be used by the comparison. Unfortunately, all the fields you search by need traversing a related object which is not yet supported by Realm .NET.
One approach to resolve this would be to denormalize your data and copy these properties on the IssueTable object. Then you can execute a query like:
var result = realm.All<IssueTable>()
// Note that the .Where overload we select uses Expression<Func<>>
// Also, StatusDisplayName is a copy of Status.DisplayName
.Where(i => i.StatusDisplayName || ...)
.OrderByDescending(i => i.UpdatedDate)
.ToArray()
.Select(i => i.Guid)
.ToList();
Related
I have the following method that I need to test:
public async Task<SomeClass> GetAsync(string partitionKey, string rowKey)
{
var entities = new List<SomeClass>();
await foreach (var e in _tableClient.QueryAsync<SomeClass>(x => x.PartitionKey == partitionKey && x.RowKey == rowKey))
{
entities.Add(e);
}
return entities.FirstOrDefault();
}
I'd like to setup the _tableClient.QueryAsync() (in the moq) to be able to return different result based on the input parameter. This is important to ensure my unit test covers the logic.
My attempt is:
var thingsToMock = new List<(string PartitionKey, string RowKey, string Value)>() {
("maxCount", "maxCount", "0"),
("maxCount", "xyz", "1000"),
("maxCount", "abc", "2000")
};
var tableClientMock = new Mock<TableClient>();
foreach (var thingToMock in thingsToMock)
{
var returnPage = Page<SomeClass>.FromValues(new List<SomeClass>
{
new SomeClass{ PartitionKey = thingToMock.PartitionKey, RowKey = thingToMock.RowKey, Value = thingToMock.Value }
}, null, new Mock<Response>().Object);
var returnPages = AsyncPageable<SomeClass>.FromPages(new[] { returnPage });
Expression<Func<SomeClass, bool>> exp = (x) => x.PartitionKey == thingToMock.PartitionKey && x.RowKey == thingToMock.RowKey ? true : false;
tableClientMock
.Setup(i => i.QueryAsync<SomeClass>(It.Is<Expression<Func<SomeClass, bool>>>(expression => LambdaExpression.Equals(expression, exp)), null, null, default))
.Returns(returnPages);
}
The issue is that the _tableClientMock doesn't seem to return what I expected when I call GetAsync("maxCount", "abc"). I'd expect with this call, it would pass in the same parameters to tableClient.QueryAsync() method, which in my Mock should return instance of SomeClass with value of 2000. But instead, it throw "Object reference not set to an instance of an object." error.
If I change the tableClientMock setup for QueryAsync to be the following, it somewhat works:
.Setup(i => i.QueryAsync<SomeClass>(It.IsAny<Expression<Func<SomeClass, bool>>>(), null, null, default))
But this will not achieve my objective, which is to be able to pass different parameters (partitionKey and rowKey) to get different result.
I'm using the following NuGet package:
"Azure.Data.Tables" Version="12.7.1"
"moq" Version="4.14.1"
I'm trying to create a React app that:
sends data (json message) from backend to frontend using socket.io
if a json message with same is sent, update the existing list
This is how i'm implementing it right now but i'm not sure if this is a good design methodology or if there's a better way to achieve what I want to do.
function App(){
const [database, setDatabase] = useState([])
useEffect(() => {
socket.on('incoming_data', (data) => {
setDatabase((currentList) => {
if (currentList.length > 0){ //only check for update/delete if more than 1 item present
let exists = !!currentList.find((item) => item.ID === data.ID)
if (exists){ //if new item exists in database list
if (data.deleteFlag === true){ // incoming data will have a json field declaring whether to delete or not
//deleting item
var item = currentList.find(itm => itm.ID === data.ID)
let ind = currentList.indexOf(item)
return (currentList.splice(ind,1))
}
else{ // else if delete flag is not true... update fields
var item = currentList.find(itm => itm.ID === data.ID)
let ind = currentList.indexOf(item)
if (item.dataField !== data.dataField){
currentList[ind].dataField = data.dataField
}
return (currentList)
}
}
//if incoming data doesnt exist in list, add to it
else{ return([...currentList, data]) }
}
}
// if there are 0 items in list, add to list
else { return ([...currentList, data]) }
})
}, [socket])
return(/*using map to display list in front end*/)
}
Right now, this code works in the following ways:
Checks if there are 0 items in 'database', if so, it adds items to it.
What it's not doing:
updating items in database
deleting items properly. Sometimes it deletes items, other times it does nothing.
Any help would be great!
Use higher-order functions to simplify code like filter, findIndex, etc.
use findIndex method to check items exist and use the same index to update currentList.
use the filter function to delete items from the list.
function App() {
const [database, setDatabase] = useState([])
useEffect(() => {
socket.on('incoming_data', (data) => {
setDatabase((currentList) => {
if (currentList.length > 0) { //only check for update/delete if more than 1 item present
// Use same index to find item
let itemIndex = currentList.findIndex((item) => item.ID === data.ID)
if (itemIndex !== -1) { //if new item exists in database list
if (data.deleteFlag === true) { // incoming data will have a json field declaring whether to delete or not
// use filter for delete
return currentList.filter((item) => item.ID !== data.ID);
}
else {
let item = currentList[itemIndex]
const newItem = { ...item, dataField: data.dataField }
if (item.dataField !== newItem.dataField) {
currentList[itemIndex] = newItem;
return [...currentList]; // Set new value for updates
}
return (currentList)
}
}
//if incoming data doesn't exist in list, add to it
else { return ([...currentList, data]) }
}
// if there are 0 items in list, add to list
else { return ([...currentList, data]) }
});
});
}, [socket])
return (/*using map to display list in front end*/)
}
I'm using the node Bigquery Package, to run a simple job. Looking at the results (say data) of the job the effective_date attribute look like this:
effective_date: BigQueryDate { value: '2015-10-02' }
which is obviously an object within the returned data object.
Importing the returned json into Firestore gives the following error:
UnhandledPromiseRejectionWarning: Error: Argument "data" is not a
valid Document. Couldn't serialize object of type "BigQueryDate".
Firestore doesn't support JavaScript objects with custom prototypes
(i.e. objects that were created via the 'new' operator).
Is there an elegant way to handle this? Does one need to iterate through the results and convert / remove all Objects?
The firestore Node.js client do not support serialization of custom classes.
You will find more explanation in this issue:
https://github.com/googleapis/nodejs-firestore/issues/143
"We explicitly decided to not support serialization of custom classes for the Web and Node.JS client"
A solution is to convert the nested object to a plain object. For example by using lodash or JSON.stringify.
firestore.collection('collectionName')
.doc('id')
.set(JSON.parse(JSON.stringify(myCustomObject)));
Here is a related post:
Firestore: Add Custom Object to db
Another way is less resource consuming:
firestore
.collection('collectionName')
.doc('id')
.set(Object.assign({}, myCustomObject));
Note: it works only for objects without nested objects.
Also you may use class-transformer and it's classToPlain() along with exposeUnsetFields option to omit undefined values.
npm install class-transformer
or
yarn add class-transformer
import {classToPlain} from 'class-transformer';
firestore
.collection('collectionName')
.doc('id')
.set(instanceToPlain(myCustomObject, {exposeUnsetFields: false}));
If you have a FirebaseFirestore.Timestamp object then don't use JSON.parse(JSON.stringify(obj)) or classToPlain(obj) as those will corrupt it while storing to Firestore.
It's better to use {...obj} method.
firestore
.collection('collectionName')
.doc('id')
.set({...obj});
Note: do not use new operator for any nested objects inside document class, it'll not work. Instead, create an interface or type for nested object properties like this:
interface Profile {
firstName: string;
lastName: string;
}
class User {
id = "";
isPaid = false;
profile: Profile = {
firstName: "",
lastName: "",
};
}
const user = new User();
user.profile.firstName = "gorv";
await firestore.collection("users").add({...user});
And if you really wanna store class object consists of deeply nested more class objects then use this function to first convert it to plain object while preserving FirebaseFirestore.Timestamp methods.
const toPlainFirestoreObject = (o: any): any => {
if (o && typeof o === "object" && !Array.isArray(o) && !isFirestoreTimestamp(o)) {
return {
...Object.keys(o).reduce(
(a: any, c: any) => ((a[c] = toPlainFirestoreObject(o[c])), a),
{}
),
};
}
return o;
};
function isFirestoreTimestamp(o: any): boolean {
if (o &&
Object.getPrototypeOf(o).toMillis &&
Object.getPrototypeOf(o).constructor.name === "Timestamp"
) {
return true;
}
return false;
}
const user = new User();
user.profile = new Profile();
user.profile.address = new Address();
await firestore.collection("users").add(toPlainFirestoreObject(user));
Serializes a value to a valid Firestore Document data, including object and its childs and Array and its items
export function serializeFS(value) {
const isDate = (value) => {
if(value instanceof Date || value instanceof firestore.Timestamp){
return true;
}
try {
if(value.toDate() instanceof Date){
return true;
}
} catch (e){}
return false;
};
if(value == null){
return null;
}
if(
typeof value == "boolean" ||
typeof value == "bigint" ||
typeof value == "string" ||
typeof value == "symbol" ||
typeof value == "number" ||
isDate(value) ||
value instanceof firestore.FieldValue
) {
return value;
}
if(Array.isArray(value)){
return (value as Array<any>).map((v) => serializeFS(v));
}
const res = {};
for(const key of Object.keys(value)){
res[key] = serializeFS(value[key]);
}
return res;
}
Usage:
await db().collection('products').doc()
.set(serializeFS(
new ProductEntity('something', 123, FieldValue.serverTimestamp()
)));
I am using ListBlobsSegmentedAsync in my C# code to list all the blobs.Is there a way i can separate the images and videos from response of ListBlobsSegmentedAsync ?
Here is an example from this link. You should be able to optimise the code to do a yield return which will return results iteratively and not leave your calling code waiting for all the results to be returned.
public static String WildCardToRegular(String value)
{
return "^" + Regex.Escape(value).Replace("\\*", ".*") + "$";
}
Then, using it with ListBlobsSegmentedAsync:
var blobList = await container.ListBlobsSegmentedAsync(blobFilePath, true, BlobListingDetails.None, 1000, token, null, null);
var items = blobList.Results.Select(x => x as CloudBlockBlob);
// Filter items by search pattern, if specify
if (!string.IsNullOrEmpty(searchPattern))
{
items = items.Select(i =>
{
var filename = Path.GetFileName(i.Name);
if (Regex.IsMatch(filename, WildCardToRegular(searchPattern), RegexOptions.IgnoreCase))
{
return i;
}
return null;
}).ToList();
}
Is there a good, efficient way to check many req.body values whether they are not undefined or null?
At most I have about 17 values to check.
You can create a function that takes a list of property names and checks to see if they have values other than null or undefined.
// checks to see if all props in the list are available and non-null
// list can either be an array or a | separated string
function checkProps(obj, list) {
if (typeof list === "string") {
list = list.split("|");
}
for (prop of list) {
let val = obj[prop];
if (val === null || val === undefined) {
return false;
}
}
return true;
}
Then, you could use it like this:
if (!checkProps(req.body, "email|firstName|lastName|address1|city|state|zip")) {
// some properties missing
} else {
// all properties available
}