Tengo una consulta LINQ contra una base de datos de SQL Server que escribe los datos de cada fila en un objeto Person. Bajo ciertas condiciones, deseo unirme con tablas adicionales y agregar algunos campos más de los objetos Person, todo mientras aprovecho la carga diferida de LINQ.

La clase Person tiene este aspecto:

public class Person 
{
    // Data provided by Persons table
    public string Name { get; set; }
    public string CityName { get; set; }
    public string JobName { get; set; }

    // Data provided by Cities table
    public int? CityPopulation 

    // Data provided by Jobs table
    public int? AverageSalary

    // Data from other tables
    ...
}

Intenté usar el operador ternario, pero la condición no se evalúa de inmediato, sino que se envía a SQL Server para ser evaluada allí, de modo que la unión se ejecute incluso si no es necesaria.

// Fill values provided by Person table
IQueryable<Person> query;

query = dbContext.Persons.Select(x => new Person
{
    Name = x.Name,
    CityName = x.CityName,
    JobName = x.JobName,

    // Get data from City table, perform join to Cities only when cityRequired
    CityPopulation = cityRequired ? x.Cities.Population : (int?) null,
    ...

    // Get data from Job table, perform join to Jobs only when jobsRequired 
    JobAverageSalary = jobRequired ? x.Jobs.AverageSalary : (int?) null,
    ...

    // Get data from other tables
    ...
});

Escribir las declaraciones de unión en una cláusula if y llamar al constructor Person después de que cada unión funcione, pero no es muy eficiente y elegante:

IQueryable<Person> query;

query = dbContext.Persons.Select(x => new Person
{
    Name = x.Name,
    City = x.CityName,
    Job = x.JobName,
}

if(cityRequired)
{
    query = query.Join(dbContext.Cities, Person => Person.CityName, City => City.Name, (Person, City) => new Person 
    {
        // Copying old values
        Name = Person.Name,
        CityName = Person.CityName,
        JobName = Person.Jobname,

        // Filling in new values from City
        CityPopulation = City.Population,
    }
}

if (jobRequired)
...

Agradezco cualquier ayuda!

3
bahne 14 ene. 2018 a las 14:02

3 respuestas

La mejor respuesta

La extensión personalizada que Ivan publicó en el comentario a mi pregunta resolvió el problema. Elimina la instrucción para unirse si la condición en el operador ternario se evalúa como falsa.

0
bahne 16 ene. 2018 a las 08:43

Eche un vistazo a LinqKit - AsExpandable + Predicate Builders + Sub-consultas

LinqKit en GitHub

LinqKit en Nuget

0
Ian Robertson 14 ene. 2018 a las 11:35

Te sugiero que te dejes como modelo de persona así ...

public class Person
{
    // Data provided by Persons table
    public string Name { get; set; }
    public string CityName { get; set; }
    public string JobName { get; set; }
}

Luego, cree una clase de modelo de vista con el posible campo adicional de otras tablas. Pasar el modelo Person como un parámetro constructor como este.

public class PersonViewModel
{
    public PersonViewModel(Person person)
    {
        Name = person.Name;
        CityName = person.CityName;
        JobName = person.JobName;
    }

    public string Name { get; set; }
    public string CityName { get; set; }
    public string JobName { get; set; }

    public int? CityPopulation { get; set; }
    public int? AverageSalary { get; set; }
}

Ahora, ejecute una consulta de combinación que incluya todas las tablas dependientes posibles y luego use el resultado para llenar el PersonViewModel de esta manera ...

var query = dbContext.Persons
            .Join(dbContext.Cities, person => person.CityName, city => city.Name,
                (person, city) => new {person, city}).Join(dbContext.Jobs, person => person.person.JobName,
                job => job.Name, (person, job) => new {person, job}).FirstOrDefault();

var personViewModel = new PersonViewModel(query.person.person)
{
    // Get data from City table, perform join to Cities only when cityRequired
    CityPopulation = cityRequired ? query.person.city.Population : (int?) null,

    // Get data from Job table, perform join to Jobs only when jobsRequired
    AverageSalary = jobRequired ? query.job.AverageSalary : (int?) null
};
return View(personViewModel);

Espero que esto ayude.

1
Ben 14 ene. 2018 a las 15:42