SQLite:从列表中的对象中检索子元素

SQLite: Retrieve child element from object in list

有一个Basket,存储了一个List<Fruit>。每个 Fruit 有一个 Pip。如果我存储此关系并稍后检索它,ForeignKey PipId 有一个值,但对象 Pipnull,尽管我使用 CascadeRead.

如果我尝试在 FruitList 上使用 CascadeOperation.All,我会得到 Constraint

  at SQLite.PreparedSqlLiteInsertCommand.ExecuteNonQuery (System.Object[] source) [0x00116] in /Users/fak/Dropbox/Projects/sqlite-net/src/SQLite.cs:2507 
  at SQLite.SQLiteConnection.Insert (System.Object obj, System.String extra, System.Type objType) [0x0014b] in /Users/fak/Dropbox/Projects/sqlite-net/src/SQLite.cs:1386 
  at SQLite.SQLiteConnection.Insert (System.Object obj) [0x00008] in /Users/fak/Dropbox/Projects/sqlite-net/src/SQLite.cs:1224 
  at SQLiteNetExtensions.Extensions.WriteOperations.InsertElement (SQLite.SQLiteConnection conn, System.Object element, System.Boolean replace, System.Reflection.PropertyInfo primaryKeyProperty, System.Boolean isAutoIncrementPrimaryKey, System.Collections.Generic.ISet`1[T] objectCache) [0x0005a] in C:\home\mk\work\frameworks\sqlite-net-extensions\SQLiteNetExtensions\Extensions\WriteOperations.cs:270 
  at SQLiteNetExtensions.Extensions.WriteOperations.InsertElements (SQLite.SQLiteConnection conn, System.Collections.IEnumerable elements, System.Boolean replace, System.Collections.Generic.ISet`1[T] objectCache) [0x00069] in C:\home\mk\work\frameworks\sqlite-net-extensions\SQLiteNetExtensions\Extensions\WriteOperations.cs:238 
  at SQLiteNetExtensions.Extensions.WriteOperations.InsertValue (SQLite.SQLiteConnection conn, System.Object value, System.Boolean replace, System.Boolean recursive, System.Collections.Generic.ISet`1[T] objectCache) [0x0002c] in C:\home\mk\work\frameworks\sqlite-net-extensions\SQLiteNetExtensions\Extensions\WriteOperations.cs:219 
  at SQLiteNetExtensions.Extensions.WriteOperations.InsertChildrenRecursive (SQLite.SQLiteConnection conn, System.Object element, System.Boolean replace, System.Boolean recursive, System.Collections.Generic.ISet`1[T] objectCache) [0x0004c] in C:\home\mk\work\frameworks\sqlite-net-extensions\SQLiteNetExtensions\Extensions\WriteOperations.cs:200 
  at SQLiteNetExtensions.Extensions.WriteOperations.InsertWithChildrenRecursive (SQLite.SQLiteConnection conn, System.Object element, System.Boolean replace, System.Boolean recursive, System.Collections.Generic.ISet`1[T] objectCache) [0x0002b] in C:\home\mk\work\frameworks\sqlite-net-extensions\SQLiteNetExtensions\Extensions\WriteOperations.cs:181 
  at SQLiteNetExtensions.Extensions.WriteOperations.InsertWithChildren (SQLite.SQLiteConnection conn, System.Object element, System.Boolean recursive) [0x00000] in C:\home\mk\work\frameworks\sqlite-net-extensions\SQLiteNetExtensions\Extensions\WriteOperations.cs:59 
  at SQLiteNetExtensionsAsync.Extensions.WriteOperations+<>c__DisplayClass1_0.<InsertWithChildrenAsync>b__0 () [0x00013] in C:\home\mk\work\frameworks\sqlite-net-extensions\SQLiteNetExtensionsAsync-PCL\Extensions\WriteOperations.cs:55 
  at System.Threading.Tasks.Task.InnerInvoke () [0x0000f] in <d18287e1d683419a8ec3216fd78947b9>:0 
  at System.Threading.Tasks.Task.Execute () [0x00010] in <d18287e1d683419a8ec3216fd78947b9>:0 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in <d18287e1d683419a8ec3216fd78947b9>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0003e] in <d18287e1d683419a8ec3216fd78947b9>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <d18287e1d683419a8ec3216fd78947b9>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <d18287e1d683419a8ec3216fd78947b9>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.GetResult () [0x00000] in <d18287e1d683419a8ec3216fd78947b9>:0 
  at TestSQLite.Database+<StoreBasketAsync>d__5.MoveNext () [0x0021b] in C:\Users\some-user\Documents\Visual Studio 2015\Projects\TestSQLite\TestSQLite\TestSQLite\Database.cs:189 

此外,我尝试在InsertWithChildrenAsync()上使用recursive: true,但Pip也是null。这是示例:

数据模型

public class Basket
{
    private string number;
    private List<Fruit> fruitList;

    [PrimaryKey]
    public string Number
    {
        get { return this.number; }
        set { this.number = value; }
    }

    public string Name { get; set; }

    [OneToMany(CascadeOperations = CascadeOperation.CascadeRead)]
    public List<Fruit> FruitList
    {
        get { return this.fruitList; }
        set { this.fruitList = value; }
    }

    public Basket()
    {

    }
}

public class Fruit
{
    private string number;
    private Pip pip;

    [PrimaryKey]
    public string Number
    {
        get { return this.number; }
        set { this.number = value; }
    }

    public string Type { get; set;}

    [ForeignKey(typeof(Pip))]
    public string PipId { get; set; }

    [OneToOne]
    public Pip Pip
    {
        get { return this.pip; }
        set { this.pip = value; }
    }

    [ForeignKey(typeof(Basket))]
    public string BasketId { get; set; }

    public Fruit()
    {  
    }

}

public class Pip
{

    private string number;
    private string title;

    [PrimaryKey]
    public string Number
    {
        get { return this.number; }
        set { this.number = value; }
    }

    public string Title
    {
        get { return this.title; }
        set { this.title = value; }
    }

    public Pip()
    {

    }
}

数据库操作

public class Database
{
    private readonly SQLiteAsyncConnection database;

    public Database(string databasePath)
    {
        this.database = new SQLiteAsyncConnection(databasePath);
        this.database.CreateTableAsync<Basket>().Wait();
        this.database.CreateTableAsync<Fruit>().Wait();
        this.database.CreateTableAsync<Pip>().Wait();
    }

    public async Task<Basket> GetBasketAsync(string basketId)
    {
        try
        {
            var queryResult = await this.database.Table<Basket>().Where(b => b.Number == basketId).CountAsync();
            if (queryResult > 0)
            {
                return await this.database.GetWithChildrenAsync<Basket>(basketId, true);
            }
            else
            {
                return null;
            }
        }
        catch(Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
            return null;
        }
    }

    public async Task<Fruit> GetFruitAsync(string number)
    {
        try
        {
            var queryResult = await this.database.Table<Fruit>().Where(f => f.Number == number).CountAsync();
            if (queryResult > 0)
            {
                return await this.database.GetWithChildrenAsync<Fruit>(number, true);
            }
            else
            {
                return null;
            }
        }
        catch(Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
            return null;
        }
    }

    public async Task<Pip> GetPipAsync(string number)
    {
        try
        {
            var queryResult = await this.database.Table<Pip>().Where(p => p.Number == number).CountAsync();
            if (queryResult > 0)
            {
                return await this.database.GetAsync<Pip>(number);
            }
            else
            {
                return null;
            }
        }
        catch(Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
            return null;
        }
    }

    public async Task StoreBasketAsync(Basket basket)
    {
        if (basket == null)
            return;

        try
        {
            await this.StoreFruitListAsync(basket.FruitList);

            var foundItem = await this.GetBasketAsync(basket.Number);
            if (foundItem != null)
            {
                await this.database.UpdateWithChildrenAsync(basket);
            }
            else
            {
                await this.database.InsertWithChildrenAsync(basket);
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
        }
    }

    public async Task StoreFruitListAsync(List<Fruit> fruitList)
    {
        if (fruitList == null || fruitList.Count == 0)
            return;

        try
        {
            foreach (Fruit fruit in fruitList)
            {
                await this.StorePipAsync(fruit.Pip);

                var foundItem = await this.GetFruitAsync(fruit.Number);
                if (foundItem != null)
                {
                    await this.database.UpdateWithChildrenAsync(fruit);
                }
                else
                {
                    await this.database.InsertWithChildrenAsync(fruit);
                }
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
        }
    }

    public async Task<int> StorePipAsync(Pip pip)
    {
        if (pip == null)
            return 0;

        try
        {
            var foundItem = await this.GetPipAsync(pip.Number);
            if (foundItem != null)
            {
                return await this.database.UpdateAsync(pip);
            }
            else
            {
                return await this.database.InsertAsync(pip);
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
            return 0;
        }
    }
}

测试用例

public MainPage()
{
    InitializeComponent();

    Pip pip = new Pip();
    pip.Number = "4";
    pip.Title = "pip from apple";

    Fruit apple = new Fruit();
    apple.Number = "1";
    apple.Pip = pip;

    Basket basket = new Basket();
    basket.Number = "10";
    basket.Name = "grandma";
    basket.FruitList = new List<Fruit>() { apple };

    this.basket = basket;
}

protected override async void OnAppearing()
{
    base.OnAppearing();

    await App.Database.StoreBasketAsync(this.basket);
    Basket existingBasket = await App.Database.GetBasketAsync(this.basket.Number);
}

我正在使用最新的 SQLiteNetExtensions.Async v2.0.0-alpha2 NuGet 包。如何正确检索子元素 Pip

现在我读了documentation,上面写着

Cascade read operations allow you to fetch a complete relationship tree from the database starting at the object that you are fetching and continuing with all the relationships with CascadeOperations set to CascadeRead

我的 Fruit class 现在看起来像这样

public class Fruit
{
    [OneToOne(CascadeOperations = CascadeOperation.CascadeRead)]
    public Pip Pip
    {
        get { return this.pip; }
        set { this.pip = value; }
    }    
}

并且它按预期工作。我想,只有当我有一个 object 时才需要 CascadeRead,它有一些进一步的关系,但事实并非如此。您需要 CascadeRead 所有对象,无论对象是如何构建的,都应该递归获取这些对象。