【问题标题】:Subsonic 3.0 and linq亚音速 3.0 和 linq
【发布时间】:2009-10-31 19:30:11
【问题描述】:

我正在使用 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
                        }
             };

谁能告诉我我在这里做错了什么?

【问题讨论】:

    标签: linq subsonic subsonic3


    【解决方案1】:

    我想我已经找到了这个问题的实际答案。我一直在 SubSonic 源中翻找,发现在将数据读取器映射到对象时使用了两种类型的对象投影:一种用于匿名类型和分组,另一种用于其他所有类型:

    这是一个 sn-p:SubSonic.Linq.Structure.DbQueryProvider 的第 269 - 298 行

    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 尝试将数据读取器中的列名与您尝试投影到的对象中的属性相匹配。我的 Linq 中的 SQL 查询如下所示:

    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 倍的投影,一切都会奏效。

    【讨论】:

    • 是 : "if (type.Name.Contains("AnonymousType")" 还是 : "if (resultType.IsValueType)" 需要放出来?
    • if(type.Name.Contains("AnonymousType") || type.Name.StartsWith("Grouping")...那一行。
    • 抱歉回复晚了 ;o) 我试过你的建议,正如你所说,它慢了 10 倍。我发现执行以下操作更快: from m in _repo.All() where m.MenuId == id mi.MenuItemId select new Menu { MenuId = m.MenuId, MenuName = m.MenuName, MenuItems = from mi in _repo.All() where mi.MenuItemId == m.MenuId };
    【解决方案2】:

    我不认为你在这里做错了什么。这似乎是 Subsonic 3.0 的问题。我现在有一个问题,我还没有得到here 的答案。我最近也尝试了一些更简单的方法。但这也不起作用。

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

    安装次数属性没有被填充,但如果我映射到匿名类型,一切正常:

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

    由于您想映射回预定义的对象类型,因此这种方式无法达到目的。也许我们可以从 Rob 那里得到答案? :)

    【讨论】:

    • 当我使用 DDD 模式时,我从服务层中的方法返回对象,我根本无法使用匿名类型。唯一可行的方法是遍历我所有的manuitems,缓存它们(只访问一次数据库)并将它们添加到菜单对象中。
    猜你喜欢
    • 2011-01-08
    • 1970-01-01
    • 2011-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多