Make Entity Framework Linq query async - c#

I have a function that populates a combo box in my winform app. The operation takes a few seconds so I'd like to make it async.
Here is the function:
public static List<string> GetAllPostCodeRegions(string country)
{
using (var db = new PlaceDBContext())
{
var regions = db.PostCodes.Where(pc => pc.Country == country).OrderBy(pc => pc.Region).Select(pc => pc.Region).Distinct().ToList();
return regions;
}
}
I tried just adding in async keywords like below:
public async static Task<List<string>> GetAllPostCodeRegions(string country)
{
using (var db = new PlaceDBContext())
{
var regions = db.PostCodes.Where(pc => pc.Country == country).OrderBy(pc => pc.Region).Select(pc => pc.Region).Distinct().ToList();
return regions;
}
}
But this isn't awaiting anything and the UI still locks when I call:
var regions = await DataFunctions.GetAllPostCodeRegions("UK");
How do I make this operation async and stop UI locking up?

you can Try .ToListAsync or:
public static Task<List<string>> GetAllPostCodeRegions(string country)
{
return Task.Run(() =>
{
using (var db = new PlaceDBContext())
{
return db.PostCodes
.Where(pc => pc.Country == country)
.OrderBy(pc => pc.Region)
.Select(pc => pc.Region).Distinct().ToList();
}
});
}

Related

Cannot await a .find method using mongodb c#

I am a novice so I apologize but I keep getting this CS1061 error when trying to await a .find() method. How do I await this find()?
MongoClient client = new MongoClient(mongoDBSettings.Value.ConnectionURI);
IMongoDatabase database = client.GetDatabase(mongoDBSettings.Value.DatabaseName);
_userCollection = database.GetCollection<User>(mongoDBSettings.Value.CollectionName);
_partyCollection = database.GetCollection<Party>(mongoDBSettings.Value.CollectionName);
public async Task<IEnumerable> GetNearbyParties(string postalCode)
{
var nearbyParties = await _partyCollection.Find(x => x.Address.postalCode == postalCode);
return (IEnumerable<Party>)nearbyParties;
}
MongoClient client = new MongoClient(mongoDBSettings.Value.ConnectionURI);
IMongoDatabase database = client.GetDatabase(mongoDBSettings.Value.DatabaseName);
_userCollection = database.GetCollection(mongoDBSettings.Value.CollectionName);
_partyCollection = database.GetCollection(mongoDBSettings.Value.CollectionName);
public async Task<IEnumerable> GetNearbyParties(string postalCode)
{
var nearbyParties = await _partyCollection.Find(x => x.Address.postalCode == postalCode);
return (IEnumerable<Party>)nearbyParties;
}
I had it originally set up like this running synchronously:
public async Task<IEnumerable> GetNearbyParties(string postalCode)
{
var nearbyParties = _partyCollection.Find(x => x.Address.postalCode == postalCode);
return (IEnumerable<Party>)nearbyParties;
//return results;
}
But I understand that since it's an async method I should have an await when I try to search the database so that other things can run while that is fetched.
You need to call asynchronous API of Mongo not normal Find method:
public async Task<IEnumerable> GetNearbyParties(string postalCode) {
var nearbyParties = await _partyCollection.FindAsync(x => x.Address.postalCode == postalCode);
return (IEnumerable<Party>)nearbyParties;
}

How can I get object fill by SignalR Method?

