Pregunta

I learn with asp.net identity, async/await, and I have this problem:

I have some function for IEnumerable<T> list. This list I fill using query syntax and looks like this:

private IEnumerable<SomeModel> GetPersons(int categoryId) {
IEnumerable<SomeModel> persons = from g in db.Categories
                               join c in db.Persons on g.PersonTypeId equals c.PersonTypeId
                               where g.CategoryId == categoryId
                               select new SomeModel
                               {
                                   PersonName = c.FirstName + " " + c.LastName,
                                   //....etc.
                                   //And here I need call asynchronous function something like this:
                                   IsAdmin = GetPermission(c.Email)
                               }
    if (persons.Any()) {
        return persons;
    }

    return Enumerable.Empty<SomeModel>();
}

In SomeModel is IsAdmin as bool (when I tried Task<bool> in GetPermission I use Task<bool> in SomeModel). In GetPermission() is this:

private bool GetPermission(string email) {
    var user = SomeMembershipService.GetUser(email); //SomeMembershipService is Interface with Tasks and so on.
    var roles = SomeMembershipService.GetRoles(user.Id); //user.Id is as string
    bool result = false;
    if (roles != null) {
        var adm = roles.Result.FirstOrDefault(x => x.Name.Contains("Admin"));
        result = adm != null;
    }
    return result;
}

I tried this write with async/await and as Task, but both my tries was false. So I thought that I have to call GetPermission() outside of IEnumerable<SomeModel> persons so I add this block of code into condition after. So code looks like this:

private IEnumerable<SomeModel> GetPersons(int categoryId) {
    IEnumerable<SomeModel> persons = from g in db.Categories
                                   join c in db.Persons on g.PersonTypeId equals c.PersonTypeId
                                   where g.CategoryId == categoryId
                                   select new SomeModel
                                   {
                                       PersonName = c.FirstName + " " + c.LastName
                                       //....etc.
                                   }
    if (persons.Any()) 
    {
        //new code
        foreach (var p in persons) 
        {
            p.IsAdmin = GetPermission(p.Email);
        }
        //end of new code
        return persons;
    }

    return Enumerable.Empty<SomeModel>();
}

But this is wrong too. Maybe I understand bad to asp.net identity and async/await and their use... Can you help me - what I have to do? Because now, GetPermission is called too late, so app crash. When I call GetPermission in persons query, function is called too late (after filled persons list) too. I have no idea how to continue.

GetUser() is as public IUser GetUser(string username) and GetRoles() is as public async Task<IEnumerable<IRole>> GetRoles(string userId). I'am sure, that these two methods work fine, I use them in other codes and no problem with them. So it has to be somewhere in code above I think.

I am sorry, if it is stupid question, but I read about this lot here and on msdn, but cannot find result. Thanks to all.


Why I want to use async function as above

I want the function there because when I make whole function as async Task, another function which call this doesn't work properly.

I have this function - it's databinding for kendogrid ():

[HttpPost]
public ActionResult _PersonsBinding([DataSourceRequest]DataSourceRequest request, int id)
{
    DataSourceResult result = GetPersons(id).ToDataSourceResult(request);
    return Json(result);
}

When I make function IEnumerable<SomeModel> GetPersons as async Task<IEnumerable<SomeModel>> GetPersons the binding function doesn't know ToDataSourceResult(), when I make this function as async too. If can be problem here, how can I fix it? Please be patient with me, I am a newbie...

¿Fue útil?

Solución 2

Ok, I resolve it. An error wasn't in code above, problem was in async Task<IEnumerable<IRole>> GetRoles(string userId). This method works fine but it is not good for my code above.

Async getting roles looks like this:

public async Task<IEnumerable<IRole>> GetRolesAsync(string userId)
{
    return await IdentityManager.Roles.GetRolesForUserAsync(userId);
}

But I had to use getting roles which is in code below. I've made new method in IMembershipService - public IEnumerable<IRole> GetRoles(string userId) (the first method renamed to GetRolesAsync) and looks like this:

public IEnumerable<IRole> GetRoles(string userId)
{
    return AsyncHelper.RunSync(() => IdentityManager.Roles.GetRolesForUserAsync(userId));
} 

This returns common IEnumerable<T>. At least I can use my function GetPermissions in sql query. GetPermission() now looks like this:

private bool GetPermission(string email) {
    var user = SomeMembershipService.GetUser(email); 
    var roles = SomeMembershipService.GetRoles(user.Id);

    bool result = false;
    if (roles != null) {
        var adm = roles.FirstOrDefault(x => x.Name.Contains("Admin"));
        result = adm != null;
    }
    return result;
}

in sql query I can use now this:

....
select new SomeModel
                               {
                                   PersonName = c.FirstName + " " + c.LastName,
                                   IsAdmin = GetPermission(c.Email) // <-- This
                               }
....

thanks to all, especially Jeroen, who tried to help.

Otros consejos

Your problem isn't about the async/await. Your problem is because you execute the query more than ones. Every foreach/ToList/ToArray etc. will execute the query from the beginning and will create new objects. Because deep inside linq the results are yielded with `yield return new { .....}' and will create new objects. That's why you lose the IsAdmin bit on the next foreach. (outside the method)

if (persons.Any()) 
{
    // !HERE! <- foreach. this will run the query.
    foreach (var p in persons) 
    {
        p.IsAdmin = GetPermission(p.Email);
    }

    //Where you foreach in it's caller.
    return persons;
}

You need to materialize the query first. You can fix this with ToArray()/ToList(). This way the query is ones executed, iterated and stored as Array.

private IEnumerable<SomeModel> GetPersons(int categoryId) 
{
    IEnumerable<SomeModel> persons = (from g in db.Categories
                                     join c in db.Persons on g.PersonTypeId equals c.PersonTypeId
                                     where g.CategoryId == categoryId
                                     select new SomeModel
                                     {
                                        PersonName = c.FirstName + " " + c.LastName
                                        //....etc.
                                     }).ToArray();   // <------ here
    foreach (var p in persons) 
    {
        p.IsAdmin = GetPermission(p.Email);
    }

    //end of new code
    return persons;
}

If you want to combine the GetPermission() and the query, you should create a SQL-Function.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top