Создавать классы из модели CoreData

Я пытаюсь разработать пару приложений C #, одно iOS, одно Mac, которые используют одну и ту же базу данных CoreData. Фактически, настольное приложение заполняет его, а затем распространяется как часть приложения iOS.

Я могу использовать XCode для создания .xcdatamodeld файла, описывающего, как должна выглядеть база данных. Я могу использовать momc, чтобы скомпилировать это в .momd файл. Я могу включить .mom файл из него в мой проект Mono и загрузить его в NSManagedObjectModel, из которого я могу получить доступ ко всем свойствам различных сущностей.

Я еще не понял, как это сделать, так это создать объект класса из базы данных, а не обращаться к свойствам таблицы. Какие-либо предложения?


Чтобы уточнить: я хочу иметь возможность создавать таблицу / класс в XCode, назовите это Person. Я даю ему два поля: Name и Phone. Я хочу иметь возможность запускать подобный код в Mono:

using (var context = new NSManagedObjectContext())
{
  var me = context.Person.GetByID(1);
  me.Name = "Bobson";
  context.Save();
}

Очевидно, что особенности получения его из базы данных и его сохранения обратно будут разными, но суть есть.


person Bobson    schedule 03.09.2012    source источник
comment
Спасибо за добавленную награду, @kdmurray!   -  person Bobson    schedule 24.09.2012
comment
возможно, вам потребуется уточнить свой вопрос. Мне непонятно, что вам нужно.   -  person TrustMe    schedule 25.09.2012
comment
@TrustMe - По сути, я ищу способ использовать конструктор XCode CoreData для создания файла .mom и некоторых классов точно так же, как я могу использовать конструктор Entity Framework для создания файла .edmx и некоторых классов, или Конструктор LINQ-to-SQL для создания файла .dbml и некоторых классов. Генерация классов - это та часть, которая до сих пор ускользала от меня, хотя я как раз собираюсь попробовать ваш ответ.   -  person Bobson    schedule 26.09.2012
comment
Вы не думали об использовании CoreData? Поскольку вы разрабатываете на C #, наиболее распространенным подходом было бы использование одного из многих .NET ORM, которые были перенесены / работают на MonoTouch. Я использую Catnap. Вы можете создать низкоуровневую схему с помощью настольного клиента SQLite. Я использую Navicat. Преимущество этого подхода в том, что вы можете использовать один и тот же код на Android и других платформах, если вам когда-нибудь понадобится.   -  person t9mike    schedule 26.09.2012
comment
@ t9mike - я думал об этом, но такие вещи, как stackoverflow.com/a/1045560/298754 и различные обсуждения рук - Оптимизация, необходимая для базы данных SQLLite на iPhone, склонила меня не делать этого, если бы я мог заставить CoreData работать. Этот конкретный ответ устарел на три года, но я не припомню, чтобы я нашел что-то более новое, что касалось его. Относительная производительность и так далее по-прежнему проблема?   -  person Bobson    schedule 27.09.2012
comment
У меня нет опыта работы с CoreData, но я не могу представить, чтобы правильно спроектированное решение ORM + SQLite было бы медленнее, чем CoreData. ORM не добавляют много накладных расходов и, как правило, настраиваются в отношении отложенной загрузки. Но вам нужно разработать схему с большинством (возможно, со всеми?) Из них. Например, мне нужно было немного оптимизировать уровень моей БД, чтобы работать как можно быстрее. У меня очень большой текстовый столбец, который я загружаю лениво. Переместив этот столбец во вспомогательную таблицу, я повысил производительность поиска и загрузки столбцов, загружаемых без отложенного выполнения.   -  person t9mike    schedule 27.09.2012
comment
Если вы выберете это здесь, возможно, вам поможет список рассылки MonoTouch. Или страницу поддержки Xamarin.   -  person t9mike    schedule 27.09.2012
comment
@ t9mike - Кажется, не так уж и плохо. Я мог бы просто пойти по этому пути - кроссплатформенность это очень хороший момент.   -  person Bobson    schedule 27.09.2012
comment
Ага, это оставит вам большую гибкость для будущих платформ. Я смог поделиться своим уровнем данных между клиентом и сервером, пройдя этот маршрут. И сейчас я работаю над версией приложения для Android, так что еще одна большая победа (она просто сработала ;-).   -  person t9mike    schedule 27.09.2012
comment
@ t9mike - Подумайте об этом, это также решило бы проблему, с которой я столкнулся, когда библиотеки MonoTouch CoreData и библиотеки MonoMac CoreData не могут использоваться одновременно в одном проекте. (Приложение Mac заполняет базу данных, и приложение iOS будет ее использовать.)   -  person Bobson    schedule 27.09.2012
comment
@ t9mike, вы должны опубликовать это как ответ, который Бобсон мог бы принять ...   -  person kdmurray    schedule 30.09.2012
comment
@kdmurray - я не совсем уверен, что он действительно решает вопрос, поскольку это обходной путь, который может не сработать для других, но я все равно приму его.   -  person Bobson    schedule 30.09.2012
comment
@Bobson Я понимаю, что это не идеальный ответ, но поскольку он самый близкий к нам, я награжу там награду, если ответ появится ...   -  person kdmurray    schedule 30.09.2012
comment
@ t9mike - Вы пропустили награду, но я все равно приму ее, если вы опубликуете это предложение в качестве ответа.   -  person Bobson    schedule 10.10.2012