This is my problem :
I want call signalR function from controller to get list of users connected.
In my OnConnected(), OnDisconnected method I collect the correct data but when I create my own method i can't get the list of Users.
I feel I have a problem with my context because my list is empty but I don't know really why.
I have try somes test :
This a counter of number users connected in my application with SignalR.
The list of users is fill with the OnConnected() method and clean with OnDisconnected()
OnConnected() an user is add:
public override async Task OnConnected()
{
var currentCollab = Context.User.Identity.Name.Length > 0 ? Context.User.Identity.Name : "";
var module = Context.QueryString["module"];
if (!string.IsNullOrEmpty(module))
{
SignalRUsers.Add(new UserConnected()
{
ConnectionId = Context.ConnectionId,
UserName = currentCollab,
ModuleActif = module
});
}
await Clients.All.UpdateCountAccueil(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)));
await base.OnConnected();
}
In this method SignalRUsers the list is filled with all previous connections but as soon as I try to retrieve the values from this list in a method to call it in my controller this list is completely empty (while there are many active connections)
Complete Hub Class :
using System;
using System.Activities.Statements;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Util;
using System.Windows.Forms;
using ConvergenceCore.ServiceSettingsManager;
using ConvergenceDataAccess.ActiveDirectory;
using Microsoft.AspNet.SignalR;
namespace Main
{
public class ChatHub : Hub
{
private static long counterAccueil = 0;
private static long counterParam = 0;
static Dictionary<string, int> CurrentConnections = new Dictionary<string, int>();
static List<UserConnected> SignalRUsers = new List<UserConnected>();
public void Send(string module, string etat, string message)
{
// Call the broadcastMessage method to update clients.
Clients.All.broadcastMessage(module, etat, message);
}
public override async Task OnReconnected()
{
var currentCollab = Context.User.Identity.Name.Length > 0 ? Context.User.Identity.Name : "";
var module = Context.QueryString["module"];
if (!string.IsNullOrEmpty(module))
{
SignalRUsers.Add(new UserConnected()
{
ConnectionId = Context.ConnectionId,
UserName = currentCollab,
ModuleActif = module
});
}
await Clients.All.UpdateCountAccueil(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)));
await Clients.All.UpdateCountParam(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Param.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Param.Value)));
await Clients.All.UpdateCountRh(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Rh.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Rh.Value)));
await Clients.All.UpdateCountFacturation(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Facturation.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Facturation.Value)));
await base.OnReconnected();
}
public override async Task OnConnected()
{
var currentCollab = Context.User.Identity.Name.Length > 0 ? Context.User.Identity.Name : "";
var module = Context.QueryString["module"];
if (!string.IsNullOrEmpty(module))
{
SignalRUsers.Add(new UserConnected()
{
ConnectionId = Context.ConnectionId,
UserName = currentCollab,
ModuleActif = module
});
}
await Clients.All.UpdateCountAccueil(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)));
await Clients.All.UpdateCountParam(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Param.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Param.Value)));
await Clients.All.UpdateCountRh(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Rh.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Rh.Value)));
await Clients.All.UpdateCountFacturation(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Facturation.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Facturation.Value)));
await base.OnConnected();
}
public override async Task OnDisconnected(bool stopCalled)
{
var userToDelete = SignalRUsers.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId);
SignalRUsers.Remove(userToDelete);
//CurrentConnections.Remove(module + Context.ConnectionId);
await Clients.All.UpdateCountAccueil(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)));
await Clients.All.UpdateCountParam(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Param.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Param.Value)));
await Clients.All.UpdateCountRh(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Rh.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Rh.Value)));
await Clients.All.UpdateCountFacturation(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Facturation.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Facturation.Value)));
await base.OnDisconnected(stopCalled);
}
public List<UserConnected> GetListUsersConnected(string module)
{
return SignalRUsers.Where(x => x.ModuleActif == module).ToList();
}
}
public class UserConnected
{
public string UserName { get; set; }
public string ConnectionId { get; set; }
public string ModuleActif { get; set; }
}
}
In my Controller :
public PartialViewResult DetailUsersConnected(string module)
{
ChatHub hub = new ChatHub();
var listUsers = hub.GetListUsersConnected(module);
return PartialView("../Parametrages/Content/_DetailsUserConnected", listUsers);
}
Why is my list empty in my GetListUsersConnected method ?
Why I do not recover met previous connection as in OnConnected() for example ?
Every time a client connects a new instance of the Hub object is created. If your SignalRUsers is a property of the hub, then is created from scratch every time. Try make it a static property or use a Singleton in a container.

Nunit testing using FirstOrDefaultAsync()

I have the following method:
public async Task<string> GetDescription(int codeId)
{
var Codes = Context.Codes.Where(x => x.Id == codeId).FirstOrDefault();
return Codes.Description;
}
and the following unit test:
[Test]
public async Task GetDescription()
{
var result = await _serivce.GetSpecialtyDescription(1);
Assert.That(result == "description");
}
and I setup my test like this:
public ServicesTests()
{
_dbContext = new Mock<LocalContext>();
var CodeList = new List<Codes>() { new Codes() { Id = 1, Description = "description" } };
var dbSetCodeList = MockDbSet.GetQueryableMockDbSet(CodeList);
_dbContext.Setup(x => x.Codes).Returns(dbSetCodeList.Object);
_context = new DataContext<LocalContext, LocalContext>(_dbContext.Object);
_serivce = new CodesServices(_context);
}
public static Mock<DbSet<T>> GetQueryableMockDbSet<T>(List<T> sourceList) where T : class
{
var queryable = sourceList.AsQueryable();
var dbSet = new Mock<DbSet<T>>();
dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>((s) => sourceList.Add(s));
return dbSet;
}
Which all works fine.
Now, I want to await the function, so do this:
public async Task<string> GetDescription(int codeId)
{
var Codes = await Context.Codes.Where(x => x.Id == codeId).FirstOrDefaultAsync();
return Codes.Description;
}
but this breaks my unit test - even though the code still works.
I have read plenty of articles and posts explaining that this does not work as the async methods cannot be mocked (I think?), but I have not come across anything which shows me how to resolve this.
I would be disappointing if I was forced to choose between awaiting my code or having a unit test, and feel sure there must be a way around this?
I am not happy with the result, but this is the best I can do so far:
public async Task<string> GetDescription(int codeId)
{
var Codes = await GetAll();
return Codes.Description;
}
private async Task<DbSet<Codes>> GetAll()
{
return Context.Codes;
}
If anyone has a better suggestion please let me know.

