简体   繁体   中英

Dynamically build Entity Framework Select queries

I have a query that looks a bit like this:

context.Users
.........
.Select(user => new UserDTO
{
    UserName = user.Name,
    DataList = context.Data.Where(x => x.UserId == user.Id).ToList(),
    Emails = context.Email.Where(x => x.UserId == user.Id).ToList(),
    BankAccounts = context.BankAccount.Where(x => x.UserId == user.Id).ToList(),
    .....
}

The problem with this is that this all creates a massive query, because all those subselects are seperate lists. The only reason these selects are there is because sometimes I need to filter on them. So I was wondering if it is possible to build them dynamically, like this:

var query = context.Users
    .........
    .Select(user => new UserDTO
    {
        UserName = user.Name,
        .....
    }

if (EmailFilter) query.Select.Add(Emails, context.Email.Where(x => x.UserId == user.Id).ToList());

if (AccountsFilter) query.Select.Add(BankAccounts, context.BankAccount.Where(x => x.UserId == user.Id).ToList());

Not exactly that of course but you get the idea hopefully.

You can dynamically build your projections from outside the DbContext. If you look into expressions you will notice how you can create your select query before calling the EF methods.

For instance what I use is something like this:

internal class TenantFullProjector : IProjector<Tenant, TenantProjection>
{
    public Expression<Func<Tenant, TenantProjection>> GetProjection()
    {
        return (x) => new TenantProjection()
        {
            Code = x.Code,
            DatabaseId = x.DatabaseId,
            Id = x.Id,               
        };
    }
}

I have a IProjector interface which is used by my repository layer. What matters is that it returns an Expression that ultimately will map the Tenant domain model into a TenantProjection projection model. I can create as many different versions as I want as long as I implement the interface correctly. This example for instance maps all columns whereas other projectors might only select a subset of columns.

In the repository layer I will do something like this:

return query.Select(myProjectionExpression); 

...where query equals ctx.Set().

You can do exactly the same thing for your where statements.

If you don't have navigation properties then I think you can use the flag itself inside the Where like this:

Select(user => new UserDTO
{
    UserName = user.Name,
    DataList = context.Data.Where(x => x.UserId == user.Id).ToList(),
    Emails = context.Email.Where(x => EmailFilter && x.UserId == user.Id).ToList(),
    BankAccounts = context.BankAccount.Where(x => AccountsFilter && x.UserId == user.Id).ToList(),
    .....
}

Maybe something like this:

var query = context.Users
    .Select(user => new
    {
        UserName = user.Name,
        Emails = EmailFilter ? context.Email.Where(x => x.UserId == user.Id).ToList() : null,
        BankAccounts = AccountsFilter ? context.BankAccount.Where(x => x.UserId == user.Id).ToList() : null,
        // and so on
    });

Depending on the filter values you get value lists or null in the result properties.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM