EF Core async select InvalidOperationException - c#

The following code
var merchant = await _dbContext.Merchants
.Include(m => m.Users)
.SingleAsync(m => m.MerchantId == id);
var userTasks = merchant.Users.Select(async u =>
{
var roles = await _userManager.GetRolesAsync(u);
return new MerchantUser
{
UserName = u.UserName,
Role = string.Join(",", roles)
};
}).ToList();
var users = await Task.WhenAll(userTasks);
return View(new MerchantViewModel
{
MerchantId = merchant.MerchantId,
MerchantName = merchant.Name,
MerchantUsers = users.ToList()
});
sometimes returns this error:
System.InvalidOperationException: A second operation started on this context before a previous operation completed.
However, this code does not. To my understanding, it's doing the same thing so I don't understand why it's failing.
var merchant = await _dbContext.Merchants
.Include(m => m.Users)
.SingleAsync(m => m.MerchantId == id);
var users = new List<MerchantUser>();
foreach (var user in merchant.Users)
{
var roles = await _userManager.GetRolesAsync(user);
users.Add(new MerchantUser
{
UserName = user.UserName,
Role = string.Join(",", roles)
});
}
return View(new MerchantViewModel
{
MerchantId = merchant.MerchantId,
MerchantName = merchant.Name,
MerchantUsers = users
});

var userTasks = merchant.Users.Select(async u => { … }).ToList();
var users = await Task.WhenAll(userTasks);
This will asynchronously start all those Select tasks at the same time and then wait for them to complete. So this will run multiple things in parallel. Since you are querying the user manager inside, this will not work since the underlying connection does not support parallel queries.
In contrast, your foreach loop will only run one query at a time, awaiting the GetRolesAsync before the next iteration begins. So instead of working in parallel, the roles will be read sequentially for all users.

Related

Combine the outputs from SubOrchestrator Function

I have a Durable Function which reads a list of AzureAD groups and gets users from each group. Here is the code:
Orchestrator:
public async Task RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var groups = await context.CallActivityAsync<List<AzureADGroup>>("GroupsReaderFunction"), null);
if (groups != null && groups.Count > 0)
{
var processingTasks = new List<Task>();
foreach (var group in groups)
{
var processTask = context.CallSubOrchestratorAsync<List<AzureADUser>>("SubOrchestratorFunction", group });
processingTasks.Add(processTask);
}
await Task.WhenAll(processingTasks);
}
}
SubOrchestrator:
public async Task<List<AzureADUser>> RunSubOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var request = context.GetInput<AzureADGroup>();
var users = await context.CallActivityAsync<List<AzureADUser>>("UsersReaderFunction", request.objectId);
return users;
}
I need to put all the users from all the groups to a list + remove duplicate users (if any - because some users may exist in multiple groups) and run some activity functions with all users as input. How would I do that? Please let me know.
Error on trying out the code from Answers:
Your suborchestrator should return List<AzureADUser>
public async Task<List<AzureADUser>> RunSubOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var request = context.GetInput<Group>();
var users = await context.CallActivityAsync<List<AzureADUser>>("UsersReaderFunction", request.objectId);
return users;
}
Then your orchestrator can receive the output and combine the lists with
public async Task RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var groups = await context.CallActivityAsync<List<AzureADGroup>>("GroupsReaderFunction"), null);
if (groups != null && groups.Count > 0)
{
var processingTasks = new List<Task<List<AzureADUser>>>();
foreach (var group in groups)
{
var processTask = context.CallSubOrchestratorAsync<List<AzureADUser>>("SubOrchestratorFunction", group);
processingTasks.Add(processTask);
}
await Task.WhenAll(processingTasks);
var users = new List<AzureADUser>();
foreach (var task in processingTasks)
{
users.AddRange(await task);
}
}
}
Im not familiar with AzureADUser but if you have an id or email you can use that to get distinct users from the list.
e.g.
var distinctList = users.GroupBy(user => user.Id).Select(userGrp => userGrp.First()).ToList()

Getting Azure AD users is taking way to much time! How to reduce the time?

