如何在Xamarin.Forms中代表SQLite实体的类上安全地重命名属性?

时间:2017-02-18 13:50:44

标签: c# sqlite xamarin xamarin.forms sqlite-net

我有一个类Foo,其属性ID如此

public class Foo
{
   [PrimaryKey]
   public int Id {get; set;}
}

但我想将Id重命名为FooId。当我这样做并重新安装我的Xamarin应用程序作为更新时,我在插入时收到以下错误:

table Foo has no column named FooId

显然,表定义正在发生变化,我错过了对sqlite db的一些显式命令以帮助完成该过程。

值得注意的是,如果删除应用并重新安装它,它可以正常工作。

使用从nuget安装的SQLite-net PCL 1.1.1版。

2 个答案:

答案 0 :(得分:3)

首先,我建议使用SQLite-net-pcl而不是SQLite.Net-PCL(注意SQLite和Net之间的差距),因为Android 6.0+上的issues从未修复过(目前为止)至少我知道这会阻止你的应用在Android 6.0+上运行。 API几乎完全相同,但切换后您可以使用更多API。

要解决列/模型属性重命名的问题以及在数据已经在表中之后添加列/模型属性的相同问题,我尝试检测异常。如果检测到,代码将删除表,重新创建它,并重新插入所有数据。

我使用通用存储库,因此它更容易,但下面的代码应该给你一个想法。此外,SQLite.Net-PCL没有DropTableAsync() API,因此如果您不更改库,则必须使用ExecuteAsync()

public static async Task<string> UpdateTableSchemaAsync<T>() where T : class, new() {
    IGenericRepository<T> genericRepo = new GenericRepository<T>();

    try {
        List<T> savedObjects = await genericRepo.AllAsync(); //Collect all current records before dropping table

        if(savedObjects != null && savedObjects.Count > 0) {
            await genericRepo.DropTableAsync();
            await genericRepo.CreateTableAsync();

            // You could do some data transformation here if necessary, such as transferring all Foo.FooId values to the Foo.Id column

            await genericRepo.InsertAllAsync(savedObjects); //Insert all objects back
        } else {
            await genericRepo.DropTableAsync();
            await genericRepo.CreateTableAsync();
        }

        return null;
    } catch(Exception ex) {
        Debug.WriteLine("\nIn ModelHelpers.UpdateTableSchemaAsync() - Exception attempting to recreate " + typeof(T).Name + " data:\n" + ex.GetBaseException() + "\n");
        return "An error occurred while attempting to update the " + typeof(T).Name + " data.";
    }
}

要使用上述内容,我通过Func方法传递所有插入或更新操作,将Func置于try / catch内以检测SQLiteException 1}}并在Exception.Message中查找一词(是的,我知道这是蹩脚的),如果检测到这一点,我们运行UpdateTableSchemaAsync()方法并尝试执行传入的是再次插入或更新Func

//Again this method is within a generic repository so T is defined through the generic repo instantiation
public async Task<TResult> ExecuteSafeAsync<TResult>(Func<Task<TResult>> taskFunc, [CallerMemberName] string caller = "") {
    try {
        return await taskFunc.Invoke();
    } catch(SQLiteException sqlException) {
        System.Diagnostics.Debug.WriteLine("\nIn GenericRepository.ExecuteSafeAsync() via " + caller + " - Exception attempting to run query on " + typeof(T).Name + "\n" + sqlException.GetBaseException() + "\n");

        if(sqlException.Message.ToLowerInvariant().Contains("column")) {    //The error being searched for is "no such column: <column name>"
            try {
                await ModelHelpers.UpdateTableSchemaAsync<T>();
            } catch(Exception ex) { //If this fails for what ever reason, we do not want the ModelHelpers.UpdateTableSchemaAsync() method's exception being the one that gets detected upstream
                System.Diagnostics.Debug.WriteLine("\nIn GenericRepository.ExecuteSafeAsync() via " + caller + " - Exception attempting to run rebuild " + typeof(T).Name + " table after detecting SQLite Exception\n" + ex.GetBaseException() + "\n");
            }

            return await taskFunc.Invoke(); //Now that the table has been recreated lets try to run it again
        }

        throw;
    }
}

答案 1 :(得分:2)

您可以尝试使用[Column()] Annotation

public class Foo
{
   [Column("Id")]
   [PrimaryKey]
   public int FooId {get; set;}
}

index

https://msdn.microsoft.com/en-us/library/jj591583(v=vs.113).aspx