WIP: database增加读取功能
单元测试没过
This commit is contained in:
+216
-5
@@ -6,6 +6,10 @@ namespace OCES.Resonance.Core;
|
||||
|
||||
public static class Database
|
||||
{
|
||||
static Database()
|
||||
{
|
||||
DefaultTypeMap.MatchNamesWithUnderscores = true;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取数据库连接
|
||||
/// </summary>
|
||||
@@ -107,11 +111,11 @@ public static class Database
|
||||
waveform BLOB
|
||||
);
|
||||
|
||||
CREATE INDEX idx_file_name on audio_files(filename);
|
||||
CREATE INDEX idx_md5 on audio_files(md5);
|
||||
CREATE INDEX idx_path on audio_files(path);
|
||||
CREATE INDEX idx_unique_id on audio_files(unique_id);
|
||||
CREATE INDEX idx_description on audio_files(description);
|
||||
CREATE INDEX if not exists idx_file_name on audio_files(filename);
|
||||
CREATE INDEX if not exists idx_md5 on audio_files(md5);
|
||||
CREATE INDEX if not exists idx_path on audio_files(path);
|
||||
CREATE INDEX if not exists idx_unique_id on audio_files(unique_id);
|
||||
CREATE INDEX if not exists idx_description on audio_files(description);
|
||||
|
||||
""";
|
||||
|
||||
@@ -253,4 +257,211 @@ public static class Database
|
||||
var count = connection.ExecuteScalar<int>(sql, new { Md5 = md5, Path = filePath });
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
#region 读取
|
||||
|
||||
// 详情查询公共列(含 BLOB),用于 GetEntryById / GetEntryByUniqueId
|
||||
private const string DetailSelectColumns = @"
|
||||
id, unique_id, short_id, md5, path, filename, folder, directory,
|
||||
duration, bit_depth, channels, sample_rate, type,
|
||||
date_added, original_modification_date AS last_write_time, origination_time AS creation_time,
|
||||
bpm, frame_rate, timecode, description, category, subcategory,
|
||||
cat_id, category_full, genre, style, mood, keywords, rating,
|
||||
artist, composer, designer, recordist, publisher, manufacturer,
|
||||
originator AS bwf_originator, originator_ref AS bwf_originator_ref, project_name, library, cd_title,
|
||||
track_title, episode, scene, take, tape, cue_number, sync_point,
|
||||
release_date, track_year, is_edited, is_split, location, [group],
|
||||
markers, comments, notes, copyright, coding_history AS bwf_coding_history, microphone,
|
||||
mic_perspective,
|
||||
fx_name, channel_layout, disk_number, track_number,
|
||||
artwork, waveform, bwf_umid,
|
||||
user1, user2, user3, user4, user5, user6, user7, user8";
|
||||
|
||||
// 列表查询公共列(排除 BLOB:artwork / waveform / bwf_umid),减少数据传输
|
||||
private const string ListSelectColumns = @"
|
||||
id, unique_id, short_id, md5, path, filename, folder, directory,
|
||||
duration, bit_depth, channels, sample_rate, type,
|
||||
date_added, original_modification_date AS last_write_time, origination_time AS creation_time,
|
||||
bpm, frame_rate, timecode, description, category, subcategory,
|
||||
cat_id, category_full, genre, style, mood, keywords, rating,
|
||||
artist, composer, designer, recordist, publisher, manufacturer,
|
||||
originator AS bwf_originator, originator_ref AS bwf_originator_ref, project_name, library, cd_title,
|
||||
track_title, episode, scene, take, tape, cue_number, sync_point,
|
||||
release_date, track_year, is_edited, is_split, location, [group],
|
||||
markers, comments, notes, copyright, coding_history AS bwf_coding_history, microphone,
|
||||
mic_perspective,
|
||||
fx_name, channel_layout, disk_number, track_number,
|
||||
user1, user2, user3, user4, user5, user6, user7, user8";
|
||||
|
||||
// 排序白名单,防止 SQL 注入
|
||||
private static readonly HashSet<string> AllowedSortColumns = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"filename", "duration", "sample_rate", "channels",
|
||||
"date_added", "rating", "type", "category", "id"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 按主键取回单条记录(含 BLOB 字段:artwork / waveform / bwf_umid)
|
||||
/// </summary>
|
||||
public static AudioFileMeta? GetEntryById(int id)
|
||||
{
|
||||
using var connection = GetConnection();
|
||||
connection.Open();
|
||||
|
||||
const string sql = $"SELECT {DetailSelectColumns} FROM audio_files WHERE id = @Id;";
|
||||
|
||||
return connection.QueryFirstOrDefault<AudioFileMeta>(sql, new { Id = id });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按全局唯一标识符取回单条记录(含 BLOB 字段)
|
||||
/// </summary>
|
||||
public static AudioFileMeta? GetEntryByUniqueId(string uniqueId)
|
||||
{
|
||||
using var connection = GetConnection();
|
||||
connection.Open();
|
||||
|
||||
const string sql = $"SELECT {DetailSelectColumns} FROM audio_files WHERE unique_id = @UniqueId;";
|
||||
|
||||
return connection.QueryFirstOrDefault<AudioFileMeta>(sql, new { UniqueId = uniqueId });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多条件过滤查询(排除 BLOB 字段),支持分页和排序
|
||||
/// </summary>
|
||||
public static IEnumerable<AudioFileMeta> QueryEntries(
|
||||
string? searchText = null,
|
||||
double? minDuration = null,
|
||||
double? maxDuration = null,
|
||||
int? sampleRate = null,
|
||||
int? channels = null,
|
||||
string? category = null,
|
||||
string? sortBy = null,
|
||||
bool sortDescending = false,
|
||||
int limit = 100,
|
||||
int offset = 0)
|
||||
{
|
||||
using var connection = GetConnection();
|
||||
connection.Open();
|
||||
|
||||
var where = new List<string>();
|
||||
var parameters = new DynamicParameters();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
where.Add("(filename LIKE @SearchText OR description LIKE @SearchText OR keywords LIKE @SearchText OR fx_name LIKE @SearchText)");
|
||||
parameters.Add("@SearchText", $"%{searchText}%");
|
||||
}
|
||||
if (minDuration.HasValue)
|
||||
{
|
||||
where.Add("duration >= @MinDuration");
|
||||
parameters.Add("@MinDuration", minDuration.Value);
|
||||
}
|
||||
if (maxDuration.HasValue)
|
||||
{
|
||||
where.Add("duration <= @MaxDuration");
|
||||
parameters.Add("@MaxDuration", maxDuration.Value);
|
||||
}
|
||||
if (sampleRate.HasValue)
|
||||
{
|
||||
where.Add("sample_rate = @SampleRate");
|
||||
parameters.Add("@SampleRate", sampleRate.Value);
|
||||
}
|
||||
if (channels.HasValue)
|
||||
{
|
||||
where.Add("channels = @Channels");
|
||||
parameters.Add("@Channels", channels.Value);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(category))
|
||||
{
|
||||
where.Add("category = @Category");
|
||||
parameters.Add("@Category", category);
|
||||
}
|
||||
|
||||
var whereClause = where.Count > 0 ? "WHERE " + string.Join(" AND ", where) : "";
|
||||
|
||||
var orderBy = "ORDER BY date_added DESC";
|
||||
if (!string.IsNullOrWhiteSpace(sortBy) && AllowedSortColumns.Contains(sortBy))
|
||||
{
|
||||
var dir = sortDescending ? "DESC" : "ASC";
|
||||
orderBy = $"ORDER BY [{sortBy}] {dir}";
|
||||
}
|
||||
|
||||
var sql = $"""
|
||||
SELECT {ListSelectColumns}
|
||||
FROM audio_files
|
||||
{whereClause}
|
||||
{orderBy}
|
||||
LIMIT @Limit OFFSET @Offset;
|
||||
""";
|
||||
|
||||
parameters.Add("@Limit", limit);
|
||||
parameters.Add("@Offset", offset);
|
||||
|
||||
return connection.Query<AudioFileMeta>(sql, parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文本搜索(filename / description / keywords / fx_name),排除 BLOB 字段
|
||||
/// </summary>
|
||||
public static IEnumerable<AudioFileMeta> SearchEntries(string query, int limit = 100, int offset = 0)
|
||||
{
|
||||
return QueryEntries(searchText: query, limit: limit, offset: offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回匹配条件的总记录数(用于分页控件)
|
||||
/// </summary>
|
||||
public static int GetTotalCount(
|
||||
string? searchText = null,
|
||||
double? minDuration = null,
|
||||
double? maxDuration = null,
|
||||
int? sampleRate = null,
|
||||
int? channels = null,
|
||||
string? category = null)
|
||||
{
|
||||
using var connection = GetConnection();
|
||||
connection.Open();
|
||||
|
||||
var where = new List<string>();
|
||||
var parameters = new DynamicParameters();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
where.Add("(filename LIKE @SearchText OR description LIKE @SearchText OR keywords LIKE @SearchText OR fx_name LIKE @SearchText)");
|
||||
parameters.Add("@SearchText", $"%{searchText}%");
|
||||
}
|
||||
if (minDuration.HasValue)
|
||||
{
|
||||
where.Add("duration >= @MinDuration");
|
||||
parameters.Add("@MinDuration", minDuration.Value);
|
||||
}
|
||||
if (maxDuration.HasValue)
|
||||
{
|
||||
where.Add("duration <= @MaxDuration");
|
||||
parameters.Add("@MaxDuration", maxDuration.Value);
|
||||
}
|
||||
if (sampleRate.HasValue)
|
||||
{
|
||||
where.Add("sample_rate = @SampleRate");
|
||||
parameters.Add("@SampleRate", sampleRate.Value);
|
||||
}
|
||||
if (channels.HasValue)
|
||||
{
|
||||
where.Add("channels = @Channels");
|
||||
parameters.Add("@Channels", channels.Value);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(category))
|
||||
{
|
||||
where.Add("category = @Category");
|
||||
parameters.Add("@Category", category);
|
||||
}
|
||||
|
||||
var whereClause = where.Count > 0 ? "WHERE " + string.Join(" AND ", where) : "";
|
||||
var sql = $"SELECT COUNT(*) FROM audio_files {whereClause};";
|
||||
|
||||
return connection.ExecuteScalar<int>(sql, parameters);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user