Ответы (3)


Использовать CoreData в MonoTouch несложно. Вам нужно сгенерировать маму с помощью momc.exe. Я успешно это сделал.

Если вы поместите файл на верхний уровень каталога приложения (возможно, другие места тоже будут работать) и вы установите действие сборки на «BundleResource» (возможно, другие действия тоже сработают). Создание экземпляра UIManagedDocument приведет к тому, что CoreData автоматически выберет файл mom, поэтому вам не нужно будет создавать его самостоятельно, а также вы получите преимущество поддержки iCloud.

Вам нужно будет самостоятельно сгенерировать файлы классов. Должно быть легко написать сценарий, который может считывать файл xcdatamodel и выдавать C #. Это пример того, как это должно выглядеть.

public partial class Photographer : NSManagedObject
{
    public static NSString NameKey = (NSString) "name";
    public static NSString PhotosKey = (NSString) "photos";

    public Photographer(IntPtr handle) : base(handle)
    {
    }

    public string Name
    {
        get { return (NSString) Runtime.GetNSObject(ValueForKey(NameKey)); }
        set { SetValueForKey(value, NameKey); }
    }

    public NSSet Photos
    {
        get { return (NSSet) Runtime.GetNSObject(ValueForKey(PhotosKey)); }
        set { SetValueForKey(value, PhotosKey); }
    }

    void SetValueForKey(string value, NSString key)
    {
        base.SetValueForKey((NSString)(value ?? ""), key);
    }

    public static Photographer InsertNewObject(NSManagedObjectContext context)
    {
        return (Photographer) NSEntityDescription.InsertNewObjectForEntityForName("Photographer", context);
    }

    public static Photographer WithName(string name, NSManagedObjectContext context)
    {
        Photographer photographer = null;

        // This is just like Photo(Flickr)'s method.  Look there for commentary.

        if (name.Length > 0)
        {
            var request = new NSFetchRequest("Photographer")
            {
                SortDescriptors = new[] {new NSSortDescriptor("name", true, new Selector("localizedCaseInsensitiveCompare:"))},
                Predicate =  NSPredicate.FromFormat("name = %@", new NSObject[] {(NSString) name})
            };

            NSError error;
            var matches = context.ExecuteFetchRequest(request, out error);

            if (matches == null || matches.Length > 1)
            {
                // handle error
            }
            else if (matches.Length == 0)
            {
                photographer = InsertNewObject(context);
                photographer.Name = name;
            }
            else
            {
                photographer = (Photographer) matches.First();
            }
        }

        return photographer;
    }
}

