I have a list of Objects that I get from Realtime-Database (Firebase) with a wrapper in C# Firebase.Xamarin
private List<Job> jobList = null;
which populates at first the application is loaded using the following code:
private async void PopulateList()
{
IReadOnlyCollection<Firebase.Xamarin.Database.FirebaseObject<Job>> items = await firebase
.Child("jobs")
.OnceAsync<Job>();
jobList = new List<Job>();
foreach (var item in items)
{
jobList.Add(
new Job
{
ID = item.Object.ID,
StartDate = item.Object.StartDate,
EndDate = item.Object.EndDate,
Description = item.Object.Description
});
}
SubscribeDbChanges();
}
I want to subscribe the DB to fire an event and change/add new Objects to the list and fire an event afterwords to show or notify the user for one time, a change has been occurred. For this purpose I am using the Observable i.e Reactive Rx.Net with All
in the following way:
private void SubscribeDbChanges()
{
Observable.All<FirebaseEvent<Job>>(
firebase
.Child("jobs")
.AsObservable<Job>(),
job => !jobList
.Contains(job.Object))
.Subscribe(jobItem =>
{
});
}
Is there any thing wrong with the code? Moreover, where should I create the event that a change has arrived?
First I would recommend getting rid of the foreach/Add, it is the work for Select
private async void PopulateList()
{
jobList = (await firebase
.Child("jobs")
.OnceAsync<Job>())
.Select(item =>
new Job
{
ID = item.Object.ID,
StartDate = item.Object.StartDate,
EndDate = item.Object.EndDate,
Description = item.Object.Description
});
SubscribeDbChanges();
}
Then I would use Where. How you use All is weird, it is an extension method and you call it like a usual static method. It is possible but it is now how it should be used. Here is the code with Where:
private void SubscribeDbChanges()
{
firebase
.Child("jobs")
.AsObservable<Job>()
.Where(job => !jobList.Contains(job.Object))
.Subscribe(jobItem =>
{
});
}
Thanks to #Alexey Zimarev for giving me a quick click, and the flowing code came into appearance:
First Of all took a ConcurrentDictionary that as a data member and initialized the Firebaseclient and than populated the list ;
FirebaseClient firebase;
private ConcurrentDictionary<string, Job> jobList ;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
firebase = new FirebaseClient("https://samplehosting-XXXX.firebaseio.com/");
PopulateList();
}
Inside this function I initialized the dictionary and done the UI work, at last set the listeners respectively to monitor the change in SubscribeToDbChanges
private async void PopulateList()
{
IReadOnlyCollection<Firebase.Xamarin.Database.FirebaseObject<Job>> items = await firebase
.Child("jobs")
.OnceAsync<Job>();
jobList = new ConcurrentDictionary<string, Job>();
foreach (var job in items)
{
while (!jobList.TryAdd(job.Object.ID, job.Object)) ;
}
list = FindViewById<ListView>(Resource.Id.listJobs);
list.ChoiceMode = ChoiceMode.Single;
HomeScreenAdapter ListAdapter = new HomeScreenAdapter(this, jobList);
list.Adapter = ListAdapter;
SubscribeToDbChanges();
}
Here i set the Insertion observer for the keys that are not available in the dictionary and than set the deletion observer for the keys that are available in the dictionary.
private void SubscribeToDbChanges()
{
firebase
.Child("jobs").AsObservable<Job>()
.Where(job => !jobList.ContainsKey(job.Object.ID) && job.EventType == Firebase.Xamarin.Database.Streaming.FirebaseEventType.InsertOrUpdate)
.Subscribe(job =>
{
if (job.EventType == Firebase.Xamarin.Database.Streaming.FirebaseEventType.InsertOrUpdate)
{
while (!jobList.TryAdd(job.Object.ID, job.Object)) ;
}
});
firebase
.Child("jobs").AsObservable<Job>()
.Where(job => jobList.ContainsKey(job.Object.ID) && job.EventType == Firebase.Xamarin.Database.Streaming.FirebaseEventType.Delete)
.Subscribe(job =>
{
Thread remove = new Thread(() =>
{
Job removed = null;
jobList.TryRemove(job.Object.ID, out removed);
});
remove.Start();
});
}
PS: Assumption is here, I assume that the objects that we were adding in the above question and answer were not getting compared because of there memory existence. Like a new object was getting instantiated the might be different from its clone already existing in the list. Please correct me if I am wrong with respect to Linq behavior. However the above given code works in my scenario with dictionary.
Related
Simple process: I am getting items from my container and iterate them. Change a property and store it again.
Method to get items
public async Task<ActionItem[]> GetActionsNotExecutedAsync(string transferId)
{
QueryDefinition query = new("SELECT * FROM c WHERE c.TransferId = #transferId AND c.Executed = #executed");
query.WithParameter("#transferId", transferId);
query.WithParameter("#executed", false);
QueryRequestOptions opts = new QueryRequestOptions();
opts.PartitionKey = new PartitionKey(transferId);
opts.MaxItemCount = -1;
List<ActionItem> actions = new();
using FeedIterator<ActionItem> feed = _action.GetItemQueryIterator<ActionItem>(query);
while (feed.HasMoreResults)
{
foreach (ActionItem action in await feed.ReadNextAsync())
{
actions.Add(action);
}
}
return actions.ToArray();
}
Method to update an item
public async Task UpdateActionAsync(ActionItem item)
{
//await _transfer.UpsertItemAsync<ActionItem>(item, new PartitionKey(item.TransferId));
await _transfer.ReplaceItemAsync<ActionItem>(item, item.id);
}
Code that uses both Methods
ActionItem[] actions = await db.GetActionsNotExecutedAsync(evt.TransferId);
foreach (ActionItem action in actions)
{
action.Executed = true;
await db.UpdateActionAsync(action);
}
I expect to see the property "Executed" to be changed to TRUE, but instead I get 404 when using ReplaceItemAsync(). No error but also no change of property when using UpsertItemAsync().
Oh, i am so blind! I was using different containers shameonme
As you can see the first method to read the ActionItems uses a container named "_action" and the second method to update the ActionItem uses a container named "_transfer".
I am trying to filter out all errors except one from a list of errors I send back to the front end. I realize this operation should be an async operation as my request is giving a 500 internal server error. I am new to C# and am having a hard time figuring out how to do so.
My code that gets invoked on the route request looks like:
public async Task<ActionResult> Index(ProfileParams profileParameters)
{
// ...... //
var user = await GenerateUser(Request.RequestContext);
var userState = await _userStateFactory.CreateAsync(user);
var stateTree = new BusinessProfileStateTreeModel
{
Global = await _globalStateFactory.CreateAsync(user),
Header = await _headerStateFactory.CreateAsync(user, null),
User = userState,
Modals = _modalsStateFactory.Create(),
Page = CreatePageState(),
BusinessProfile = _businessProfileReviewsStateFactory.Create(viewModel, customerReviewModel),
Analytics = await _analyticsStateFactory.CreateAsync(user, CreateDtmData(viewModel?.Categories?.PrimaryCategoryName, profileBbbInfo?.BbbName, viewModel), userState)
};
// trying to filter out errors here from the state tree alerts
var errors = filterErrorsAsync(stateTree.BusinessProfile.Display.Alerts.AllAlerts);
var metaData =
GenerateProfileMetaData(customerReviewModel.NumFound, viewModel.ProfileUrl.ToUrlString(), viewModel);
var serverSideModel =
GenerateServerSideModel(
viewModel,
metaData,
profileBbbInfo,
stateTree.Analytics.DtmData,
user);
return await ReduxViewAsync(stateTree.ToList(), serverSideModel);
}
}
The filterErrorsAsync method looks like:
private List<BPAlert> filterErrorsAsync(List<BPAlert> allAlerts)
{
foreach (BPAlert alert in allAlerts)
{
if (alert.AlertTypeId == (int)BusinessReportCustomTextType.CustomerReviews)
{
allAlerts.Clear();
allAlerts.Add(alert);
}
}
return allAlerts;
}
Can someone tell me how to achieve this correctly?
You can't loop a list and modify it at the same time. This is probably what is causing your 500 error.
It looks like you only want filter out certain errors from a list. If you want to keep your method as a loop you can do:
private List<BPAlert> filterErrorsAsync(List<BPAlert> allAlerts)
{
List<BPAlert> temp = new List<BPAlert>(); //copy into new list
foreach (BPAlert alert in allAlerts)
{
if (alert.AlertTypeId == (int)BusinessReportCustomTextType.CustomerReviews)
{
temp.Add(alert);
}
}
return temp;
}
If you want to be a little more modern you can also just use LINQ
private List<BPAlert> filterErrorsAsync(List<BPAlert> allAlerts)
{
return allAlerts.Where(alert => alert.AlertTypeId == (int)BusinessReportCustomTextType.CustomerReviews).ToList();
}
You're attempting to modify a list while enumerating it which won't work. Since you already know which kind of error you want to filter to, you can utilize LINQ's Where method to filter out the other errors, then use Take to get the first one.
private List<BPAlert> filterErrors(List<BPAlert> allAlerts)
=> allAlerts.Where(alert => alert.AlertTypeID == (int)BusinessReportCustomTextType.CustomerReviews)
.Take(1)
.ToList();
There isn't anything asynchronous happening in this method, so no need to mark it async.
I have listview containing data from web API. I want to search in the listview with character wise. The problem I am facing is when I start searching, it works fine but it gets very very slow. I need some solution to fix it. Here is my code:
private async void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
var httpClient = new HttpClient();
var json = await httpClient.GetStringAsync(" http://172.16.4.212:51583/api/GetItems");
var admtPatients = JsonConvert.DeserializeObject<List<tblItem>>(json);
ObservableCollection<tblItem> trends = new ObservableCollection<tblItem>(admtPatients);
if (string.IsNullOrEmpty(medicine.Text))
{
MyListView.ItemsSource = trends;
}
else
{
MyListView.ItemsSource = trends
.Where(x =>
x.strItemName.ToLowerInvariant().Contains(e.NewTextValue.ToLowerInvariant()) ||
x.strItemName.ToUpperInvariant().Contains(e.NewTextValue.ToUpperInvariant()));
}
//await ((MainViewModel)this.BindingContext).LoadCountNotificationAsync();
}
Each time Entry_TextChanged is triggered, the call to GetStringAsync is done, which is very time consuming. This means that whenever the user presses a key a call to the API is made. This is why it is so slow.
You are better off calling GetStringAsync in the page's OnAppearing (for example), and saving the result globally:
private List<tblItem> listOfTableItems = new List<tblItem>();
protected override void OnAppearing()
{
var json = await httpClient.GetStringAsync("http://172.16.4.212:51583/api/GetItems");
listOfTableItems = JsonConvert.DeserializeObject<List<tblItem>>(json);
}
Then, in your Entry_TextChanged you reference listOfTableItems from the examples above:
if (String.IsNullOrEmpty(e.NewTextValue))
{
MyListView.ItemsSource = new ObservableCollection<tblItem>(listOfTableItems);
}
else
{
MyListView.ItemsSource = new ObservableCollection<tblItem>(listOfTableItems
.Where(x => x.strItemName.ToLowerInvariant().Contains(e.NewTextValue.ToLowerInvariant())));
}
I used the following approach long time (approx 5 years):
Create one big class with initialization of XXXEntities in controller and create each method for each action with DB. Example:
public class DBRepository
{
private MyEntities _dbContext;
public DBRepository()
{
_dbContext = new MyEntities();
}
public NewsItem NewsItem(int ID)
{
var q = from i in _dbContext.News where i.ID == ID select new NewsItem() { ID = i.ID, FullText = i.FullText, Time = i.Time, Topic = i.Topic };
return q.FirstOrDefault();
}
public List<Screenshot> LastPublicScreenshots()
{
var q = from i in _dbContext.Screenshots where i.isPublic == true && i.ScreenshotStatus.Status == ScreenshotStatusKeys.LIVE orderby i.dateTimeServer descending select i;
return q.Take(5).ToList();
}
public void SetPublicScreenshot(string filename, bool val)
{
var screenshot = Get<Screenshot>(p => p.filename == filename);
if (screenshot != null)
{
screenshot.isPublic = val;
_dbContext.SaveChanges();
}
}
public void SomeMethod()
{
SomeEntity1 s1 = new SomeEntity1() { field1="fff", field2="aaa" };
_dbContext.SomeEntity1.Add(s1);
SomeEntity2 s2 = new SomeEntity2() { SE1 = s1 };
_dbContext.SomeEntity1.Add(s2);
_dbContext.SaveChanges();
}
And some external code create DBRepository object and call methods.
It worked fine. But now Async operations came in. So, if I use code like
public async void AddStatSimplePageAsync(string IPAddress, string login, string txt)
{
DateTime dateAdded2MinsAgo = DateTime.Now.AddMinutes(-2);
if ((from i in _dbContext.StatSimplePages where i.page == txt && i.dateAdded > dateAdded2MinsAgo select i).Count() == 0)
{
StatSimplePage item = new StatSimplePage() { IPAddress = IPAddress, login = login, page = txt, dateAdded = DateTime.Now };
_dbContext.StatSimplePages.Add(item);
await _dbContext.SaveChangesAsync();
}
}
can be a situation, when next code will be executed before SaveChanged completed and one more entity will be added to _dbContext, which should not be saved before some actions. For example, some code:
DBRepository _rep = new DBRepository();
_rep.AddStatSimplePageAsync("A", "b", "c");
_rep.SomeMethod();
I worry, that SaveChanged will be called after line
_dbContext.SomeEntity1.Add(s1);
but before
_dbContext.SomeEntity2.Add(s2);
(i.e. these 2 actions is atomic operation)
Am I right? My approach is wrong now? Which approach should be used?
PS. As I understand, will be the following stack:
1. calling AddStatSimplePageAsync
2. start calling await _dbContext.SaveChangesAsync(); inside AddStatSimplePageAsync
3. start calling SomeMethod(), _dbContext.SaveChangesAsync() in AddStatSimplePageAsync is executing in another (child) thread.
4. complete _dbContext.SaveChangesAsync() in child thread. Main thread is executing something in SomeMethod()
Ok this time I (think)'ve got your problem.
At first, it's weird that you have two separate calls to SaveChangesmethod. Usually you should try to have it at the end of all your operations and then dispose it.
Even thought yes, your concerns are right, but some clarifications are needed here.
When encountering an asyncor await do not think about threads, but about tasks, that are two different concepts.
Have a read to this great article. There is an image that will practically explain you everything.
To say that in few words, if you do not await an async method, you can have the risk that your subsequent operation could "harm" the execution of the first one. To solve it, simply await it.
I'm developing a function to return a collection, generated from an xml file.
Initially, I was using a local xml file for testing, but now I'm ready to have the app download the real xml file from a server. I'm struggling to see how I could do this due to the fact a WebClient object needs to be given an OpenReadCompleted event handler - I cannot return the collection data from this, and also by the time this handler executes, the original function has ended.
My original code is as follows:
public static ObservableCollection<OutletViewModel> GetNear(GeoCoordinate location)
{
ObservableCollection<OutletViewModel> Items = new ObservableCollection<OutletViewModel>();
// Load a local XML doc to simulate server response for location
XDocument xDoc = XDocument.Load("SampleRemoteServer/outlet_list.xml");
foreach (XElement outlet in xDoc.Descendants("outlet"))
{
Items.Add(new OutletViewModel()
{
Name = outlet.Attribute("name").Value,
Cuisine = outlet.Attribute("cuisine").Value
});
}
return Items;
}
How can I load the file in this function, have the event handler run, and then continue the function?
The only was I can think of is to add a loop to keep checking a variable, which is updated by the event handler code... and that doesn't sound like a good solution.
Thanks,
Josh
You move the foreach() loop to the completed event.
And that indeed means you cannot return anything from the original method. Make it a void.
This is how async I/O works, better get used to it. You will need to rethink your design.
You should start to take a look at async programming.
One (old school) way would be to implement a public event and subscribe to that event in the calling class.
However, using callbacks is more elegant. I whipped up a simple (useless, but still conceptually valid) example that you can build upon:
public static void Main(string[] args)
{
List<string> list = new List<string>();
GetData(data =>
{
foreach (var item in data)
{
list.Add(item);
Console.WriteLine(item);
}
Console.WriteLine("Done");
});
Console.ReadLine();
}
public static void GetData(Action<IEnumerable<string>> callback)
{
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += (s, e) =>
{
List<string> data = new List<string>();
for (int i = 0; i < 5; i++)
{
data.Add(e.Result);
}
callback(e.Error == null ? data : Enumerable.Empty<string>());
};
webClient.DownloadStringAsync(new Uri("http://www.google.com"));
}
If you want to jump onto the C# async bandwagon (link for WP7 implementation), you can implement it using the new async and await keywords:
public static async void DoSomeThing()
{
List<string> list = new List<string>();
list = await GetDataAsync();
foreach (var item in list)
{
Console.WriteLine(item);
}
}
public static async Task<List<string>> GetDataAsync()
{
WebClient webClient = new WebClient();
string result = await webClient.DownloadStringTaskAsync(new Uri("http://www.google.com"));
List<string> data = new List<string>();
for (int i = 0; i < 5; i++)
{
data.Add(result);
}
return data;
}