【问题标题】:Object mapping with LINQ and SubSonic使用 LINQ 和 SubSonic 进行对象映射
【发布时间】:2009-10-21 23:42:39
【问题描述】:

我正在使用 SubSonic 3.0.0.3 ActiveRecord 构建一个小项目,但遇到了一个似乎无法解决的问题。

这是 LINQ 查询:

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,
                 Release = new Release
                 {
                     Id = r.Id,
                     ProductId = r.ProductId,
                     ReleaseNumber = r.ReleaseNumber,
                     RevisionNumber = r.RevisionNumber,
                     ReleaseDate = r.ReleaseDate,
                     ReleasedBy = r.ReleasedBy
                 }
             };

ReleaseInfo 对象是一个自定义类,如下所示:

public class ReleaseInfo
{
    public Release Release { get; set; }
    public int NumberOfInstalls { get; set; }
}

Release 和 Install 是 SubSonic 生成的类。

当我查看结果时,Release 属性为空。

如果我将其设为更简单的查询并观察结果,则该值不为空。

var result = from r in Release.All()
             let i = Install.All().Count(x => x.ReleaseId == r.Id)
             where r.ProductId == productId
             select new Release
             {
                 Id = r.Id,
                 ProductId = r.ProductId,
                 ReleaseNumber = r.ReleaseNumber,
                 RevisionNumber = r.RevisionNumber,
                 ReleaseDate = r.ReleaseDate,
                 ReleasedBy = r.ReleasedBy
             };

这是我的 LINQ 查询的问题还是 SubSonic 的限制?

【问题讨论】:

  • 在构建 ReleaseInfo 对象之前,您是否尝试过使用更简单的查询来创建临时变量(使用 let)?
  • 即使我从查询中取出 let 部分,我也有同样的问题。

标签: c# linq subsonic subsonic3


【解决方案1】:

我认为问题可能在于您实际上是在复制 ORM 的功能。要理解的关键是这一行:

from r in Release.All()

此行返回数据库中每个项目的完全填充发布记录列表。永远不需要在查询中的其他任何地方更新版本 - 只需返回 SubSonic 已经为您填充的版本!

使用此逻辑,您应该能够执行以下操作:

 var result = from r in Release.All()
              select new ReleaseInfo {
                  Release = r,
                  NumberOfInstalls = Install.All().Count(x => x.ReleaseId == r.Id)
              };

话虽如此,您应该查看 Install.All() 调用,因为这可能非常低效。这将做的是从数据库中提取每个安装,将这些安装水合到对象中,然后比较 .NET 中每个记录的 id 以检查记录是否满足该条件。您可以使用 SubSonic 中的 .Find 方法仅返回数据库层的某些记录,这将显着提高性能。即使如此,膨胀对象可能仍然很昂贵,您可能需要在此处考虑视图或存储过程。但作为简单的第一步,以下应该可行:

var result = from r in Release.All()
             select new ReleaseInfo {
                 Release = r,
                 NumberOfInstalls = Install.Find(x => x.ReleaseId == r.Id).Count()
             };

【讨论】:

【解决方案2】:

我想我已经找到了这个问题的实际答案。我一直在 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 倍的投影,一切都会奏效。

【讨论】:

    【解决方案3】:

    我们有一个错误,预测在某些情况下会跳闸 - 我认为它已被修补,但我需要对其进行更多测试。我邀请您尝试最新的位 - 我想我们已经修复了它......抱歉这么含糊,但一个错误在 3.0.0.1 和 3.0.0.3 之间起作用,我无法找到它。

    【讨论】:

    • 罗布,我使用的是 3.0.0.3。如果您有兴趣,我认为我标记为答案的帖子将向您显示错误所在的确切位置。感谢您花时间看这个。
    【解决方案4】:

    这个问题在 3.0.0.4 中修复了吗?我很生气找到这个帖子。经过 2 天的尝试找出为什么我的预测不起作用 - 除非属性名称与查询完全匹配 - 我最终来到了这里。 我非常依赖 SS SimpleRepository,现在回头已经太迟了。像这样的错误令人瘫痪。有机会解决吗?

    我现在走的是慢 10 倍的路线,所以我至少可以发布给我的客户。更希望更快的方法正常工作:)

    【讨论】:

    • 它表示该问题已在 3.0.0.4 发布公告中得到解决,但截至今天,我仍然遇到该版本和最新 GitHub 源的此问题。
    猜你喜欢
    • 2013-05-24
    • 2013-07-22
    • 1970-01-01
    • 2019-01-10
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多