在 C# 中处理 SQLite - 将传递字符串操作作为命令推进

Handling SQLite in C# - advancing pass string manipulations as commands

我在我的应用程序中使用 SQLite(通过 System.Data.SQLite 包)。现在所有的插入、查询和其他操作都是通过使用字符串发送命令来完成的,例如:

        SQLiteCommand command = new SQLiteCommand(comStr, db);

其中 comStr - 是保存命令的字符串变量。

我可以使用其他选项来代替字符串吗?或者字符串是处理来自 .NET 的 SQL 查询时应该使用的正确方法吗?

问题是使用字符串会变得相当混乱,例如我有一些用户可以设置的过滤器。使用字符串操作命令 - 虽然有效 - 对我来说感觉非常脆弱:

    public string GetFilterString()
    {
        string fil1 = "";
        string fil2 = "";
        string fil3 = "";
        string fil4 = "";

        // filter by time
        switch (WithinTimeBtnStatus)
        {
            case WithinTime.All:
                break;
            case WithinTime.Hour:
                string minusHour = (DateTime.Now - new TimeSpan(0, 1, 0, 0)).ToString("yyyy-MM-dd HH:mm:ss.fff");
                fil1 = $" timestamp >= datetime('{minusHour}')";
                break;
            case WithinTime.Day:
                string minusDay = (DateTime.Now - new TimeSpan(1, 0, 0, 0)).ToString("yyyy-MM-dd HH:mm:ss.fff");
                fil1 = $" timestamp >= datetime('{minusDay}')";
                break;
            case WithinTime.Week:
                string minusWeek = (DateTime.Now - new TimeSpan(7, 0, 0, 0)).ToString("yyyy-MM-dd HH:mm:ss.fff");
                fil1 = $" timestamp >= datetime('{minusWeek}')";
                break;
        }

        // filter by extension
        for (int i = 0; i < FilteredExt.Count; i++)
        {
            fil2 += " ext != '" + FilteredExt[i] + "'";
            if (i < FilteredExt.Count - 1)
                fil2 += " and";
        }

        // filter by process
        if (_processFilterSelected.ToLower() != "all" && _processFilterSelected != "")
        {
            fil3 = $" proc == '{_processFilterSelected}'";
        }

        // filter by File Operation
        if (_FileOperationFilterSelected.ToLower() != "all" && _FileOperationFilterSelected != "")
        {
            FileOperation fo = Converters.StringToFileOperation(_FileOperationFilterSelected);
            switch (fo)
            {
                case FileOperation.Deleted:
                    fil4 = " oper == 'DELETED'";
                    break;
                case FileOperation.Renamed:
                    fil4 = " oper == 'RENAMED'";
                    break;
                case FileOperation.Modified:
                    fil4 = " oper == 'MODIFIED'";
                    break;
            }
        }


        string fil = "";
        var tmp = new[] { fil1, fil2, fil3, fil4 };
        foreach (var t in tmp)
        {
            if (t != "")
            {
                fil += " and" + t;
            }
        }

        return fil;
    }

编辑以提供一些解决方案作为答案。

本教程向您展示如何正确实施 SQLite 并使用 Linq 扩展与您的数据库 table 交互。我已经复制了下面的相关部分。打开数据库连接并首先创建数据 tables 后,您就可以与 table 交互,就像使用 Linq 与任何 IEnumerable 交互一样。它还提供了将 SQL 作为字符串传递的选项,但是由于这在编译时未检查,您 运行 有 运行 时间错误的风险。

https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/databases/

TodoItemDatabase 构造函数如下所示:

public TodoItemDatabase(string dbPath)
{
  database = new SQLiteAsyncConnection(dbPath);
  database.CreateTableAsync<TodoItem>().Wait();
}

此方法创建一个单一的数据库连接,该连接在应用程序 运行 时保持打开状态,因此避免了每次执行数据库操作时打开和关闭数据库文件的开销。 TodoItemDatabase 的其余部分 class 包含 SQLite 查询 运行 跨平台。示例查询代码如下所示(有关语法的更多详细信息,请参阅使用 SQLite.NET 一文):

public Task<List<TodoItem>> GetItemsAsync()
{
  return database.Table<TodoItem>().ToListAsync();
}

public Task<List<TodoItem>> GetItemsNotDoneAsync()
{
  return database.QueryAsync<TodoItem>("SELECT * FROM [TodoItem] WHERE [Done] = 0");
}

public Task<TodoItem> GetItemAsync(int id)
{
  return database.Table<TodoItem>().Where(i => i.ID == id).FirstOrDefaultAsync();
}

public Task<int> SaveItemAsync(TodoItem item)
{
  if (item.ID != 0)
  {
    return database.UpdateAsync(item);
  }
  else {
    return database.InsertAsync(item);
  }
}

public Task<int> DeleteItemAsync(TodoItem item)
{
  return database.DeleteAsync(item);
}

既然我没有得到满意的答复,我会post我最后做的。我认为这可能是一种不错的方法,但可能还有其他更好的方法来实现我正在寻找的东西(在我的数据库上使用 LINQ 类型语法,而不是使用包含查询的字符串)。

此外 - 我不确定这是否比使用查询字符串更快。

TL;DR:使用 SQLite.CodeFirst + EntityFramework。

