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();
只需将 ToObject
和 FromObject
替换为您的实际对象类型,然后填写要复制的属性即可。
这会很有效。
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()
的调用或任何其他调用中使用它
所以,我认为关于性能的最佳选择是:
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)
库会失败,因为它无法映射你的投影(不是抨击库,请注意)
考虑下面的完整代码块,特别是这部分 - 对象初始化(你会这样称呼它吗?):
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();
只需将 ToObject
和 FromObject
替换为您的实际对象类型,然后填写要复制的属性即可。
这会很有效。
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()
的调用或任何其他调用中使用它
所以,我认为关于性能的最佳选择是:
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)
库会失败,因为它无法映射你的投影(不是抨击库,请注意)