Como posso consultar hierarquias com LINQ to SQL?
-
19-08-2019 - |
Pergunta
Eu tenho uma hierarquia que eu gostaria de consulta com LinqToSql:
Country -> Região -> City -> ZipCode
Cada entidade detém tanto uma referência a ele de pai (ex. Region.Country) e uma coleção de TI de crianças (por exemplo. Region.Cities).
Eu gostaria de carga ansioso pai de cada entidade, juntamente com os países e regiões, mas cidades carga lenta e códigos postais.
Para coisas complicar, cada entidade está sendo localizada antes de ser projetada para o modelo. Então Country.Name muda de acordo com o idioma.
Eis alguns trechos do que eu tenho até agora:
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);
}
O teste falha com:
System.NotSupportedException:. Método 'System.Linq.IQueryable`1 [Beeline.EducationCompass.Model.Region] getRegions (Int32)' não tem apoiado tradução para SQL
Como você recomendaria eu vou sobre isso? Seria mais simples (ou possível) se cada nível da hierarquia estava na mesma mesa em vez dos separados?
Solução
Você vai querer usar o designer LINQ para configurar as relações entre seus objetos. Isso você fica fora da escrita aderir após aderir após juntar-se através da criação de propriedades.
- Entre um país e suas regiões
- entre uma região e suas Cidades
- entre um país e sua Localizações
- entre uma região e suas Localizações
Você vai querer usar ToList para separado as operações que pretende ser traduzido em SQL, e às operações que pretende ser feito em código local. Se você não fizer isso, você vai continuar vendo aqueles "não pode traduzir o seu método em SQL" exceções.
Você também vai querer usar DataLoadOptions para carregar ansiosamente essas propriedades em alguns casos. Aqui está a minha facada nele.
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();
}
}
Outras dicas
Isso é um pedaço pegajoso de código, e eu não teria respondido isso devido à falta de habilidade relevante se mais alguém teve, mas desde que você não tinha respostas ...
Eu posso dizer o que os meios de mensagens de erro. Isso significa que os getRegions função não pode ser traduzido em sql pelo provedor LINQ to SQL. Algumas funções internas podem ser, porque o provedor entende-los, aqui está uma href="http://msdn.microsoft.com/en-us/library/bb882657.aspx" rel="nofollow lista . Caso contrário, você pode fornecer traduções ver aqui .
Na sua situação você precisa 'inline' a lógica desta consulta, a lógica não irá atravessar a fronteira de uma chamada de função, porque você está lidando com um árvore de expressão, o servidor SQL não pode chamar de volta em seu getRegions método.
Quanto à maneira exata de fazer isso, você terá que ter um ir, eu não tenho o tempo para obrigá-lo no momento. (A menos que alguém tem tempo e habilidade?)
Boa sorte.