CoreData - can't set empty string as default v

2020-05-29 13:31发布

问题:

I have an entity in my datamodel with a string attribute that is currently optional, and I'd like to convert this to a required attribute with a default value of the empty string.

As others have discovered, leaving the default value blank in the Xcode CoreData data modeller results in validation errors (since the designer interprets this as NULL), but trying '', "", or @"" as the default value results in those literal characters being interpreted as the default, rather than the empty zero-length string, as desired.

I did find this thread on Google, however, apart from the solution being really ugly (model definition split between the .xcdatamodel and objc source), it also doesn't work for lightweight migrations because those migrations are done solely based on the .xcdatamodel files and the objc logic from your entity implementations isn't loaded.

Is there any way to achieve this in the data model designer?

回答1:

This is a very interesting question. After some testing I don't think this is possible because of the way the text field in the data model is configured.

In principle, you could use the unicode empty-set character of \u2205 to represent a default empty string but the text field does not seem to accept any escapes so it converts any attempt to escape a unicode character code to the literal string of the code characters themselves e.g. entering '\u2205' ends up as the literal text '\u2205'.

In theory you could write a utility app to read in the graphically generated managed object model file and then programmatically set the attribute default to equal an empty string and then save the file back to disk. I say "in theory" because there is no documented way to way to save a managed object model file from code. You can read one and modify it in memory but not persist the changes.

Bit of an oversight, I think.

I don't think you have any choice but to set the default empty string pragmatically when the model first loads. That is simple to do but it's ugly and you'll have to remember you did (especially if you migrate versions) but I think right now that is the only choice.



回答2:

Whip out your favorite XML editor (I just used Emacs) and dive down to the contents file inside the .xcdatamodel bundle inside the .xcdatamodeld bundle. Then just add a defaultValueString="" XML attribute to the <attribute>...</attribute> element inside the <entity>...</entity> brackets. Here's an example:

<attribute name="email" attributeType="String" defaultValueString="" syncable="YES"/>

I can't speak to whether this survives migration since I haven't had to do that yet.



回答3:

I resolved this by overriding the getter for my field - if it contains null, I return an empty string instead:

-(NSString *)unit {
    if ([self primitiveValueForKey:@"unit"] == NULL) {
        return @"";
    } else {
        return [self primitiveValueForKey:@"unit"];
    }
}

So far it seems to be doing the trick, and I would imagine it wouldn't impact migrations (although I don't know enough about them to say for sure). I don't really care whether there's a null or an empty string in the db, after all - so long as I get "" instead of null when I ask for the field.



回答4:

My approach to resolving this issue was to create an NSManagedObject subclass and handle the substitution of empty strings for NULL values in awakeFromInsert. I then set all entities as children of this subclass rather than children of NSManagedObject. The assumption here is that I want every string attribute within a given entity to be set to an empty string by default (it wouldn't work, or would at least require extra logic, if you wanted some to remain NULL within the same entity).

There's probably a more efficient way of doing this, but since it's only called upon entity creation, I don't think it is too much of a performance hit.

- (void)awakeFromInsert {
    [super awakeFromInsert];

    NSDictionary *allAttributes = [[self entity] attributesByName];
    NSAttributeDescription *oneAttribute;

    for (NSString *oneAttributeKey in allAttributes) { 
        oneAttribute = [allAttributes objectForKey:oneAttributeKey];
        if ([oneAttribute attributeType] == NSStringAttributeType) {
            if (![self valueForKey:[oneAttribute name]]) {
                [self setValue:@"" forKey:[oneAttribute name]];
            }
        }
    }
}


回答5:

You can do it manually. In your model class, override awakeFromInsert and set your strings to empty string

Swift:

override func awakeFromInsert()
{
    super.awakeFromInsert()
    self.stringProperty = ""
}

Objective-C

- (void) awakeFromInsert
{
    [super awakeFromInsert];
    self.stringProperty = @"";
}


回答6:

Here is the Swift solution based on David Ravetti's answer and edelaney05's comment. In addition, I added optionality check.

This solution works fine in my projects.

class ExampleEntity: NSManagedObject {

    ...

    override func awakeFromInsert() {
        super.awakeFromInsert()
        for (key, attr) in self.entity.attributesByName {
            if attr.attributeType == .stringAttributeType && !attr.isOptional {
                if self.value(forKey: key) == nil {
                    self.setPrimitiveValue("", forKey: key)
                }
            }
        }
    }

    ...
}


回答7:

A simpler solution based on Scott Marks answer to avoid syntax errors:

First, temporarily set the default value to be easy to find, something like here you are. Open with any text editor the contents file inside the .xcdatamodel bundle inside the .xcdatamodeld bundle. Then just do a search with replacing the string "here you are" with the "" in this file.

The migration took place without problems.



回答8:

Maybe I'm late with this answer, but I was Googling and found this forum.

The solution is very simple:

  1. When you click on the xcdatamodelId (On the left screen)
  2. Change the Entity View to Graph
  3. Double Click on any Attribute you want and the menu will appear on the right.

All changes are easy.

Part 2

Part 3