Subsonic 3.0 and linq

两盒软妹~` 提交于 2019-12-01 08:40:55

I think I've found the actual answer to this problem. I've been rummaging around in the SubSonic source and found that there are two types of object projection that are used when mapping the datareader to objects: one for anonymous types and groupings and one for everything else:

Here is a snippet: Line 269 - 298 of 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;

Turns out that the SubSonic ToEnumerable tries to match the column names in the datareader to the properties in the object you're trying to project to. The SQL Query from my Linq looks like this:

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)

Notice the [t0].[c0] is not the same as my property name NumberOfInstalls. So the value of c0 never gets projected into my object.

THE FIX: You can simply take out the if statement and use the 10x slower projection and everything will work.

Jeremy Seekamp

I don't think you're doing anything wrong here. This seems to be a problem with Subsonic 3.0. I have a question on it right now that I haven't gotten an answer to here. I've also recently tried something simpler. But that isn't working either.

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

The Number of Installs Property does not get populated, but if I map to an anonymous type everything works:

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

If you change your code to the following it will probably work:

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

This kind of defeats the purpose since you want to map back to your predefined object type. Maybe we can get an answer from Rob on this? :)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!