开发者

What is the best strategy for mirroring a remote DB in Core Data?

Let's say that I have two tables in a DB: Expenses and Account. Expenses is the data that I'm interested in and that table has a foreign key to Account. This DB is remote, accessed via Restful-esque commands, and I want to mirror just the data I need for my app in a Core Data data store on the iPhone. The actual DB I'm working with is much bigger than this example. ~30 tables and the Expenses table has ~7 FKs. I'm working closely with the person doing the API design, so I can modify the way I make my requests or the data returned, if necessary.

What is the best strategy for loading this data into Core Data?

My first thought was to have the request for the expense bring back the ids for the FK.

<expense>
    <date>1/1/2011</date>
    <cost>1.50</cost>
    <account_id>123</account_id>
</expense>

This works fine if I already have an account with id '123' in my data store. If I don't, then I've got to make additional web requests every time I encounter an id I don't have… which is going to be incredibly slow. I can get around this by making requests in a specific order, i.e. request all new accounts before requesting expenses, so that I way I know all the FK rows exist. I feel this would become much too cumbersome once the DB starts reaching moderate complexity.

My second thought was to have the data returned from the request follow FKs and return data from the FK.

<expense>
    <date>1/1/2011</date>
    <cost>1.50</cost>
    <account>
        <id>123</id>
        <name>Bob's Big Boy</name>
        <address>1234 Main Street</address>
 开发者_高级运维   </account>
</expense>

This looks better and guarantees that I'll have all the data I need when I need it. If I don't already have an account '123' I can create a new account object from that XML. My concern with this method, though, is that as the database grows in complexity, these XML files could become excessively large. The Expenses table has ~7 foreign keys, each of those tables has multiple FKs. It feels like a simple request for just a single Expense could end up returning a huge chunk of data.

How have other people solved this issue?


I am assuming that at any given time you only want to cache part of the server DB in the local app and that the data cached may change overtime.

You probably want to use "stub" entities to represent related objects that you haven't actually downloaded yet. You would set up the entities like this:

Expense{
  date:Date
  cost:Number 
  account<<-->AccountStub.expenses
}

AccountStub{
  id:Number
  expenses<-->>Expenses.account
}

Account:AccountStub{
  name:String
  address:String
}

The AccountStub entity has the bare minimum info needed to identify the Account in the server DB based on info provided from the Expense table. It serves as a placeholder in the object graph for the full fledged Account object (you can think of it as a type of fault if you like.)

Since Expenses has the relationship with AccountStub and Account inherits from AccountStub you can swap out an Account for an AccountStub (and vice versa) as needed.

You will need to provide a custom subclass for AccountStub and Account such that AccountStub can trigger the downloading of account data and the creation of an Account object when that data is actually required. Then the new Account object should be swapped out for AccountStub in all its relationships (that may take rather a lot of code.)

To use, you would first obtain the data for an Expense object and create that object. You would attempt to fetch for an AccountStub with the ID provided from the Expense table data. Set the fetch to include subentries. If an AccountStub or Account object exist with that ID you will add the Expense object to the relationship. If not, the you create an AccountStub object with that ID and add it to the relationship. Now you have a basic object graph showing the relationship of an Expense object to an AccountStub object. To access the account data of an Expense, you would first check if the related account is a stub or a full account. If it is a stub, then you need to load the full account data before preceding.

The advantage of this system is that you can maintain a fairly complex object graph without having to actually have all the data locally. E.g. you can maintain several relationships and walk those relationships. E.g you could expand your model like this:

AccountStub{
  id:Number
  expenses<-->>Expenses.account
  owner<<--AccountOwnerStub.accounts
}

AccountOwnerStub{
    id:Number
    accounts<-->>AccountStub.owner
}

AccountOwner{
    name:String
    address:String
    bill:Number
}

If you wanted to find the name of an Expense object's account owner, you would just walk the relationship across the stubs with account.owner.name the Account object itself would would remain just a stub.

If you need to conserve room locally, you can revert an object back to a stub without compromising the graph.

This would take some work and you would have to keep an eye on the stubs but it would let you mirror a complex external DB without having to keep all the data on hand.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