Статьи

Как получить доступ на запись к предварительно заполненной базе данных SQL CE в приложении Windows Phone

SQL CE является одной из наиболее популярных функций для разработчиков, представленных в Windows Phone 7.5: до SQL CE единственным способом хранения данных на локальном уровне была сериализация или удаленные службы (например, в облаке). Вместо этого, в SQL CE у нас есть полный доступ к реляционной базе данных, и мы можем работать со знакомыми понятиями, такими как таблицы, отношения и так далее. Windows Phone официально поддерживает два способа работы с базой данных SQL CE:

  • Сначала код: база данных создается, начиная с определения наших сущностей. Мы создаем класс для каждой сущности и с помощью некоторых специальных атрибутов база данных создается при первом запуске приложения. Эти атрибуты помогут нам определить таблицы, столбцы, отношения и так далее. Поскольку база данных будет создана при запуске приложения, она, очевидно, будет пустой.
  • Только для чтения: база данных создается из приложения с помощью внешнего инструмента (например, инструмента обозревателя сервера, который является частью Visual Studio). Этот файл будет SDF-файлом, который будет включен в проект Visual Studio. Это решение является лучшим выбором, когда у нас есть предварительно заполненная база данных, и нам нужно отобразить данные в нашем приложении. Недостатком этого подхода является то, что база данных будет только для чтения: мы сможем только читать данные из нее, у нас не будет доступа для записи.

 

По своему опыту я часто нуждался в том, чтобы иметь лучшее из обоих миров: у меня есть база данных, которая предварительно заполнена данными, которые отображаются в приложении, но в то же время я хочу иметь возможность писать что-то. Есть способ сделать это: скопировать базу данных в изолированное хранилище при первом запуске приложения. Таким образом, мы можем просто добавить базу данных в проект, как если бы мы хотели использовать ее в режиме только для чтения: после того, как мы скопировали ее в хранилище, мы сможем записать в нее данные, как если бы они были созданы в коде первый режим.

Код для достижения этого результата не совсем прост, поэтому, пожалуйста, добро пожаловать в SQL Server Compact Toolbox : я большой поклонник этого расширения, разработанного коллегой MVP ErikEJ , и я широко использовал его в своем проекте Windows Phone. Это расширение Visual Studio, поэтому его можно установить прямо из галереи Visual Studio .

Это расширение предоставляет ряд утилит, которые пригодятся при работе с базой данных SQL CE: одна из них — поддержка Windows Phone, которая позволяет, начиная с существующей базы данных SQL CE, генерировать все классы, необходимые для ее работы в приложение Windows Phone (как сущности, так и контекст данных).

А как насчет нашего сценария? Сгенерированный контекст данных содержит метод CreateIfNotExists , который переопределяет исходные методы контекста данных, которые используются для проверки, существует ли база данных, и, если нет, для ее создания. По сути, это код, который мы писали в прошлом:

using (CommunityDaysContext db = new CommunityDaysContext(CommunityDaysContext.ConnectionString))
{
    if (!db.DatabaseExists())
    {
        db.CreateDatabase();
    }
}

Используя SQL Server Compact Toolbox, мы можем просто написать:

using (CommunityDaysContext db = new CommunityDaysContext(CommunityDaysContext.ConnectionString))
{
    db.CreateIfNotExists();
}

 

Этот метод вернет true или false, чтобы отразить, была ли база данных создана или нет. Если вы посмотрите глубже на класс DataContext, сгенерированный SQL Server Compact Toolbox, вы заметите, что метод CreateIfNotExists гораздо больше, чем просто оболочка для оригинальных методов DataContext: это потому, что метод содержит всю логику, необходимую для проверки, есть ли базу данных SQL CE, встроенную в проект, и скопировать ее в изолированное хранилище.

Чтобы обмануть волшебство, нам просто нужно скопировать SDF-файл в наш проект и убедиться, что для параметра « Build Action» (которое можно изменить на панели « Свойства» ) установлено значение   EmbeddedResource .

И теперь все готово: просто вызовите CreateIfNotExists при запуске приложения (например, в событии Application_Launching внутри файла App.xaml.cs или при загрузке MainPage вашего приложения), и база данных будет скопирована в изолированное хранилище. Чтобы получить к нему доступ, вам нужно использовать обычную строку подключения (поскольку это не база данных только для чтения): сгенерированный DataContext автоматически предоставит ему статическое свойство ConnectionString .

Следите за размером базы данных!

Будьте внимательны, чем больше база данных, тем больше времени требуется для копирования базы данных в хранилище. Не забудьте предоставить анимацию загрузки в случае, если операция загрузки длится более 2 или 3 секунд, и отложить ее после полной загрузки приложения, в противном случае вы рискуете превысить 5-секундный предел времени, разрешенный для отображения заставки.

Что если база данных хранится в другом проекте?

Если вы как разработчик используете свое решение для разделения своего решения на несколько проектов (например, одно для основного приложения, другое для служб и т. Д.), Вы можете оказаться в ситуации, когда база данных хранится в одном проекте, в то время как объекты и контекст данных в другом.

В этом случае код, сгенерированный SQL Server Compact Toolbox, не будет работать, потому что метод CreateIfNotExists будет искать базу данных в той же сборке приложения (используя отражение). Не волнуйтесь, есть обходной путь! Первым шагом является изменение действия Build для базы данных на Content , затем вам нужно изменить метод CreateIfNotExists следующим образом:

public bool CreateIfNotExists()
{
    bool created = false;
    using (var db = new DbContext(DbContext.ConnectionString))
    {
        if (!db.DatabaseExists())
        {
            //string[] names = this.GetType().Assembly.GetManifestResourceNames();
            //string name = names.Where(n => n.EndsWith(FileName)).FirstOrDefault();
            StreamResourceInfo stream = Application.GetResourceStream(new Uri("Database/Recipes.sdf", UriKind.Relative));
            if (stream != null)
            {
                using (Stream resourceStream = stream.Stream)
                {
                    if (resourceStream != null)
                    {
                        using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
                        {
                            using (IsolatedStorageFileStream fileStream = new IsolatedStorageFileStream(FileName, FileMode.Create, myIsolatedStorage))
                            {
                                using (BinaryWriter writer = new BinaryWriter(fileStream))
                                {
                                    long length = resourceStream.Length;
                                    byte[] buffer = new byte[32];
                                    int readCount = 0;
                                    using (BinaryReader reader = new BinaryReader(resourceStream))
                                    {
                                        // read file in chunks in order to reduce memory consumption and increase performance
                                        while (readCount < length)
                                        {
                                            int actual = reader.Read(buffer, 0, buffer.Length);
                                            readCount += actual;
                                            writer.Write(buffer, 0, actual);
                                        }
                                    }
                                }
                            }
                        }
                        created = true;
                    }
                    else
                    {
                        db.CreateDatabase();
                        created = true;
                    }
                }
            }
            else
            {
                db.CreateDatabase();
                created = true;
            }
        }
    }
    return created;
}

В этом коде мы изменили загрузку путем отражения с помощью метода Application.GetResourceStream , который может загружать ресурс внутри XAP (поэтому он работает, даже если база данных не находится в том же проекте), как Stream. Этот метод требует в качестве входного параметра Uri файла (начиная с корня проекта): в этом примере база данных называется Recipes.sdf и помещается в папку базы данных .

Вот и все! Веселитесь вместе с SQL CE и Windows Phone!