Two simple questions regarding Core-Data

  • Hello List,
    I'm playinig with core-data framework; it's looks really good but I've
    two simple questions:
    a) is possible to set the value of an attribute with a predicate? For
    example I would to set the 'label' attribute to 1 for all entities
    into the managed objects. Is the only way to get the complete array of
    these entities and then apply the attribute to each manually"?

    b) is possible to get the number of items given from a predicate
    results instead of using [resultsArray count]? (a sort of count(*) for
    sql, I don't need of the list). Or there is not any issue on general
    performances? This action will be repeated lots of time in my app.

    Thanks a lot
    Michael.
  • On 5/4/07, Hell's KItchen Hell's KItchen <thehellskitchen...> wrote:

    > a) is possible to set the value of an attribute with a predicate? For
    > example I would to set the 'label' attribute to 1 for all entities
    > into the managed objects. Is the only way to get the complete array of
    > these entities and then apply the attribute to each manually"?

    No, but you can retrieve them into an array and then call

    - (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(id)anObject

    on the array.  This avoids having to loop over the array and call the
    method on each object individually.

    > b) is possible to get the number of items given from a predicate
    > results instead of using [resultsArray count]? (a sort of count(*) for
    > sql, I don't need of the list). Or there is not any issue on general
    > performances? This action will be repeated lots of time in my app.

    Again no.  Fortunately when the array is retrieved all of the objects
    are "faults" which means they are not loaded into memory yet and your
    call to count will not cause them to load.  Therefore the memory and
    performance hits are negligible.

    Marcus S. Zarra
    Zarra Studios LLC
    Simply Elegant Software for OS X
    www.zarrastudios.com
  • First, it's a common tendency to think about Core Data as if it were
    a database.  Here is a short excerpt from the Core Data Programming
    Guild:

    ------------------------------------------
    What Core Data Is Not
    Having given an overview of what Core Data is, it is also useful to
    correct some misperceptions and state what it is not.

    Core Data is not a database. Core Data provides an infrastructure for
    change management and for saving objects to and retrieving them from
    persistent storage. It is not in and of itself a database.

    ------------------------------------------

    > a) is possible to set the value of an attribute with a predicate? For
    > example I would to set the 'label' attribute to 1 for all entities
    > into the managed objects. Is the only way to get the complete array of
    > these entities and then apply the attribute to each manually"?

    As far as I know the answer is basically yes.  You are making a
    change to each object, there may be other objects using the value
    that is changed that need to be notified of the change through KVO.
    You don't want the objects being mutated during a fetch request.

    > b) is possible to get the number of items given from a predicate
    > results instead of using [resultsArray count]? (a sort of count(*) for
    > sql, I don't need of the list). Or there is not any issue on general
    > performances? This action will be repeated lots of time in my app.

    Again, this is why I mentioned the excerpt from the Core Data
    Programming Guide.  My suggestion would be to start your work with
    Core Data using the XML data store.  This should help you break the
    habit of thinking about Core Data like it was a database.  In the
    case of the XML or binary persistent stores the entire set of data is
    loaded into memory when the file is first opened.  Then the entire
    store gets replaced, atomically, during a save operation.  In this
    case it become obvious that you count the objects in the array.

    A common design pattern when counting a to-many relation is to cache
    the count value within an attribute of the source object.

    For example if you have the relation Department < 1----------* >
    Person then you could add a "personCount" attribute to Department and
    write code in Department to observe changes to the people relation to
    update your "counter cache."  In this case your count operation
    would have no need to fire the Person fault simply to get the count
    of people within the department.
    On May 4, 2007, at 7:25 AM, Hell's KItchen Hell's KItchen wrote:

    > Hello List,
    > I'm playinig with core-data framework; it's looks really good but I've
    > two simple questions:
    > a) is possible to set the value of an attribute with a predicate? For
    > example I would to set the 'label' attribute to 1 for all entities
    > into the managed objects. Is the only way to get the complete array of
    > these entities and then apply the attribute to each manually"?
    >
    > b) is possible to get the number of items given from a predicate
    > results instead of using [resultsArray count]? (a sort of count(*) for
    > sql, I don't need of the list). Or there is not any issue on general
    > performances? This action will be repeated lots of time in my app.
    >
    > Thanks a lot
    > Michael.

    --
    Robert Walker
    <robertwalker1...>
  • Marcus,

    > Again no.  Fortunately when the array is retrieved all of the objects
    > are "faults" which means they are not loaded into memory yet and your
    > call to count will not cause them to load.  Therefore the memory and
    > performance hits are negligible.

    Are you sure that asking for the count would not cause the fault
    representing a collection to fire?  If so that's really impressive.
    How would Core Data know the number of objects represented by the
    fault without realizing the fault first?

    --
    Robert Walker
    <robertwalker1...>
  • Basically, the fetch will return an array of faults if the objects are
    not already in memory.  Performing a count on the array will not cause
    the faults to fire since you are not accessing the attributes of the
    objects inside of the array.  You are just asking the array how many
    objects it holds.

    Personally, I would recommend this method over keeping a count
    attribute in a parent object.  It is way too easy for that count to
    get out of sync with the array.  Since counting the number objects in
    the relationship will not cause the faults to fire it is safer to get
    this information each time it is requested.

    Marcus S. Zarra
    Zarra Studios LLC
    Simply Elegant Software for OS X
    www.zarrastudios.com

    On 5/4/07, Robert Walker <robertwalker1...> wrote:
    > Marcus,
    >
    >> Again no.  Fortunately when the array is retrieved all of the objects
    >> are "faults" which means they are not loaded into memory yet and your
    >> call to count will not cause them to load.  Therefore the memory and
    >> performance hits are negligible.
    >
    > Are you sure that asking for the count would not cause the fault
    > representing a collection to fire?  If so that's really impressive.
    > How would Core Data know the number of objects represented by the
    > fault without realizing the fault first?
    >
    > --
    > Robert Walker
    > <robertwalker1...>
    >
  • Marcus,

    Ok for my own sanity and full understanding of this, I created a very
    simple Core Data application with a button connected to an action
    used to count people in a department:

    Here's my counting code:

    01  (IBAction)countPeople:sender
    02  {
    03     NSManagedObject *selection = [[arrayController selectedObjects]
    objectAtIndex:0];
    04     NSLog(@"\n\nDepartment before counting: %@", [selection
    description]);
    05
    06     int count = [[selection valueForKey:@"people"] count];
    07
    08     NSLog(@"\n\nCount of people: %d", count);
    09     NSLog(@"\n\nDepartment after counting: %@", [selection
    description]);
    10  }

    And here are the results:

    [Session started at 2007-05-04 15:55:51 -0400.]
    2007-05-04 15:55:56.931 FaultTest[3015]

    Department before counting: <NSManagedObject: 0x320b00> (entity:
    Department; id: 0x320dd0 <x-coredata://9C7E671E-A376-4ED1-AB42-
    AC0F88013CE6/Department/p1> ; data: {name = "Department A"; people =
    "<relationship fault: 0x320ff0 'people'>"; })
    2007-05-04 15:55:56.933 FaultTest[3015]

    Count of people: 2
    2007-05-04 15:55:56.934 FaultTest[3015]

    Department after counting: <NSManagedObject: 0x320b00> (entity:
    Department; id: 0x320dd0 <x-coredata://9C7E671E-A376-4ED1-AB42-
    AC0F88013CE6/Department/p1> ; data: {
        name = "Department A";
        people = (
            0x31b2f0 <x-coredata://9C7E671E-A376-4ED1-AB42-AC0F88013CE6/
    Person/p4>,
            0x31c250 <x-coredata://9C7E671E-A376-4ED1-AB42-AC0F88013CE6/
    Person/p1>
        );
    })

    Does this not indicate that accessing the count of people, through
    the relationship on a department, does indeed fire the fault?  Or is
    there another way of getting the count without using KVC to access
    the "people" relationship on a department (as shown in line 6)?

    On May 4, 2007, at 1:03 PM, Marcus S. Zarra wrote:

    > Basically, the fetch will return an array of faults if the objects are
    > not already in memory.  Performing a count on the array will not cause
    > the faults to fire since you are not accessing the attributes of the
    > objects inside of the array.  You are just asking the array how many
    > objects it holds.
    >
    > Personally, I would recommend this method over keeping a count
    > attribute in a parent object.  It is way too easy for that count to
    > get out of sync with the array.  Since counting the number objects in
    > the relationship will not cause the faults to fire it is safer to get
    > this information each time it is requested.
    >
    > Marcus S. Zarra
    > Zarra Studios LLC
    > Simply Elegant Software for OS X
    > www.zarrastudios.com
    >
    >
    > On 5/4/07, Robert Walker <robertwalker1...> wrote:
    >> Marcus,
    >>
    >>> Again no.  Fortunately when the array is retrieved all of the
    >> objects
    >>> are "faults" which means they are not loaded into memory yet and
    >> your
    >>> call to count will not cause them to load.  Therefore the memory
    >> and
    >>> performance hits are negligible.
    >>
    >> Are you sure that asking for the count would not cause the fault
    >> representing a collection to fire?  If so that's really impressive.
    >> How would Core Data know the number of objects represented by the
    >> fault without realizing the fault first?
    >>
    >> --
    >> Robert Walker
    >> <robertwalker1...>
    >>
    >>
  • Interesting that your example causes the faults to fire.  By removing
    the bindings from the equation I was not able to duplicate your
    results.

    NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
    [request setEntity:[NSEntityDescription entityForName:@"Invoice"
    inManagedObjectContext:managedObjectContext]];
    [request setPredicate:[NSPredicate predicateWithFormat:@"invoiceNumber == 19"]];
    NSError *errord;
    NSArray *array = [managedObjectContext executeFetchRequest:request
    error:&errord];
    if (!array) {
        NSLog(@"Error: %@", errord);
        return;
    }
    NSLog(@"Count of invoices: %d\n%@", [array count], array);

    id invoice = [array objectAtIndex:0];
    id detailSet = [invoice valueForKey:@"invoiceDetails"];

    NSLog(@"Count of details: %d\n%@", [detailSet count], detailSet);

    --------Output

    2007-05-04 14:44:15.634 seSales[25169] Count of invoices: 1
    (
        <InvoiceEntity: 0x4e82c0> (entity: Invoice; id: 0x5d37860
    <x-coredata://799303D2-FFF6-409B-BA4A-396CE56E3864/Invoice/p334> ;
    data: <fault>)
    )
    2007-05-04 14:44:15.650 seSales[25169] Count of details: 5
    <_NSFaultingMutableSet: 0x5b33930> (<InvoiceDetailEntity: 0x5d9efb0>
    (entity: InvoiceDetail; id: 0x4f1da0
    <x-coredata://799303D2-FFF6-409B-BA4A-396CE56E3864/InvoiceDetail/p1230>
    ; data: <fault>), <InvoiceDetailEntity: 0x4ce240> (entity:
    InvoiceDetail; id: 0x4f1dc0
    <x-coredata://799303D2-FFF6-409B-BA4A-396CE56E3864/InvoiceDetail/p2140>
    ; data: <fault>), <InvoiceDetailEntity: 0x5d29320> (entity:
    InvoiceDetail; id: 0x4f1d50
    <x-coredata://799303D2-FFF6-409B-BA4A-396CE56E3864/InvoiceDetail/p1210>
    ; data: <fault>), <InvoiceDetailEntity: 0x4cd2b0> (entity:
    InvoiceDetail; id: 0x4f1de0
    <x-coredata://799303D2-FFF6-409B-BA4A-396CE56E3864/InvoiceDetail/p2217>
    ; data: <fault>), <InvoiceDetailEntity: 0x4ce200> (entity:
    InvoiceDetail; id: 0x5d9e190
    <x-coredata://799303D2-FFF6-409B-BA4A-396CE56E3864/InvoiceDetail/p452>
    ; data: <fault>))

    As you can see I am accessing the invoiceDetails through the
    relationship and in my output I do not get the items fired.  So this
    may indicate that the array controller is giving you a proxy object in
    your example or some other magic is going on behind the scenes.

    NOTE: the OP was looking for a way to perform a "select count(*) from
    xxx" which the above code does perform without a performance hit of
    firing the faults.

    Marcus S. Zarra
    Zarra Studios LLC
    Simply Elegant Software for OS X
    www.zarrastudios.com

    On 5/4/07, Robert Walker <robertwalker1...> wrote:
    > Marcus,
    >
    > Ok for my own sanity and full understanding of this, I created a very
    > simple Core Data application with a button connected to an action
    > used to count people in a department:
    >
    > Here's my counting code:
    >
    > 01  (IBAction)countPeople:sender
    > 02  {
    > 03      NSManagedObject *selection = [[arrayController selectedObjects]
    > objectAtIndex:0];
    > 04      NSLog(@"\n\nDepartment before counting: %@", [selection
    > description]);
    > 05
    > 06      int count = [[selection valueForKey:@"people"] count];
    > 07
    > 08      NSLog(@"\n\nCount of people: %d", count);
    > 09      NSLog(@"\n\nDepartment after counting: %@", [selection
    > description]);
    > 10  }
    >
    > And here are the results:
    >
    > [Session started at 2007-05-04 15:55:51 -0400.]
    > 2007-05-04 15:55:56.931 FaultTest[3015]
    >
    > Department before counting: <NSManagedObject: 0x320b00> (entity:
    > Department; id: 0x320dd0 <x-coredata://9C7E671E-A376-4ED1-AB42-
    > AC0F88013CE6/Department/p1> ; data: {name = "Department A"; people =
    > "<relationship fault: 0x320ff0 'people'>"; })
    > 2007-05-04 15:55:56.933 FaultTest[3015]
    >
    > Count of people: 2
    > 2007-05-04 15:55:56.934 FaultTest[3015]
    >
    > Department after counting: <NSManagedObject: 0x320b00> (entity:
    > Department; id: 0x320dd0 <x-coredata://9C7E671E-A376-4ED1-AB42-
    > AC0F88013CE6/Department/p1> ; data: {
    > name = "Department A";
    > people = (
    > 0x31b2f0 <x-coredata://9C7E671E-A376-4ED1-AB42-AC0F88013CE6/
    > Person/p4>,
    > 0x31c250 <x-coredata://9C7E671E-A376-4ED1-AB42-AC0F88013CE6/
    > Person/p1>
    > );
    > })
    >
    > Does this not indicate that accessing the count of people, through
    > the relationship on a department, does indeed fire the fault?  Or is
    > there another way of getting the count without using KVC to access
    > the "people" relationship on a department (as shown in line 6)?
    >
    > On May 4, 2007, at 1:03 PM, Marcus S. Zarra wrote:
    >
    >> Basically, the fetch will return an array of faults if the objects are
    >> not already in memory.  Performing a count on the array will not cause
    >> the faults to fire since you are not accessing the attributes of the
    >> objects inside of the array.  You are just asking the array how many
    >> objects it holds.
    >>
    >> Personally, I would recommend this method over keeping a count
    >> attribute in a parent object.  It is way too easy for that count to
    >> get out of sync with the array.  Since counting the number objects in
    >> the relationship will not cause the faults to fire it is safer to get
    >> this information each time it is requested.
    >>
    >> Marcus S. Zarra
    >> Zarra Studios LLC
    >> Simply Elegant Software for OS X
    >> www.zarrastudios.com
    >>
    >>
    >> On 5/4/07, Robert Walker <robertwalker1...> wrote:
    >>> Marcus,
    >>>
    >>>> Again no.  Fortunately when the array is retrieved all of the
    >>> objects
    >>>> are "faults" which means they are not loaded into memory yet and
    >>> your
    >>>> call to count will not cause them to load.  Therefore the memory
    >>> and
    >>>> performance hits are negligible.
    >>>
    >>> Are you sure that asking for the count would not cause the fault
    >>> representing a collection to fire?  If so that's really impressive.
    >>> How would Core Data know the number of objects represented by the
    >>> fault without realizing the fault first?
    >>>
    >>> --
    >>> Robert Walker
    >>> <robertwalker1...>
    >>>
    >>>
  • Marcus,

    Thanks for check into this for me.  I do realize the OP's question.
    My previous post was for my own desire to understand the internal
    mechanics of Core Data.  I'm really just looking to crack open that
    "black box" a bit to rise to the next level of understanding.

    It is very interesting that it made a difference fetching the objects
    in code with a fetch request, as opposed to pulling the selection out
    of the NSArrayController.  It would be interesting to know for sure
    why that is.  Now that I have your example I'll do some more
    experimenting on my own.  I quite enjoy puzzles like these.

    On May 4, 2007, at 5:08 PM, Marcus S. Zarra wrote:

    > Interesting that your example causes the faults to fire.  By removing
    > the bindings from the equation I was not able to duplicate your
    > results.
    >
    > NOTE: the OP was looking for a way to perform a "select count(*) from
    > xxx" which the above code does perform without a performance hit of
    > firing the faults.
    >
    > Marcus S. Zarra
    > Zarra Studios LLC
    > Simply Elegant Software for OS X
    > www.zarrastudios.com
    >

    --
    Robert Walker
    <robertwalker1...>
  • Maybe the array controller sorted the array and thus had to access
    each object causing it to fault?

    On 4-May-07, at 2:48 PM, Robert Walker wrote:

    > Marcus,
    >
    > Thanks for check into this for me.  I do realize the OP's
    > question.  My previous post was for my own desire to understand the
    > internal mechanics of Core Data.  I'm really just looking to crack
    > open that "black box" a bit to rise to the next level of
    > understanding.
    >
    > It is very interesting that it made a difference fetching the
    > objects in code with a fetch request, as opposed to pulling the
    > selection out of the NSArrayController.  It would be interesting to
    > know for sure why that is.  Now that I have your example I'll do
    > some more experimenting on my own.  I quite enjoy puzzles like these.
    >
    > On May 4, 2007, at 5:08 PM, Marcus S. Zarra wrote:
    >
    >> Interesting that your example causes the faults to fire.  By removing
    >> the bindings from the equation I was not able to duplicate your
    >> results.
    >>
    >> NOTE: the OP was looking for a way to perform a "select count(*) from
    >> xxx" which the above code does perform without a performance hit of
    >> firing the faults.
    >>