2011-07-27 27 views
12

Potrzebuję wstawić kilka wierszy w jednej transakcji. Czy mogę to zrobić za pomocą ContentProvider?Wstawianie wielu wierszy za pomocą ContentProvider

+0

Pytanie jest jasne .. zresztą można sprawdzić tutaj http : //developer.android.com/guide/topics/providers/content-providers.html#modifying –

+0

http://stackoverflow.com/questions/5596354/insertion-thousands-of-contact-entries-using-applybatch -jest-wolny – Selvin

Odpowiedz

18

Po stronie klienta ContentResolver obsługuje metodę bulkInsert(). Te nie koniecznie będą przetwarzane w pojedynczej transakcji przez ContentProvider, po prostu dlatego, że nie mogą być przeprowadzane żadne transakcje przez ContentProvider.

+1

Jak rozumiem, jeśli to nie jest o verrriden - po prostu wywoła kilka metod "wstawiania" standardu? – earsonheart

+2

Prawidłowo. Nie masz pojęcia, czy dany 'ContentProvider' nadpisuje' bulkInsert() ', chyba że jest to twój własny" ContentProvider ". – CommonsWare

+0

Myślę, że powinieneś pójść z ContentProviderOperations, przeczytaj ten samouczek: http://www.grokkingandroid.com/better-performance- with-contentprovideroperation/ –

21

Zaimplementowałem to w mojej aplikacji i tutaj jest sens kodu, którego używam.

W moim dostawcy treści, mam przesłonięte metody applyBatch() i jest to bardzo prosta metoda, aby zastąpić:

/** 
* Performs the work provided in a single transaction 
*/ 
@Override 
public ContentProviderResult[] applyBatch(
     ArrayList<ContentProviderOperation> operations) { 
    ContentProviderResult[] result = new ContentProviderResult[operations 
      .size()]; 
    int i = 0; 
    // Opens the database object in "write" mode. 
    SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 
    // Begin a transaction 
    db.beginTransaction(); 
    try { 
     for (ContentProviderOperation operation : operations) { 
      // Chain the result for back references 
      result[i++] = operation.apply(this, result, i); 
     } 

     db.setTransactionSuccessful(); 
    } catch (OperationApplicationException e) { 
     Log.d(TAG, "batch failed: " + e.getLocalizedMessage()); 
    } finally { 
     db.endTransaction(); 
    } 

    return result; 
} 

Wynik podawany jest do następnej operacji, ponieważ chcemy, aby wesprzeć powrót referencje. Kiedy rzeczywiście chcesz zmienić rzeczy w bazie danych, w tym jednej pętli transakcja I nad moim zawartości i robić rzeczy tak:

operations.add(ContentProviderOperation 
        .newInsert(
          Uri.withAppendedPath(
            NotePad.Notes.CONTENT_ID_URI_BASE, 
            Long.toString(task.dbId))) 
        .withValues(task.toNotesContentValues(0, listDbId)) 
        .build()); 
// Now the other table, use back reference to the id the note 
// received 
noteIdIndex = operations.size() - 1; 

operations.add(ContentProviderOperation 
        .newInsert(NotePad.GTasks.CONTENT_URI) 
        .withValues(task.toGTasksContentValues(accountName)) 
        .withValueBackReferences(
          task.toGTasksBackRefContentValues(noteIdIndex)) 
        .build()); 

Trzeba tylko pamiętać, aby zakończyć przez wywołanie:

provider.applyBatch(operations); 

ten wykona twoje rzeczy w pojedynczej transakcji i obsługuje odsyłacze wstecz, jeśli potrzebujesz identyfikatora z wcześniejszej wstawki bez problemu.

6

tutaj jakiś przykładem bulkInsert:

/** 
* Perform bulkInsert with use of transaction 
*/ 
@Override 
public int bulkInsert(Uri uri, ContentValues[] values) { 
    int uriType = 0; 
    int insertCount = 0; 
    try { 

     uriType = sURIMatcher.match(uri); 
     SQLiteDatabase sqlDB = dbHelper.getWritableDatabase(); 

     switch (uriType) { 
     case MEASUREMENTS: 
      try { 
       sqlDB.beginTransaction(); 
       for (ContentValues value : values) { 
        long id = sqlDB.insert(Tab_Measurements.TABLE_NAME, null, value); 
        if (id > 0) 
         insertCount++; 
       } 
       sqlDB.setTransactionSuccessful(); 
      } catch (Exception e) { 
       // Your error handling 
      } finally { 
       sqlDB.endTransaction(); 
      } 
      break; 
     default: 
      throw new IllegalArgumentException("Unknown URI: " + uri); 
     } 
     // getContext().getContentResolver().notifyChange(uri, null); 
    } catch (Exception e) { 
     // Your error handling 
    } 

    return insertCount; 
} 

