Pregunta

Tengo una jerarquía que me gustaría consultar con LinqToSql:

País - > Región - > Ciudad - > Código postal

Cada entidad contiene tanto una referencia a su elemento primario (p. ej., Region.Country) como una colección de sus elementos secundarios (p. ej., Region.Cities).

Me gustaría cargar ansiosamente el padre de cada entidad junto con los Países y Regiones, pero cargar perezosamente las ciudades y los códigos postales.

Para complicar las cosas, cada entidad se está localizando antes de proyectarse en el modelo. Entonces Country.Name cambia según el idioma.

Aquí hay algunos fragmentos de lo que tengo hasta ahora:

public IQueryable<Country> ListCountries()
{
  return ProjectCountry(dataContext.GetTable<ec_Country>());
}

private IQueryable<Country> ProjectCountry(IQueryable<ec_Country> query)
{
  var result = from country in query
  join localized in dataContext.GetTable<ec_CountryLocalization>() on country.CountryID equals localized.CountryID
  let regions = GetRegions(country.CountryID)
  where localized.StatusID == 4 && localized.WebSiteID == this.webSiteID
  select new Country(country.CountryID) {
    CreatedDate = country.CreatedDate,
    IsDeleted = country.IsDeleted,
    IsoCode = country.IsoCode,
    Name = country.Name,
    Regions = new LazyList<Region>(regions),
    Text = localized.Text,
    Title = localized.Title,
    UrlKey = country.UrlKey
  };

  return result;
}

private IQueryable<Region> GetRegions(Int32 countryID)
{
  var query = from r in dataContext.GetTable<ec_Region>()
  where r.CountryID == countryID
  orderby r.Name
  select r;

  return ProjectRegion(query);
}

private IQueryable<Region> ProjectRegion(IQueryable<ec_Region> query)
{
  var result = from region in query
  join localized in dataContext.GetTable<ec_RegionLocalization>() on region.RegionID equals localized.RegionID
  join country in ListCountries() on region.CountryID equals country.CountryID
  let cities = GetCities(region.RegionID)
  select new Region(region.RegionID) {
    Cities = new LazyList<City>(cities),
    Country = country,
    CountryID = region.CountryID,
    CreatedDate = region.CreatedDate,
    IsDeleted = region.IsDeleted,
    IsoCode = region.IsoCode,
    Name = region.Name,
    Text = localized.Text,
    Title = localized.Title,
    UrlKey = region.UrlKey
  };

  return result;
}

... etc.

[TestMethod]
public void DataProvider_Correctly_Projects_Country_Spike()
{
  // Act
  Country country = dataProvider.GetCountry(1);

  // Assert
  Assert.IsNotNull(country);
  Assert.IsFalse(String.IsNullOrEmpty(country.Description));
  Assert.IsTrue(country.Regions.Count > 0);
}

La prueba falla con:

System.NotSupportedException: Método 'System.Linq.IQueryable`1 [Beeline.EducationCompass.Model.Region] GetRegions (Int32)' no tiene traducción compatible a SQL.

¿Cómo recomendarías que haga esto? ¿Sería más simple (o posible) si cada nivel de la jerarquía estuviera en la misma tabla en lugar de estar separados?

¿Fue útil?

Solución

Querrá utilizar el diseñador linq para establecer relaciones entre sus objetos. Esto le permite dejar de escribir unirse después de unirse después de unirse creando propiedades.

  • entre un país y sus regiones
  • entre una región y sus ciudades
  • entre un país y sus localizaciones
  • entre una región y sus localizaciones

Vas a querer usar ToList para separar aquellas operaciones que pretende traducir a SQL y aquellas operaciones que pretende realizar en código local. Si no hace esto, seguirá viendo que "no puede traducir su método a SQL" excepciones.

También querrá usar DataLoadOptions para cargar con entusiasmo estas propiedades en algunos casos. Aquí está mi puñalada.

DataLoadOptions dlo = new DataLoadOptions();
//bring in the Regions for each Country
dlo.LoadWith<ec_Country>(c => c.Regions);
//bring in the localizations
dlo.AssociateWith<ec_Country>(c => c.Localizations
  .Where(loc => loc.StatusID == 4 && loc.WebSiteID == this.webSiteID)
);
dlo.AssociateWith<ec_Region>(r => r.Localizations);

//set up the dataloadoptions to eagerly load the above.
dataContext.DataLoadOptions = dlo;

//Pull countries and all eagerly loaded data into memory.
List<ec_Country> queryResult = query.ToList();

//further map these data types to business types
List<Country> result = queryResult
  .Select(c => ToCountry(c))
  .ToList();

public Country ToCountry(ec_Country c)
{
  return new Country()
  {
    Name = c.Name,
    Text = c.Localizations.Single().Text,
    Regions = c.Regions().Select(r => ToRegion(r)).ToList()
  }
}

public Region ToRegion(ec_Region r)
{
  return new Region()
  {
    Name = r.Name,
    Text = r.Localizations.Single().Text,
    Cities = r.Cities.Select(city => ToCity(city)).ToLazyList();
  }
}

Otros consejos

Ese es un código pegajoso, y no habría respondido esto debido a la falta de habilidad relevante si alguien más lo hubiera hecho, pero dado que no tenía respuestas ...

Puedo decirte lo que significa el mensaje de error. Significa que el proveedor linq a sql no puede traducir la función GetRegions a sql. Algunas funciones integradas pueden ser, porque el proveedor las entiende, aquí hay una lista . De lo contrario, puede proporcionar traducciones; consulte aquí .

En su situación, necesita 'en línea' la lógica de esta consulta, la lógica no cruzará el límite de una llamada a la función, ya que está tratando con un árbol de expresión, el servidor SQL no puede volver a llamar a su Método GetRegions.

En cuanto a la forma exacta de hacerlo, tendrás que intentarlo, no tengo tiempo para obligarte en este momento. (¿A menos que alguien más tenga tiempo y habilidad?)

Buena suerte.

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