I am getting Azure AD users into a list to be used in a dropdown, but it takes about at least 8/9 seconds to do the call... I know this can probably be reduced... So I will place my code here, hoping that someone can give me a better idea of how to change the code to a better one.
public async Task<List<Microsoft.Graph.User>> getAzureUsers()
{
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("***")
.WithTenantId("***")
.WithClientSecret("***")
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
List<Microsoft.Graph.User> utilizadores = new List<Microsoft.Graph.User>();
var user = await graphClient.Users
.Request()
.Select(p => new {
p.DisplayName,
p.UserPrincipalName,
p.Id
})
.GetAsync();
utilizadores.AddRange(user.CurrentPage);
while (user.NextPageRequest != null)
{
user = await user.NextPageRequest.GetAsync();
utilizadores.AddRange(user.CurrentPage);
}
return utilizadores;
}
public async Task<ViewModelColaboradores> GetVM()
{
bool colExist = false;
List<Microsoft.Graph.User> utilizadores = new List<Microsoft.Graph.User>();
List<UserAD> nomes = new List<UserAD>();
utilizadores = await getAzureUsers();
ViewModelColaboradores vm = new ViewModelColaboradores();
foreach (var n in utilizadores)
{
foreach (var col in _context.RH_Colaboradores)
{
if (n.DisplayName == col.Nome)
{
colExist = true;
}
}
if (!colExist)
{
nomes.Add(new UserAD { DisplayName = n.DisplayName, Id = n.Id, Email = n.UserPrincipalName });
}
colExist = false;
}
vm.usersAd = nomes;
return vm;
}
and then I just call GETVM to get the viewmodel with the users inside. Any ideas?
You can use pagination to improve the performance.
You can have a search-based dropdown(non-paginated) or scroll based dropdown (paginated - that fetches new items on scrolling down and fetches scroll up results from cache).
C# sample code:
public async Task<AzureUserDto> GetUsersPage(int pageSize, string skipToken)
{
var filterString = $"startswith(givenName, '{firstName}')";
var queryOptions = new List<QueryOption>
{
new QueryOption("$top", pageSize)
};
if (!string.IsNullOrEmpty(skipToken))
{
queryOptions.Add(new QueryOption("$skiptoken", skipToken));
}
var azureUsers = await GraphServiceClient.Users
.Request(queryOptions)
.Filter(filterString)
.Select(x => new
{
x.Id,
x.DisplayName,
x.GivenName,
x.Surname,
x.UserPrincipalName,
x.AccountEnabled,
x.Identities,
x.BusinessPhones,
x.JobTitle,
x.MobilePhone,
x.OfficeLocation,
x.PreferredLanguage,
x.Mail
})
.GetAsync();
// Get SkipToken, if exists
var skipToken = azureUsers
.NextPageRequest?
.QueryOptions?
.FirstOrDefault(
x => string.Equals("$skiptoken", x.Name, StringComparison.InvariantCultureIgnoreCase))?
.Value;
var azureUserDto = new AzureUserDto
{
// Map the azureUsers to AzureUsersDto or something similar and return
// don't forget to include the SkipToken
};
return azureUsers;
}
For more details on how to implement the latter method, you can visit Paging Microsoft Graph data in your app and Microsoft Graph API: C# / Filtering / Pagination.

Test connection with TLSharp

I'm trying to send a message with TLSharp but cant,i dont get errors,it just execute the code and do nothing;
This is my method.
public virtual async Task SendMessageTest()
{
string NumberToSendMessage = "+55199999999";
if (string.IsNullOrWhiteSpace(NumberToSendMessage))
throw new Exception("TESTE");
// this is because the contacts in the address come without the "+" prefix
var normalizedNumber = NumberToSendMessage.StartsWith("+") ?
NumberToSendMessage.Substring(1, NumberToSendMessage.Length - 1) :
NumberToSendMessage;
var client = NewClient();
var tsk = client.ConnectAsync();
await client.ConnectAsync();
var result = await client.GetContactsAsync();
var user = result.users.lists
.OfType<TLUser>()
.FirstOrDefault(x => x.phone == normalizedNumber);
if (user == null)
{
throw new System.Exception("Number was not found in Contacts List of user: " + NumberToSendMessage);
}
await client.SendTypingAsync(new TLInputPeerUser() { user_id = user.id });
Thread.Sleep(3000);
await client.SendMessageAsync(new TLInputPeerUser() { user_id = user.id }, "TEST");
}
This is my code,is says is wait for activation,what should i do?
I'm trying to use this method also,but it doenst return nothing too.
I'm new to TelegramApi,what i'm doing wrong?
await client.ConnectAsync();
You should authorize first! And only after that you can call other methods. Look at the examples here.
In general you should write something like this:
var hash = await client.SendCodeRequestAsync(NotRegisteredNumberToSignUp);
var code = Console.ReadLine(); //Input the code, that was sent to your phone
var loggedInUser = await client.MakeAuthAsync(NotRegisteredNumberToSignUp, hash, code);

A second operation started on this context before a previous asynchronous operation completed