I w kodzie coś takiego:

/** 
* Inserts new measurement information. 
* 
* @param ArrayList of measurements 
* @return number of inserted entries 
*/ 
public static long bulkInsertEntries(ArrayList<Item_Measurement> readings) { 
    // insert only if data is set correctly 
    if (readings.size() == 0) 
     return 0; 

    long insertCount = 0; 
    try { 
     // insert new entries 

     // ArrayList<ContentValues> valueList = new ArrayList<ContentValues>(); 
     ContentValues[] valueList = new ContentValues[readings.size()]; 
     int i = 0; 
     for (Item_Measurement reading : readings) { 
      ContentValues values = new ContentValues(); 
      values.put(COL_TIME_READING, reading.getTimeReading()); 
          // ... 
      valueList[i++] = values; 
     } 

     // returns ID 
     insertCount = ContentProviderOwn.getAppContext().getContentResolver() 
       .bulkInsert(ContentProviderOwn.MEASUREMENTS_URI_BASE, valueList); 

    } catch (Exception e) { 
     // Your error handling 
    } 
    return insertCount; 
} 
+0

Jak to jest lepsze niż wywoływanie zwykłego wstawienia w pętli z oryginalnej tablicy danych? Czy wydajność w korzystaniu z BulkInsert jest lepsza? –

+2

@AndrewS bulkInsert() jest znacznie lepszy w dużych operacjach. Właśnie zakończyłem optymalizację w mojej aplikacji: applyBatch() z 2000 operacjami na kilku tabelach zajmuje 2000 ms, 10 bulkInsert's trwa 100 ms. –

1

ja również użyć trybu wymienić na wkładkę rzędu - db.insertWithOnConflict (EVENT_TABLE_NAME, null , wartość, SQLiteDatabase.CONFLICT_REPLACE); ITS będzie pozbyć konfliktu jeśli płyta jest już istnieją

W DatabaseHelper dodać unikatowy indeks

public class DataProvider extends ContentProvider { 

    private static class DatabaseHelper extends SQLiteOpenHelper { 
     DatabaseHelper(Context context){ 
      super(context, DATABASE_NAME, null, DATABASE_VERSION); 
     } 

     @Override 
     public void onCreate(SQLiteDatabase db){ 
      db.execSQL(CREATE_EVENT_TABLE); 
      db.execSQL("CREATE UNIQUE INDEX event_idx ON " + EVENT_TABLE_NAME + " (" + EventTable.EVENT_ID + ")"); 
// ... 

     ... 
     @Override 
     public int bulkInsert(Uri uri, ContentValues[] values) { 
      Log.i(TAG, "bulkInsert"); 
      if (values.length == 0) 
       return 0; 
      int insertCount = 0; 
      try { 
       switch (uriMatcher.match(uri)) { 
        case EVENT_LIST: 
         try { 
          db.beginTransaction(); 
          for (ContentValues value : values) { 
           long id = db.insertWithOnConflict(EVENT_TABLE_NAME, null, value, SQLiteDatabase.CONFLICT_REPLACE); 
           if (id > 0) 
            insertCount++; 
          } 
          db.setTransactionSuccessful(); 
         } catch (Exception e) { 
          // Your error handling 
         } finally { 
          db.endTransaction(); 
         } 
         break; 
        default: 
         throw new IllegalArgumentException("Unknown URI " + uri); 
       } 
       getContext().getContentResolver().notifyChange(uri, null); 
      } catch (Exception e) { 
       Log.i(TAG, "Exception : " + e); 
      } 
      return insertCount; 
     } 

I zadzwonić bulkInsert tak:

  ContentValues[] cvArr = new ContentValues[eventList.size()]; 
      long insertCount = 0; 
      int i = 0; 
      for (Event event : eventList) { 
       ContentValues cv = new ContentValues(); 
       cv.put(DataProvider.EventTable.EVENT_ID, event.id); 
       cv.put(DataProvider.EventTable.SENSOR_ID, event.sensor_id); 
       cv.put(DataProvider.EventTable.TIMESTAMP, event.time); 
       cvArr[i++] = cv; 
      } 
      // returns ID 
      insertCount = context.getContentResolver() 
        .bulkInsert(DataProvider.CONTENT_EVENT_LIST, cvArr); 
Powiązane problemy