Continuous update of ViewModel from DataService MVVM Light

Originally my ViewModel had the following:
public ObservableCollection<DataListItem> files { get; private set; }
private object _filesLock = new object();
public MainViewModel(IDataService dataService)
{
files = new ObservableCollection<DataListItem>();
BindingOperations.EnableCollectionSynchronization(files, _filesLock);
_dataService = dataService;
}
One of the commands ran this code
await Task.Run(() => {
Parallel.ForEach(files, new ParallelOptions() { MaxDegreeOfParallelism = 2 }, (file) =>
{
this.files.Add(new DataListItem(file));
});
});
But this doesn't work at design time. So I moved the code into the DataService.
public async void ScanFolder()
{
if (!CanScanFolder()) return;
files.Clear();
await Task.Run(() =>
{
_dataService.GetData(SelectedFolder, filter, IncludeSubs, (myfiles, error) =>
{
if (error != null)
{
return;
}
foreach (var file in myfiles.files)
{
files.Add(file);
}
}
);
});
}
The DataService code looks like this
public async void GetData(string folder, string filter, bool includeSubs, Action<DataItem, Exception> callback)
{
// Use this to connect to the actual data service
var item = new DataItem(folder, filter, includeSubs);
await item.ScanFolderAsync();
callback(item, null);
}
public async Task<bool> ScanFolderAsync()
{
try
{
var ret = new List<DataListItem>();
var files = Directory.EnumerateFiles(folder, filter, includeSubs ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
await Task.Run(() => {
Parallel.ForEach(files, new ParallelOptions() { MaxDegreeOfParallelism = 2 }, (file) =>
{
this.files.Add(new DataListItem(file));
});
});
return true;
}
catch (Exception)
{
return false;
}
}
But as far as I can tell there is no continuous communication channel between the DataService and the ViewModel. So with the new approach it reads all of the files and then displays it in the grid, but I want it to display each update as it goes.
My instinct is to add a Messenger within the foreach and subscribe to it in the ViewModel, but there does not seem to be a way to use a messenger within a data service.
How can the DataService send a message to the ViewModel each time it has scanned a file? Alternatively is there a better architecture for loading the data?
It turns out I was just missing the using statement for messenger and it is possible to update the ViewModel using it.
In the ViewModel constructor I added
Messenger.Default.Register<DataListItem>(this, (item) => { files.Add(item); });
and the ScanFolderAsync() method was updated to send the message
using GalaSoft.MvvmLight.Messaging;
public async Task<bool> ScanFolderAsync()
{
try
{
var ret = new List<DataListItem>();
var files = Directory.EnumerateFiles(folder, filter, includeSubs ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
await Task.Run(() => {
Parallel.ForEach(files, new ParallelOptions() { MaxDegreeOfParallelism = 1 }, (file) =>
{
var item = new DataListItem(file);
Messenger.Default.Send<DataListItem>(item);
this.files.Add(item);
});
});
return true;
}
catch (Exception)
{
return false;
}
}

Task Results into a Single list

I have a method below in my WCF service:
public List<string> ProcessTask(IEnumerable<string> data)
{
var contentTasks = ..........
List<string> contentWeb = new List<string>();
Task.Factory.ContinueWhenAll(contentTasks, tasks =>
{
foreach (var task in tasks)
{
if (task.IsFaulted)
{
Trace.TraceError(task.Exception.GetBaseException().Message);
continue;
}
if (task.Result == null || String.IsNullOrEmpty(task.Result.Content))
{
continue;
}
contentWeb.Add(task.Result.Content);
}
});
}
How do I return the List of strings that have Result.Content from all
the tasks? These tasks are asynchronous tasks, so basically I have to wait until all tasks are done before I return the result.
You should return a Task<List<string>>:
public Task<List<string>> ProcessTasksAsync(IEnumerable<string> data)
{
var contentTasks = ..........
return Task.Factory.ContinueWhenAll(contentTasks, tasks =>
{
var contentWeb = new List<string>(); // Build this in the continuation
foreach (var task in tasks)
{
// ...same code...
contentWeb.Add(task.Result.Content);
}
return contentWeb; // Set the task's result here
});
}
As this is a WCF service, you can use the Task<T> method to implement an asynchronous method pair by returning the Task<T> in the Begin*** method, and unwrapping the Task<T> in the End*** method.
This makes this method asynchronous in a proper manner.
Note that this is far easier in C# 5 using async/await:
public async Task<List<string>> ProcessTasksAsync(IEnumerable<string> data)
{
var contentTasks = ..........
await Task.WhenAll(contentTasks);
var contentWeb = new List<string>(); // Build this in the continuation
foreach (var task in contentTasks)
{
// ...same code...
contentWeb.Add(task.Result.Content);
}
return contentWeb;
}
instead of continuewhenall i had to use Task.WaitAll(contentTasks);

Categories