InsertWithChildrenAsync() и дублировать первичные ключи

Я получаю список объектов из веб-сервиса. Данные должны храниться в базе данных SQLite. Таким образом, я могу сохранить первый элемент в базе данных с помощью InsertWithChildrenAsync(), на втором я получаю исключение, и приложение вылетает.

Определение класса:

public class Color
{
    [PrimaryKey]
    public string Code { get; set; }
    public string Name { get; set; }
}
public class Person
{
    [PrimaryKey]
    public int Id { get; set; }
    public string Name { get; set; }
    [ForeignKey(typeof(Color))]
    public string FavoriteColorId { get; set; }
    [OneToOne(CascadeOperations = CascadeOperation.All)]
    public Color FavoriteColor { get; set; }
}

Инициализация:

var dbPath = DependencyService.Get<IStorageService>().GetFilePathForDB();
DependencyService.Get<IStorageService>().DeleteFile(dbPath);
var db = new SQLiteAsyncConnection(dbPath);
await db.CreateTableAsync<Color>();
await db.CreateTableAsync<Person>();

Код, демонстрирующий проблему:

var pers1 = new Person()
{
    Id = 1,
    Name = "John",
    FavoriteColor = new Color() { Code = "FF0000", Name = "Red" }
};

var pers2 = new Person()
{
    Id = 2,
    Name = "Doe",
    FavoriteColor = new Color() { Code = "FF0000", Name = "Red" }
};

await db.InsertWithChildrenAsync(pers1, true);
await db.InsertWithChildrenAsync(pers2, true);

Сообщение об ошибке

SQLite.SQLiteException: ограничение

Этого не произойдет, если я использую

var pers2 = new Person()
{
    Id = 2,
    Name = "Doe",
    FavoriteColor = new Color() { Code = "00FF00", Name = "Green" }
};

Проблема в том, что один и тот же первичный ключ вставляется дважды. Одним из решений было бы использование автоматического увеличения, но тогда одни и те же данные сохраняются несколько раз. Как я могу использовать одни и те же данные для разных объектов? Данные из веб-сервиса анализируются и позже сохраняются в базе данных. Я не держу объекты в памяти все время. В противном случае я мог бы использовать что-то вроде

Color red = new Color { Code = "00FF00", Name = "Green" };
pers1.FavoriteColor = red;
pers2.FavoriteColor = red;

Нужна ли мне таблица «многие ко многим»? Что насчет удаления? В настоящее время я планирую использовать DeleteAsync(), но запись не может быть полностью удалена, потому что ее использует другой экземпляр. Учитывает ли это метод?

Каковы мои варианты?


person testing    schedule 18.05.2018    source источник


Ответы (2)


Вы вставляете этот объект дважды:

new Color() { Code = "00FF00", Name = "Green" }

Поэтому вы получаете ошибку ограничения первичного ключа. Я бы рекомендовал вам проверить наличие этого элемента, прежде чем вставлять его в базу данных.

person redent84    schedule 21.05.2018
comment
Я понимаю, но я вставляю только с InsertWithChildrenAsync(). Если я последую вашему совету, мне придется проверить каждое свойство Person, если запись существует. И если да, получить это из базы данных, назначить человеку, а затем снова использовать InsertWithChildrenAsync()? Что насчет удаления? Это делает его намного более сложным и приводит к ручному хранению данных. InsertWithChildrenAsync() не подходит для сложных данных. Значит, нет способа проще? - person testing; 22.05.2018
comment
Ну да. Если вы попытаетесь вставить уже существующий объект в базу данных, вы получите ошибку ограничения, все базы данных и большинство ORM работают таким образом. Если вы хотите, чтобы он переопределял существующие поля, вы можете использовать методы InsertOrReplace. Если вы не хотите переопределять, вам придется сначала проверить существование и вставить, только если он еще не существует. - person redent84; 22.05.2018

Если у вас сложная структура данных, как у меня, вы можете сделать следующее: Первичные ключи, которые не используют флаг автоинкремента [PrimaryKey, AutoIncrement], должны обрабатываться отдельно.

Для вставок не используйте CascadeOperations = CascadeOperation.CascadeInsert. Вместо этого вы должны вставить их вручную. Например.

await StoreColorAsync(db, red);
await db.InsertWithChildrenAsync(pers1, false);

public async Task StoreColorAsync(SQLiteAsyncConnection db, Color color)
{
    if (color == null)
        return;

    var foundItem = await GetColorAsync(db, color.Code);
    if (foundItem != null)
    {
        await db.UpdateAsync(color);
    }
    else
    {
        await db.InsertAsync(color);
    }
}

public async Task<Color> GetColorAsync(SQLiteAsyncConnection db, string colorCode)
{
    var queryResult = await db.Table<Color>().Where(c => c.Code == colorCode).CountAsync();
    if (queryResult > 0)
    {
        return await db.GetAsync<Color>(colorCode);
    }
    else
    {
        return null;
    }
}

Для удаления используйте только CascadeOperations = CascadeOperation.CascadeDelete для записей с флагом автоинкремента. Здесь я оставляю другие записи в базе данных, которые, возможно, будут повторно использованы позже. В некоторых особых случаях (например, выход из системы) я очищаю все таблицы.

person testing    schedule 22.05.2018