(附带说明,可能可以使用 LINQ 来 SQL 而不是 EntityFramework,但不确定是否也在 CodeFirst 方法中。一旦我尝试并测试了这个就会更新).


首先,您需要添加这些包:

  • Entity Framework
  • System.Data.SQLite(可能还会安装:)
  • System.Data.SQLite.核心
  • System.Data.SQLite.EF6
  • System.Data.SQLite.Linq

最后,如果您是从代码开始的(就像我一样),您还需要

  • SQLite.CodeFirst

接下来要做的是在 app.config 中设置连接字符串。

(附带说明 - 指定提供程序似乎存在大量错误,卸载并重新安装上面的包似乎可以修复。我不太确定删除和添加以及不变性背后的逻辑名字 - 如果你喜欢我写的东西,那就是:

<system.data>
   <DbProviderFactories>
      <remove invariant="System.Data.SQLite.EF6" />
      <add name="SQLite Data Provider (Entity Framework 6)" invariant="System.Data.SQLite.EF6" description=".NET Framework Data Provider for SQLite (Entity Framework 6)" type="System.Data.SQLite.EF6.SQLiteProviderFactory, System.Data.SQLite.EF6" />
      <remove invariant="System.Data.SQLite" />
      <add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
   </DbProviderFactories>
</system.data>

<entityFramework>
  <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
      <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
      <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6"/>
     </providers>
  </entityFramework>

)

连接字符串应指定名称,并且至少应指定数据库文件所在位置的路径。您还可以使用稍后在代码中定义的相对路径(通过使用 |DataDirectory| 语法):

<connectionStrings>
  <add name="YourModel" connectionString="Data Source=|DataDirectory|\NameOfYourDBFile.sqlite" providerName="System.Data.SQLite" />
</connectionStrings>

下一步,如果您正在执行代码优先,则创建一个新的 class 作为您的模型,这基本上是使用 SQLite.CodeFirst 包:

class YourModel : DbContext
{
    // Your context has been configured to use a 'YourModel' connection string from your application's 
    // configuration file (App.config or Web.config). By default, this connection string targets the 
    // 'YourProject.YourModel' database on your LocalDb instance. 
    // 
    // If you wish to target a different database and/or database provider, modify the 'YourModel' 
    // connection string in the application configuration file.
    public YourModel()
        : base("name=YourModel")
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        var sqliteConnectionInitializer = new SqliteCreateDatabaseIfNotExists<YourModel>(modelBuilder);
        Database.SetInitializer(sqliteConnectionInitializer);
        Database.SetInitializer(new SqliteDropCreateDatabaseWhenModelChanges<YourModel>(modelBuilder));
    }

    // Add a DbSet for each entity type that you want to include in your model. For more information 
    // on configuring and using a Code First model, see http://go.microsoft.com/fwlink/?LinkId=390109.

    public virtual DbSet<YourTableClass> YourTable { get; set; }
}

[Table("YourTable")]
public class YourTableClass
{
    [Key]
    public string Id { get; set; }
    [Required]
    public FileOperation Oper { get; set; }
    [Required, Index]
    public OperationState State { get; set; }
    [Index]
    public string Proc { get; set; }
    [Required]
    public string Src { get; set; }
    public DateTime Timestamp { get; set; }
    // etc.
}

}

您可以阅读更多相关信息 here

准备工作基本上就是这样。

(附带说明,如果你想从代码后面更改数据库文件的相对路径,你需要写:

AppDomain.CurrentDomain.SetData("DataDirectory", @"the\path\you\desire");

)

现在您可以直接使用它了。基本语法非常简单,你只需使用:

using (var context = new YourModel())
{
     // some query
}

所以Select:

using (var context = new YourModel())
{
     var t = context.YourTable
       .Where(e => e.State == OperationState.BackedUp)
       .Select(e => e.Proc)
       .Distinct()
 }

如果你想插入:

using (var context = new YourModel())
{
    var e = context.YourTable.Create();
    e.Id = guid;
    // ...etc
    e.Timestamp = timestamp;
    context.YourTable.Add(e);
    context.SaveChanges();
}

如果您仍想使用字符串查询:

using (var context = new YourModel())
{
    context.Database.ExecuteSqlCommand(comString);
}

需要记住的一些重要事项:

  • 如果您在数据库中更改某些内容,则最后必须调用 Context.SaveChange()(ExecuteSqlCommand 不需要这个)
  • 可以使用 RemoveRange + SaveChange() 或仍然使用查询字符串来完成删除。

因此问题 GetFilterString 中的示例更改为:

    public static IQueryable<YourTable> GetFilteredQueryable(IQueryable<YourTable> yourTable)
    {
        // filter by time
        switch (RestoreLogic.WithinTimeBtnStatus)
        {
            case WithinTime.All:
                break;
            case WithinTime.Hour:
                DateTime offsetHour = DateTime.Now.Add(new TimeSpan(-1, 0, 0));
                yourTable = yourTable.Where(e => e.Timestamp >= offsetHour);
                break;
           // etc.
        }

        // filter by extension
        foreach (var i in FilteredExt)
        {
            yourTable = yourTable.Where(e => e.Ext != i);
        }

        // etc.

        return yourTable;
    }