У меня есть иерархия, которую я хотел бы запросить с помощью LinqToSql:
Страна -> Регион -> Город -> ZipCode
Каждый объект содержит ссылку на своего родителя (например, Region.Country) и коллекцию его дочерних элементов (например, Region.Cities).
Я бы хотел загрузить родительский элемент каждой сущности вместе со странами и регионами, но не загружать города и почтовые индексы.
Чтобы усложнить ситуацию, каждая сущность локализуется перед проецированием в модель. Итак, Country.Name меняется в зависимости от языка.
Вот несколько фрагментов того, что у меня есть на данный момент:
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;
}
... так далее.
[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);
}
Тест не проходит с:
System.NotSupportedException: Метод 'System.Linq.IQueryable`1 [Beeline.EducationCompass.Model.Region] GetRegions (Int32)' не поддерживает преобразование в SQL.
Как вы порекомендуете мне это сделать? Было бы проще (или возможно), если бы каждый уровень иерархии находился в одной таблице, а не в отдельных?





Это один липкий фрагмент кода, и я бы не ответил на него из-за отсутствия соответствующих навыков, если бы кто-то еще имел, но поскольку у вас нет ответов ...
Я могу сказать вам, что означает сообщение об ошибке. Это означает, что функция GetRegions не может быть переведена в sql поставщиком linq to sql. Некоторые встроенные функции могут быть, потому что провайдер их понимает, вот это список. В противном случае вы можете предоставить перевод см. здесь.
В вашей ситуации вам необходимо «встроить» логику этого запроса, логика не будет пересекать границу вызова функции, поскольку вы имеете дело с деревом выражений, сервер sql не может выполнить обратный вызов в ваш метод GetRegions.
Что касается точного способа сделать это, вам придется попробовать, в данный момент у меня нет времени, чтобы помочь вам. (Если у кого-то еще нет времени и навыков?)
Удачи.
Вы захотите использовать конструктор linq для настройки отношений между вашими объектами. Это избавляет вас от записи соединения после соединения после соединения путем создания свойств.
Вы захотите использовать К списку для разделения тех операций, которые вы собираетесь транслировать в SQL, и тех операций, которые вы собираетесь выполнять в локальном коде. Если вы этого не сделаете, вы будете продолжать видеть исключения типа «невозможно преобразовать ваш метод в SQL».
Вы также захотите использовать DataLoadOptions для быстрой загрузки этих свойств в некоторых случаях. Вот мой удар.
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();
}
}
Вариант этого сработал. Чтобы LinqToSql не выдавал по одному запросу на регион для получения городов, мне пришлось создать как ToRegion (ec_Region dto), так и ToRegionShallow (ec_Region dto). Мелкая версия создает модель, но не заполняет дочерние коллекции (Города в случае Региона).
Я думаю, если бы моя база данных использовала подход Джо Селко «Вложенный набор», это можно было бы сделать в одном запросе. Теоретически для создания экземпляра иерархии потребуется только один проход по набору результатов. У кого-нибудь еще есть мысли по этому поводу?
Спасибо! Это выглядит многообещающе. Я попробую завтра.