public partial class Photo : NSManagedObject
{
    public static NSString ImageUrlKey = (NSString) "imageURL";
    public static NSString TitleKey = (NSString) "title";
    public static NSString UniqueKey = (NSString) "unique";
    public static NSString SubtitleKey = (NSString) "subtitle";
    public static NSString WhoTookKey = (NSString) "whoTook";

    public Photo (IntPtr handle) : base (handle)
    {
    }

    public string ImageUrl  {
        get { return (NSString)Runtime.GetNSObject(ValueForKey(ImageUrlKey)); }
        set { SetValueForKey(value, ImageUrlKey); }
    }

    public string Subtitle  {
        get { return (NSString)Runtime.GetNSObject(ValueForKey(SubtitleKey)); }
        set { SetValueForKey(value, SubtitleKey); }
    }

    public string Title  {
        get { return (NSString)Runtime.GetNSObject(ValueForKey(TitleKey)); }
        set { SetValueForKey(value, TitleKey); }
    }

    public string Unique  {
        get { return (NSString)Runtime.GetNSObject(ValueForKey(UniqueKey)); }
        set { SetValueForKey(value, UniqueKey); }

    }

    public Photographer WhoTook  {
        get { return (Photographer)Runtime.GetNSObject(ValueForKey(WhoTookKey)); }
        set { SetValueForKey(value, WhoTookKey); }
    }

void SetValueForKey(string value, NSString key)
{
    base.SetValueForKey((NSString) (value??""), key);
}

    public static Photo InsertNewObject(NSManagedObjectContext context)
    {
        return (Photo) NSEntityDescription.InsertNewObjectForEntityForName("Photo", context);
    }

    public UIImage Image
    {
        get {
            if (string.IsNullOrEmpty(ImageUrl))
                return null;

            var imageData = NSData.FromUrl(new NSUrl(ImageUrl));
            if (imageData == null)
                return null;
            return new UIImage(imageData);
        }
    }
person Wesner Moise    schedule 25.04.2013
comment
Прошло много времени с тех пор, как я пытался работать с этим - я отказался и перешел на SQLLite ORM. Но я попробую это сделать в следующий раз, когда буду работать над проектом, потому что мне бы хотелось вернуться к CoreData. - person Bobson; 25.04.2013
comment
Ключевые моменты: добавьте атрибут Register для идентификации имени класса ObjC и конструктор с аргументом IntPtr, чтобы Monotouch мог создавать классы C # на лету. Избегайте добавления полей экземпляров в класс C # (за исключением тех, которые кэшируют значения, которые можно легко пересчитать), потому что новые экземпляры объектов C # могут создаваться для одной и той же строки при каждой выборке. Свойства должны вызывать ValueForKey и SetValueForKey, чтобы использовать базовую поддержку динамических свойств, предлагаемую NSManagedObject. Функции Messaging.objc_send полезны для уменьшения выделения gc. - person Wesner Moise; 03.05.2013
comment
Кроме того, напишите сценарий, который читает файл xcdatamodel для создания частичных классов. Файл xcdatamodel - это простой для чтения XML. Также для свойств коллекции вы можете создать типизированные ICollections ‹› вместо NSSet. - person Wesner Moise; 03.05.2013

Я считаю, что это то, что вам нужно:

public partial class Item : NSManagedObject
{
   internal Item(string entityName)
      : base(NSEntityDescription)m_managedObjectModel.EntitiesByName.ObjectForKey(new NSString(entityName)), m_managedObjectContext)

Где entityName - это имя из MOM.

person TrustMe    schedule 24.09.2012
comment
Я не думаю, что это все, или, по крайней мере, не все - я все еще не могу получить доступ к полям данных в Item. Уточню вопрос. - person Bobson; 26.09.2012

Согласно комментарию @ t9mike (который я приму, если он когда-нибудь ответит), я отказался от использования CoreData в пользу более кроссплатформенного подхода. В итоге я использовал Vici CoolStorage для ORM, хотя мне пришлось встроить исходный код в свой проект, чтобы он заработал.

person Bobson    schedule 25.10.2012