Дозвуковой 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
                        }
             };

Может кто-нибудь сказать мне, что я здесь делаю не так?


person Martin Overgaard    schedule 31.10.2009    source источник


Ответы (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 раз медленнее, и все заработает.

person Jeremy Seekamp    schedule 03.11.2009
comment
Это: if (type.Name.Contains (AnonymousType) или это: if (resultType.IsValueType), который нужно выпустить? - person Martin Overgaard; 04.11.2009
comment
If (type.Name.Contains (AnonymousType) || type.Name.StartsWith (Grouping) ... эта строка. - person Jeremy Seekamp; 04.11.2009
comment
Извините за поздний ответ; o) Я попробовал ваше предложение, и, как вы сказали, оно в 10 раз медленнее. Я обнаружил, что выполнение чего-то подобного ниже было быстрее: от m в _repo.All ‹Menu› (), где m.MenuId == id mi.MenuItemId выберите новое меню {MenuId = m.MenuId, MenuName = m.MenuName, MenuItems = from mi в _repo.All ‹MenuItem› (), где mi.MenuItemId == m.MenuId}; - person Martin Overgaard; 10.12.2009

Не думаю, что вы здесь делаете что-то не так. Похоже, это проблема 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
                        }
             };

Этот вид побеждает цель, поскольку вы хотите вернуться к своему предопределенному типу объекта. Может быть, мы сможем получить ответ от Роба по этому поводу? :)

person Jeremy Seekamp    schedule 03.11.2009
comment
Поскольку я использую шаблон DDD, где я возвращаю объект из метода на моем уровне обслуживания, я просто не могу использовать анонимный тип. Единственное, что пока удалось, - это перебрать все мои мануалы, кэшировать их (только один раз попасть в базу данных) и добавить их в объект меню. - person Martin Overgaard; 03.11.2009