SQLite-net TableQuery<T>.Select() 性能不佳

SQLite-net TableQuery<T>.Select() poor performance

考虑下面的完整代码块,特别是这部分 - 对象初始化(你会这样称呼它吗?):

new LocalFileInfo() {
  IsFavorite = p.IsFavorite,
  ...
  WhenCrawled = p.WhenCrawled
}

有没有办法将这段代码抽象成某种东西,比如我可以重用的方法,而不是将对象初始化代码复制粘贴到每个查询中?我的强烈偏好是性能最好的代码,而不是最容易维护的代码(但显然易于维护是可取的)。

public static List<LocalFileInfo> RecentlyCrawledFiles(int take)
{
    if (take < 1) take = 1;

    List<LocalFileInfo> list = new List<LocalFileInfo>();

        using (SQLite.Net.SQLiteConnection conn = new SQLite.Net.SQLiteConnection(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), sqliteDb))
        {
            // works but is slower.
            // list = (from p in conn.Table<LocalFileInfo>() select p).OrderBy(f => f.WhenCrawled).Take(take).ToList();

            list = (from p in conn.Table<LocalFileInfo>() select new LocalFileInfo() {
                IsFavorite = p.IsFavorite,
                LastModified = p.LastModified,
                Name = p.Name,
                ParentFolder = p.ParentFolder,
                Path = p.Path,
                Size = p.Size,
                SourceId = p.SourceId,
                SourceName = p.SourceName,
                SourceType = p.SourceType,
                WhenCrawled = p.WhenCrawled
            })
            .OrderByDescending(f => f.WhenCrawled)
            .Take(take)
            .ToList();
        };

    return list;
}

而不是

list = (from p in conn.Table<LocalFileInfo>() select new LocalFileInfo() {
            IsFavorite = p.IsFavorite,
            LastModified = p.LastModified,
            Name = p.Name,
            ParentFolder = p.ParentFolder,
            Path = p.Path,
            Size = p.Size,
            SourceId = p.SourceId,
            SourceName = p.SourceName,
            SourceType = p.SourceType,
            WhenCrawled = p.WhenCrawled
        })

怎么样:

list = conn.Table<LocalFileInfo>()

只要您将所有属性投影到相同的对象类型中,它就可以工作。如果其中任何一个都不是真的,那么你实际上可以像这样抽象出来:

public static class FromObjectExtensions
{
  public static IEnumerable<ToObject> ToToObject(this IEnumerable<FromObject> q)
  {
    return q.Select(t=>new ToObject
    {
       Property1=t.Property1,
       ...
    };
  }
}

那么你可以这样称呼它:

list = conn.Table<LocalFileInfo>().ToToObject();

只需将 ToObjectFromObject 替换为您的实际对象类型,然后填写要复制的属性即可。

这会很有效。

     using (SQLite.Net.SQLiteConnection conn = new SQLite.Net.SQLiteConnection(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), sqliteDb))
        {
  var list = conn.Table<LocalFileInfo>().OrderByDesending(f=>f.WhenCrawled).Take(take).ToList();
        };

尽管您的查询有效,但您重新选择所选项目毫无意义,特别是:

 list = (from p in conn.Table<LocalFileInfo>() select new LocalFileInfo() {
                IsFavorite = p.IsFavorite,
                LastModified = p.LastModified,
                Name = p.Name,
                ParentFolder = p.ParentFolder,
                Path = p.Path,
                Size = p.Size,
                SourceId = p.SourceId,
                SourceName = p.SourceName,
                SourceType = p.SourceType,
                WhenCrawled = p.WhenCrawled
            })

由于这是 Linq,您首先要收集对象,然后重新创建已声明对象的新实例。这毫无意义,而且效率极低。

您需要为选择器定义 Expression

public static Expression<Func<LocalFileInfo, LocalFileInfo>> MyLocalFileInfoSelector = 
    p => new LocalFileInfo() {
            IsFavorite = p.IsFavorite,
            LastModified = p.LastModified,
            Name = p.Name,
            ParentFolder = p.ParentFolder,
            Path = p.Path,
            Size = p.Size,
            SourceId = p.SourceId,
            SourceName = p.SourceName,
            SourceType = p.SourceType,
            WhenCrawled = p.WhenCrawled
        };

然后您可以在任何地方使用它:

return conn.Table<LocalFileInfo>()
    .Select(MyLocalFileInfoSelector)
    .OrderBy(f => f.WhenCrawled)
    .Take(take)
    .ToList();

我看过SQLite-net

中master分支的源码

该库似乎忽略了您的预测,所以我认为对它们进行表达不会有任何帮助

参见 SqlLite.cs 中的示例 TableQuery<T> class

    public IEnumerator<T> GetEnumerator ()
    {
        if (!_deferred)
            return GenerateCommand("*").ExecuteQuery<T>().GetEnumerator();

        return GenerateCommand("*").ExecuteDeferredQuery<T>().GetEnumerator();
    }

每次ToList()foreach (var item in query)时都会用到这个GetEnumerator()方法。

GenerateCommand()会构建SQL,它"theoretically"支持一个字符串参数selectList,但是库永远不会使用这个参数

此外,当您执行 Select(Expression) 时,库会存储一个 _selector 私有 属性,但绝不会在对 GenerateCommand() 的调用或任何其他调用中使用它

This issue has been reported

所以,我认为关于性能的最佳选择是:

var list = conn.Table<LocalFileInfo>()
    .OrderByDescending(f => f.WhenCrawled)
    .Take(take)
    .ToList();

在此之后您可以执行 Select<T>(),但是库已经加载了整个实体列表以及每个实体列表 属性。不过,如果您保留这些实体,事后选择可能会帮助您进行 GC(也就是说,ToList() 之后的投影不会给您带来任何立竿见影的收益)。

或使用连接对象中的 Query<T>()...这将带您回到 SQL 大陆。

或者,等待 Entity Framework Core to support Xamarin

Roadmap 中提到:

Xamarin works in some scenarios but has not been fully tested as a supported scenario.

抱歉,这是我目前能想到的最好的了:(

编辑: 事实上,我相信如果你做 Select(p => p.IsFavorite) 库会失败,因为它无法映射你的投影(不是抨击库,请注意)