32

El objetivo: actualizar la base de datos a partir de datos XMLAndroid: transacciones SQLite cuando se utiliza ContentResolver

El proceso:

  • transacción de inicio
  • Eliminar todas las filas existentes de las tablas
  • Para cada elemento principal del XML analizado inserte la fila en la tabla principal y obtenga PK
  • por cada hijo del principal elemento de inserción registro en segunda tabla que proporciona FK de la etapa anterior
  • confirmar la transacción

cosas bastante estándar en cuanto a las operaciones db. El problema es que las operaciones CRUD no se realizan dentro de ContentProvider, sino más bien usando ContentResolver, por lo que la inserción, por ejemplo, se ve como resolver.insert(CONTENT_URI, contentValues). La API de ContentResolver no parece tener nada relacionado con la transacción y no puedo usar bulkInsert ya que estoy insertando en 2 tablas intermitentemente (además quiero tener delete dentro de la transacción).

Estaba pensando en registrar mi ContentProvider personalizado como oyente usando registerContentObserver pero como los métodos ContentResolver#acquireProvider están ocultos, ¿cómo obtengo la referencia correcta?

¿No tengo suerte?

+2

ver esto: http://stackoverflow.com/questions/4655291/semantics-of-withvaluebackreference –

+0

¿Alguna vez encontrar una solución a esto? No puedo encontrar una solución que funcione – jamesc

Respuesta

41

he visto que en el código fuente de la aplicación Google I/O, que anulan ContentProvider 's applyBatch() método y el uso de las transacciones dentro de ella. Entonces, crea un lote de ContentProviderOperation sy luego llama al getContentResolver().applyBatch(uri_authority, batch).

Estoy planeando utilizar este enfoque para ver cómo funciona. Tengo curiosidad si alguien más lo ha intentado.

+7

He intentado este enfoque y funciona bien. Sin embargo, cada ContentProviderOperation en el lote son operaciones atómicas. Lo que quiero decir con esto es que no hay forma de manejar correctamente las operaciones dependientes para las relaciones de detalle maestro donde la clave de identidad creada por la primera operación es necesaria como entrada para las operaciones posteriores. He pedido esto antes, pero obtuve cero respuestas (http://stackoverflow.com/questions/3224857/master-detail-using-contentresolver-applybatch). – Dan

+0

Lo intenté también y noté una ganancia de rendimiento de más del 1000 por ciento. Solo copiando el código del Proyecto IOShed a mi Proveedor. – fmo

+0

Esta respuesta no es correcta. La impl predeterminada de 'applyBatch()' solo itera sobre las operaciones y las aplica de forma aislada. Esto solo proporciona un medio para implementar una transacción, sobrescribiendo 'applyBatch()' en su 'ContentProvider'. No proporciona comportamiento transaccional por sí mismo. Si no controla la implementación 'ContentProvider', no tiene suerte. –

4

Muy bien, así que esto no suena sin rumbo fijo: la única forma en que se me ocurre es codificar startTransaction y endTransaction como solicitudes de consulta basadas en URL. Algo así como ContentResolver.query(START_TRANSACTION, null, null, null, null). Luego, en ContentProvider#query basado en el inicio de la llamada URL registrada o transacción final

16

Es posible realizar insertos de tabla múltiple basados ​​en transacciones de forma bastante limpia desde Android 2.1 mediante ContentProviderOperation, como lo menciona kaciula.

Cuando crea el objeto ContentProviderOperation, puede llamar a .withValueBackReference (fieldName, refNr). Cuando la operación se aplica con applyBatch, el resultado es que el objeto ContentValues ​​que se suministra con la llamada insert() tendrá un entero insertado. El entero se tecleará con fieldName String, y su valor se recuperará del ContentProviderResult de un ContentProviderOperation aplicado previamente, indexado por refNr.

Consulte el siguiente ejemplo de código. En el ejemplo, se inserta una fila en la tabla 1, y la ID resultante (en este caso, "1") se usa como un valor al insertar la fila en la tabla 2. Por brevedad, ContentProvider no está conectado a una base de datos. En ContentProvider, hay impresiones donde sería adecuado agregar el manejo de transacciones.

public class BatchTestActivity extends Activity { 
    /** Called when the activity is first created. */ 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.main); 

     ArrayList<ContentProviderOperation> list = new 
      ArrayList<ContentProviderOperation>(); 

     list.add(ContentProviderOperation. 
      newInsert(BatchContentProvider.FIRST_URI).build()); 
     ContentValues cv = new ContentValues(); 
     cv.put("name", "second_name"); 
     cv.put("refId", 23); 

     // In this example, "refId" in the contentValues will be overwritten by 
     // the result from the first insert operation, indexed by 0 
     list.add(ContentProviderOperation. 
      newInsert(BatchContentProvider.SECOND_URI). 
      withValues(cv).withValueBackReference("refId", 0).build()); 

     try { 
      getContentResolver().applyBatch(
       BatchContentProvider.AUTHORITY, list); 
     } catch (RemoteException e) { 
      e.printStackTrace(); 
     } catch (OperationApplicationException e) { 
      e.printStackTrace(); 
     } 
    } 
} 

public class BatchContentProvider extends ContentProvider { 

    private static final String SCHEME = "content://"; 
    public static final String AUTHORITY = "com.test.batch"; 

    public static final Uri FIRST_URI = 
     Uri.parse(SCHEME + AUTHORITY + "/" + "table1"); 
    public static final Uri SECOND_URI = 
     Uri.parse(SCHEME + AUTHORITY + "/" + "table2"); 


    public ContentProviderResult[] applyBatch(
     ArrayList<ContentProviderOperation> operations) 
      throws OperationApplicationException { 
     System.out.println("starting transaction"); 
     ContentProviderResult[] result; 
     try { 
      result = super.applyBatch(operations); 
     } catch (OperationApplicationException e) { 
      System.out.println("aborting transaction"); 
      throw e; 
     } 
     System.out.println("ending transaction"); 
     return result; 
    } 

    public Uri insert(Uri uri, ContentValues values) { 
     // this printout will have a proper value when 
     // the second operation is applied 
     System.out.println("" + values); 

     return ContentUris.withAppendedId(uri, 1); 
    } 

    // other overrides omitted for brevity 
} 
0

Puede obtener la aplicación del propio objeto proveedor de contenido (si está en el mismo proceso, pista: se puede controlar el proceso del proveedor con multiproceso = "true" o proceso = "" http://developer.android.com/guide/topics/manifest/provider-element.html) usando ContentProviderClient.getLocalContentProvider() que puede transferirse a la implementación de su proveedor que puede proporcionar una funcionalidad adicional como un reinicio() que cierra y elimina la base de datos y también puede devolver una instancia de clase de transacción personalizada con los métodos guardar() y cerrar().

public class Transaction { 
    protected Transaction (SQLiteDatabase database) { 
     this.database = database; 
     database.beginTransaction(); 
    } 

    public void save() { 
     this.database.setTransactionSuccessful(); 
    } 

    public void close() { 
     this.database.endTransaction(); 
    } 

    private SQLiteDatabase database; 
} 

public Transaction createTransaction() { 
    return new Transaction (this.dbHelper.getWritableDatabase()); 
} 

continuación:

ContentProviderClient client = getContentResolver().acquireContentProviderClient (Contract.authorityLocal); 
Transaction tx = ((LocalContentProvider) client.getLocalContentProvider()).createTransaction(); 
Cuestiones relacionadas