Subsonic 3.0 и linq
Я играю с Subsonic 3.0 SimpleRepository и пытаюсь получить меню и элементы меню одним запросом linq, но элементы меню всегда пустые
Меню
public class Menu
{
public Menu()
{
MenuId = 0;
MenuName = "";
MenuItems = null;
}
public int MenuId { get; set; }
public string MenuName { get; set; }
public MenuItem MenuItems { get; set; }
}
Пункт меню
public class MenuItem
{
public MenuItem()
{
MenuItemId = 0;
MenuId = 0;
MenuItemName = "";
}
public int MenuItemId { get; set; }
public int MenuId { get; set; }
public string MenuItemName { get; set; }
}
Запрос Linq
var menus = from m in _repo.All<Menu>()
from mi in _repo.All<MenuItem>()
where m.MenuItems.MenuItemId == mi.MenuItemId
select new Menu
{
MenuId = m.MenuId,
MenuName = m.MenuName,
MenuItems = {
MenuItemId = mi.MenuItemId,
MenuItemName = mi.MenuItemName
}
};
Может кто-нибудь сказать мне, что я здесь делаю не так?
2 ответа
Я думаю, что нашел фактический ответ на эту проблему. Я покопался в источнике SubSonic и обнаружил, что существует два типа проекции объектов, которые используются при отображении хранилища данных на объекты: один для анонимных типов и группировок и один для всего остального:
Вот фрагмент: строка 269 - 298 из SubSonic.Linq.Structure.DbQueryProvider
IEnumerable<T> result;
Type type = typeof (T);
//this is so hacky - the issue is that the Projector below uses Expression.Convert, which is a bottleneck
//it's about 10x slower than our ToEnumerable. Our ToEnumerable, however, stumbles on Anon types and groupings
//since it doesn't know how to instantiate them (I tried - not smart enough). So we do some trickery here.
if (type.Name.Contains("AnonymousType") || type.Name.StartsWith("Grouping`") || type.FullName.StartsWith("System.")) {
var reader = _provider.ExecuteReader(cmd);
result = Project(reader, query.Projector);
} else
{
using (var reader = _provider.ExecuteReader(cmd))
{
//use our reader stuff
//thanks to Pascal LaCroix for the help here...
var resultType = typeof (T);
if (resultType.IsValueType)
{
result = reader.ToEnumerableValueType<T>();
}
else
{
result = reader.ToEnumerable<T>();
}
}
}
return result;
Оказывается, что SubSonic ToEnumerable пытается сопоставить имена столбцов в устройстве чтения данных со свойствами объекта, на который вы пытаетесь проецировать. Запрос SQL из моего Linq выглядит так:
SELECT [t0].[Id], [t0].[ProductId], [t0].[ReleaseDate], [t0].[ReleasedBy], [t0].[ReleaseNumber], [t0].[RevisionNumber], [t0].[c0]
FROM (
SELECT [t1].[Id], [t1].[ProductId], [t1].[ReleaseDate], [t1].[ReleasedBy], [t1].[ReleaseNumber], [t1].[RevisionNumber], (
SELECT COUNT(*)
FROM [dbo].[Install] AS t2
WHERE ([t2].[ReleaseId] = [t1].[Id])
) AS c0
FROM [dbo].[Release] AS t1
) AS t0
WHERE ([t0].[ProductId] = 2)
Обратите внимание, что [t0].[C0] не совпадает с именем моего свойства NumberOfInstalls. Таким образом, значение c0 никогда не проецируется в мой объект.
ИСПРАВЛЕНИЕ: Вы можете просто взять оператор if и использовать в 10 раз более медленную проекцию, и все будет работать.
Я не думаю, что ты здесь делаешь что-то не так. Это похоже на проблему с Subsonic 3.0. У меня сейчас есть вопрос, на который я не получил ответа. Я также недавно попробовал что-то попроще. Но это тоже не работает.
var result = from r in Release.All()
let i = Install.All().Count(x => x.ReleaseId == r.Id)
where r.ProductId == productId
select new ReleaseInfo
{
NumberOfInstalls = i,
Id = r.Id,
ProductId = r.ProductId,
ReleaseNumber = r.ReleaseNumber,
RevisionNumber = r.RevisionNumber,
ReleaseDate = r.ReleaseDate,
ReleasedBy = r.ReleasedBy
};
Свойство Number of Installs не заполняется, но если я сопоставляю анонимный тип, все работает:
var result = from r in Release.All()
let i = Install.All().Count(x => x.ReleaseId == r.Id)
where r.ProductId == productId
select new
{
NumberOfInstalls = i,
Id = r.Id,
ProductId = r.ProductId,
ReleaseNumber = r.ReleaseNumber,
RevisionNumber = r.RevisionNumber,
ReleaseDate = r.ReleaseDate,
ReleasedBy = r.ReleasedBy
};
Если вы измените свой код на следующий, он, вероятно, будет работать:
var menus = from m in _repo.All<Menu>()
from mi in _repo.All<MenuItem>()
where m.MenuItems.MenuItemId == mi.MenuItemId
select new
{
MenuId = m.MenuId,
MenuName = m.MenuName,
MenuItems = new {
MenuItemId = mi.MenuItemId,
MenuItemName = mi.MenuItemName
}
};
Этот тип побеждает цель, так как вы хотите отобразить обратно к вашему предопределенному типу объекта. Может быть, мы можем получить ответ от Роба на это?:)