2009-01-20 9 views
2

J'ai une hiérarchie que je voudrais interroger avec LinqToSql:Comment puis-je interroger des hiérarchies avec LinqToSQL?

Pays -> Région -> Ville -> ZipCode

Chaque entité détient à la fois une référence à son parent (. Par exemple Region.Country) et une collection de ses enfants (par exemple Region.Cities). Je voudrais charger avec impatience le parent de chaque entité avec les pays et les régions, mais les villes de chargement paresseux et les codes postaux. Pour compliquer les choses, chaque entité est localisée avant d'être projetée dans le modèle. So Country.Name change en fonction de la langue.

Voici quelques extraits de ce que j'ai jusqu'à présent:

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); 
} 

Le test échoue avec:

System.NotSupportedException: Méthode « Système. Linq.IQueryable`1 [Beeline.EducationCompass.Model.Region] GetRegions (Int32) 'n'a pas de traduction prise en charge vers SQL.

Que recommanderiez-vous de faire? Serait-il plus simple (ou possible) si chaque niveau de la hiérarchie était dans la même table au lieu de ceux séparés?

Répondre

2

Vous allez vouloir utiliser le concepteur linq pour configurer les relations entre vos objets. Cela vous évite d'écrire une jointure après une jointure après une jointure en créant des propriétés.

  • entre un pays et ses régions
  • entre une région et ses villes
  • entre un pays et ses Localizations
  • entre une région et ses Localizations

Vous allez voulez utiliser ToList pour séparer les opérations que vous avez l'intention d'être traduites en SQL et les opérations que vous avez l'intention de faire en code local.Si vous ne le faites pas, vous continuerez à voir les exceptions "ne peut pas traduire votre méthode en SQL".

Vous voudrez également utiliser DataLoadOptions pour charger avec impatience ces propriétés dans certains cas. Voici mon coup de couteau.

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(); 
    } 
} 
+0

Merci! Cela semble vraiment prometteur. Je vais essayer demain. –

+0

Une variation de cela a travaillé. Pour éviter que LinqToSql ne lance une requête par région pour obtenir les villes, j'ai dû créer à la fois un ToRegion (ec_Region dto) et un ToRegionShallow (ec_Region dto). La version peu profonde crée le modèle mais ne remplit pas les collections enfants (Villes dans le cas de la Région). –

+0

Je pense que si ma base de données utilisait l'approche Nested Set de Joe Celko, il serait peut-être possible de le faire en une seule requête. Théoriquement, il suffit d'un passage dans le jeu de résultats pour instancier la hiérarchie. Quelqu'un d'autre a des idées à ce sujet? –

1

C'est une pièce collante de code, et je ne l'aurais pas répondu à cette raison d'un manque de compétences pertinentes si quelqu'un d'autre avait, mais depuis que vous avez eu aucune réponse ...

je peux vous dire ce que l'erreur moyen de message. Cela signifie que la fonction GetRegions ne peut pas être traduite en sql par le fournisseur linq to sql. Certaines fonctions intégrées peuvent être, car le fournisseur les comprend, voici un list. Sinon, vous pouvez fournir des traductions voir here. Dans votre situation, vous devez 'inline' la logique de cette requête, la logique ne franchira pas la limite d'un appel de fonction, parce que vous avez affaire à une arborescence d'expression, le serveur sql ne peut pas rappeler votre méthode GetRegions.

Quant à la façon exacte de faire cela, vous devrez essayer, je n'ai pas le temps de vous obliger pour le moment. (Sauf si quelqu'un d'autre a le temps et la compétence?)

Bonne chance.

Questions connexes