البيانات الأساسية "Upsert" من قاعدة بيانات SQLite
-
22-09-2019 - |
سؤال
أنا أكتب حاليًا تطبيقًا يحتاج إلى القدرة على تعديل واستمرار أجزاء مختلفة من البيانات. لقد قررت استخدام البيانات الأساسية لهذا الغرض. عندما يفتح المستخدم التطبيق لأول مرة ، أحتاج إلى استيراد كمية كبيرة من البيانات من قاعدة بيانات SQLite ، تتكون هذه البيانات من العلاقات العديدة إلى العدد.
أرغب في معرفة أفضل طريقة لإدخال جميع هذه البيانات في متجر بيانات الكود الخاص بي. في الوقت الحالي ، أستخدم nsoperation للقيام بالاستيراد بينما يظل بقية التطبيق نشطًا ، حتى يتمكن المستخدم من القيام بأشياء أخرى ، لكنني أرغب في حدوث الاستيراد في أسرع وقت ممكن حتى يمكن الوصول إلى التطبيق بأكمله على الفور .
تتمثل الطريقة التي أستخدمها الآن في استخدام NSFetchRequest لمحاولة العثور على الكيان ذي الصلة في متجر البيانات ، إذا كان الكيان موجودًا ، فأنا أضيفه كعلاقة ، إذا لم يكن الكيان هناك ، فأنا أقوم بإنشاء واحدة جديدة و أضفها كعلاقة. هذا يعمل ، لكنني أشعر أنه ربما لا يكون قريبًا من الأمثل.
الرمز الذي أستخدمه الآن:
- (void)importEntitiesIntoContext: (NSManagedObjectContext*)managedObjectContext
{
// Setup the database object
static NSString* const databaseName = @"DBName.sqlite";
NSString* databasePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: databaseName];
sqlite3* database;
// Open the database from the user's filessytem
if ( sqlite3_open_v2( [databasePath UTF8String], &database, SQLITE_OPEN_READONLY, NULL ) == SQLITE_OK )
{
// Setup the SQL Statement
NSString* sqlStatement = [NSString stringWithFormat: @"SELECT some_columns FROM SomeTable;"];
sqlite3_stmt* compiledStatement;
if ( sqlite3_prepare_v2( database, [sqlStatement UTF8String], -1, &compiledStatement, NULL ) == SQLITE_OK )
{
// Create objects to test for existence of exercises
NSPredicate* predicate = [NSPredicate predicateWithFormat: @"something == $SOME_NAME"];
NSEntityDescription* entityDescription = [NSEntityDescription entityForName: @"SomeEntity"
inManagedObjectContext: managedObjectContext];
NSFetchRequest* fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity: entityDescription];
// Loop through the results and add them to the feeds array
while ( sqlite3_step( compiledStatement ) == SQLITE_ROW )
{
NSString* someName = [NSString stringWithCharsIfNotNull: (char*)sqlite3_column_text( compiledStatement, 1 )];
NSPredicate* localPredicate = [predicate predicateWithSubstitutionVariables:
[NSDictionary dictionaryWithObject: someName
forKey: @"SOME_NAME"]];
[fetchRequest setPredicate: localPredicate];
NSError* fetchError;
NSArray* array = [managedObjectContext executeFetchRequest: fetchRequest
error: &fetchError];
if ( array == nil )
{
// handle error
}
else if ( [array count] == 0 )
{
SomeEntity* entity =
[NSEntityDescription insertNewObjectForEntityForName: @"SomeEntity"
inManagedObjectContext: managedObjectContext];
entity.name = someName;
// **here I call a method that attempts to add the relationships(listed below)**
}
else
{
// Some entity already in store
}
}
}
else
{
NSLog( @"sqlStatement failed: %@", sqlStatement );
}
// Release the compiled statement from memory
sqlite3_finalize( compiledStatement );
}
// All the data has been imported into this temporary context, now save it
NSError *error = nil;
if ( ![managedObjectContext save: &error] )
{
NSLog( @"Unable to save %@ - %@", [error localizedDescription] );
}
}
طريقة لإضافة العلاقات:
- (void)setRelationshipForEntity: (Entity*)entity
inManagedObjectContext: (NSManagedObjectContext*)managedObjectContext
usingDatabase: (sqlite3*)database
entityId: (NSNumber*)entityId
{
// Setup the SQL Statement and compile it for faster access
NSString* sqlStatement = [NSString stringWithFormat: @"SELECT Relationship.name FROM Relationship JOIN Entitys_Relationship ON Entitys_Relationship.id_Relationship = Relationship.id JOIN Entitys ON Entitys_Relationship.id_Entitys = Entitys.id WHERE Entitys.id = %d;", [entityId integerValue]];
sqlite3_stmt* compiledStatement;
if ( sqlite3_prepare_v2( database, [sqlStatement UTF8String], -1, &compiledStatement, NULL ) == SQLITE_OK )
{
// Create objects to test for existence of relationship
NSPredicate* predicate = [NSPredicate predicateWithFormat: @"relationshipName == $RELATIONSHIP_NAME"];
NSEntityDescription* entityDescription = [NSEntityDescription entityForName: @"EntityRelationship"
inManagedObjectContext: managedObjectContext];
NSFetchRequest* fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity: entityDescription];
while ( sqlite3_step( compiledStatement ) == SQLITE_ROW )
{
NSString* relationshipName = [NSString stringWithCharsIfNotNull: (char*)sqlite3_column_text( compiledStatement, 0 )];
NSPredicate* localPredicate = [predicate predicateWithSubstitutionVariables:
[NSDictionary dictionaryWithObject: relationshipName
forKey: @"RELATIONSHIP_NAME"]];
[fetchRequest setPredicate: localPredicate];
NSError* fetchError;
NSArray* array = [managedObjectContext executeFetchRequest: fetchRequest
error: &fetchError];
if ( array == nil )
{
// handle error
}
else if ( [array count] == 0 )
{
EntityRelationship* entityRelationship =
[NSEntityDescription insertNewObjectForEntityForName: @"EntityRelationship"
inManagedObjectContext: managedObjectContext];
entityRelationship.relationshipName = relationshipName;
[entity addRelationshipObject: entityRelationship];
//NSLog( @"Inserted relationship named %@", relationshipName );
}
else
{
[entity addRelationship: [NSSet setWithArray: array]];
}
}
}
else
{
NSLog( @"slqStatement failed: %@", sqlStatement );
}
// Release the compiled statement from memory
sqlite3_finalize( compiledStatement );
}
المحلول
من أين تأتي قاعدة البيانات الأصلية؟
عادةً ما تقوم بتحويل جميع بياناتك إلى البيانات الأساسية أثناء التطوير ، ثم شحن التطبيق باستخدام متجر بيانات أساسي مسبقًا (لا حاجة للمستخدم لانتظار الاستيراد).
نصائح أخرى
لدى Apple بعض الاقتراحات حول كيفية تحسين الواردات الكبيرة في متجر بيانات أساسي:
- تعطيل مديرة التراجع أثناء استيراد الدفعة
- لا تقم بإدراج سجل على حدة-قم بإنشاء مجموعات من الحجم N (اعتمادًا على حجم السجل)
- استخدم حمام سباحة Autorelease محليًا واستنزفه بعد كل دفعة
انظر توثيق للحصول على التفاصيل.