Message:
"System.NotSupportedException was unhandled
Message: An unhandled exception of type 'System.NotSupportedException' occurred in mscorlib.dll
Additional information: A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe."
Code:
public async Task<IEnumerable<UserLangDTO>> ImportLang(int userId)
{
var userLangs = new List<UserLangDTO>();
using (FirstContext ctx = new FirstContext())
{
if (await (ctx.UserLang.AnyAsync(u => u.UserId == userId)) == false)
//some exception here
userLangs = await ctx.UserLang.AsNoTracking()
.Where(ul => ul.UserId == userId)
.Join(ctx.Language,
u => u.LangID,
l => l.LangID,
(u, l) => new { u, l })
.Join(ctx.Level,
ul => ul.u.LevelID,
le => le.LevelID,
(ul, le) => new { ul, le })
.Select(r => new UserLangDTO
{
UserId = r.ul.u.UserId,
Language = r.ul.l.Language,
Level = r.le.Level,
}).ToListAsync().ConfigureAwait(false);
}
using (SecondContext ctx = new SecondContext())
{
if ( await (ctx.UserLangs.AnyAsync(u => u.UserId == userId)) == true && userLangs.Any())
ctx.UserLangs.RemoveRange(ctx.UserLangs.Where(u => u.UserId == userId));
if (await hasUserLangs && userLangs.Any())
{
userLangs.ForEach(async l =>
{
var userLanguage = new UserLang();
userLanguage.UserId = userId;
userLanguage.LanguageId = await ctx.Languages.AsNoTracking()
.Where(la => la.NameEn == l.Language)
.Select(la => la.Id).FirstOrDefaultAsync().ConfigureAwait(false);
userLanguage.LevelId = await ctx.Levels.AsNoTracking()
.Where(la => la.NameEn == l.Language)
.Select(la => la.Id).FirstOrDefaultAsync().ConfigureAwait(false);
ctx.UserLangs.Add(userLanguage);
});
}
await ctx.SaveChangesAsync().ConfigureAwait(false);
}
return userLangs;
}
What I Tried:
I'm not sure what I'm doing wrong, I tried different stuff like :
1.
await Task.Run(() => Parallel.ForEach(strings, s =>
{
    DoSomething(s);
}));
2.
var tasks = userLangs.Select(async l =>
{
//rest of the code here
}
await Task.WhenAll(tasks);
3.
var tasks = userLangs.Select(async l =>
{
//rest of the code here
}
await Task.WhenAll(tasks);
await ctx.SaveChangesAsync().ConfigureAwait(false);
Other trial and error attempts, that i do not rember now
What am I doing wrong?
Here's your problem:
userLangs.ForEach(async
This is creating an async void method, because ForEach does not understand asynchronous delegates. So the body of the ForEach will be run concurrently, and Entity Framework does not support concurrent asynchronous access.
Change the ForEach to a foreach, and you should be good:
foreach (var l in userLangs)
{
var userLanguage = new UserLang();
userLanguage.UserId = userId;
userLanguage.LanguageId = await ...
}
For more information, see the "avoid async void" guidance in my Async Best Practices article.
For those who have the ability to use .NET 6, there is now a Parallel.ForEachAsync()
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreachasync?view=net-6.0
https://www.hanselman.com/blog/parallelforeachasync-in-net-6

Outlook Contacts API - Paging Results

I'm trying to display ALL the Outlook contacts for a selected account. When an account has a few thousand contacts, the following code only shows the first n contacts. The contactResults object has a MorePagesAvailable property and a GetNextPageAsync() method available, but I clearly do NOT know how to use them. Can someone please enlighten me.
string token = (string)Session["access_token"];
string email = (string)Session["user_email"];
// Since we have the token locally from the Session, just return it here
OutlookServicesClient client = new OutlookServicesClient(new Uri("https://outlook.office.com/api/v2.0"), async () => { return token; });
client.Context.SendingRequest2 += new EventHandler<SendingRequest2EventArgs>((sender, e) => InsertXAnchorMailboxHeader(sender, e, email));
var contactResults = await client.Me.Contacts
.OrderBy(c => c.DisplayName)
.Take(2500)
.Select(c => new DisplayContact(c))
.ExecuteAsync();
foreach (DisplayContact displayContact in contactResults.CurrentPage)
System.Diagnostics.Debug.WriteLine(displayContact);
var contactResults = await client.Me.Contacts
.OrderBy(c => c.DisplayName)
.Select(c => new DisplayContact(c))
.ExecuteAsync();
while (true)
{
foreach (DisplayContact displayContact in contactResults.CurrentPage)
System.Diagnostics.Debug.WriteLine(displayContact);
if (contactResults.MorePagesAvailable)
contactResults = await contactResults.GetNextPageAsync();
else
break;
}

Categories

Resources