Я создал файл questions.db с помощью DB Browser.

Вот он: Мой прекрасный файл .db

Я хочу добавить его в решение Xamarin.Forms и прочитать из него данные. (Это будет приложение-викторина)

Звучит довольно просто, верно, но я часами гуглю. Большинство ответов просто ссылаются на этот https:// Learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/databases

Что ничего не объясняет о файле базы данных, созданном вручную, о том, куда его поместить и т. д.

РЕДАКТИРОВАТЬ: это то, чего я пытаюсь достичь: https://arteksoftware.com/deploying-a-database-file-with-a-xamarin-forms-app/

К сожалению, руководство устарело, и многое в нем не работает.

Итак, куда мне поместить мой файл .db? В ресурсах и ресурсах MyApp.Android для MyApp.iOS? Если да, то как мне получить строку DBpath для нового подключения?

_database = new SQLiteAsyncConnection(dbPath);
3
mm_archeris 11 Ноя 2019 в 15:39
Вам нужно просто читать данные, или вам также нужно будет писать в них?
 – 
Jason
11 Ноя 2019 в 15:41
Хотя бы просто читать пока.
 – 
mm_archeris
11 Ноя 2019 в 15:44
1
NSBundle.MainBundle.PathForResource("MyLite", "db"); должен дать вам путь к ресурсам. Обратите внимание, что файлы в пакете приложений доступны только для чтения.
 – 
Jason
11 Ноя 2019 в 18:34
Поместите файл .db в проект xxx.iOS и проект xxx.Android, используйте служба зависимостей, чтобы получить путь от каждой платформы. Взгляните на эту тему, которая может помочь. Дайте знать, если у вас появятся вопросы.
 – 
nevermore
12 Ноя 2019 в 05:52
Я заработал. Для кого-то в будущем - вот ссылка с руководством, что нужно сделать в значительной степени: stackoverflow.com/questions/42092607/…
 – 
mm_archeris
17 Ноя 2019 в 23:35

1 ответ

Если вы хотите сделать следующее:

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


Короткий ответ

Вам нужно добавить файл базы данных в определенную папку в проекте paltform (Assets в Android, в iOS вы можете просто создать папку и поместите туда файл) и используйте DependencyService для доступа к нему. Скопируйте файл базы данных куда-нибудь на свое устройство (папка данных приложения или InternalStorage или любой другой вариант разрешены на каждой платформе) и используйте этот окончательный путь в конструкторе SQLite. Отныне вы можете просто использовать это соединение SQLite, как и любое другое, для выполнения операции CRUD в вашей базе данных.


Длинный ответ

Теперь я опишу, как добиться этого шаг за шагом, от создания нового проекта Xamarin.Forms до чтения данных из предварительно загруженной базы данных.

Примечание. Следующее решение будет охватывать только часть кроссплатформенного решения Android. Расширение этого на iOS не должно быть проблемой.

Отказ от ответственности: код, представленный далее, следует рассматривать только как руководство, и я ни в коем случае не утверждаю, что он проверен или похож на производственный.


0. Подготовка проекта:

Создайте новый проект Xamarin.Forms (в моем случае я использую Visual Studio v. 16.3.8 и в новом проекте установлен Xamarin.Forms 4.2)

enter image description here

Затем, прежде всего, установите пакет sqlite nuget во ВСЕ ваши проекты:

enter image description here


1. Подготовьте базу данных

В файле App.xaml.cs

// Create db static property to perform the 
// Database calls from around the project
public static Database db { get; set; }

public App()
{
    InitializeComponent();


    MainPage = new MainPage();
}

Создайте новый файл класса и назовите его Database.cs. Там вы открываете соединение с базой данных.

public class Database
{
    // Create a SQLiteAsyncConnection property to be accessed 
    // publicly thru your App.
    public SQLiteAsyncConnection DBInstance { get; set; }

    public Database(String databasePath)
    {
        DBInstance = new SQLiteAsyncConnection(databasePath);

    }


    private async Task<string> GetDatabaseFilePath()
    {
        return await DependencyService.Get<IPathFinder>().GetDBPath();
    }
}

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


2. Загрузите уже существующую базу данных в свой проект и получите путь к ней (Android)

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

Один из вариантов — добавить его в качестве актива. Чтобы реализовать этот подход, сделайте следующее:

2.1 Добавить файл базы данных в папку Assets в проекте Android

enter image description here

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

2.2 Создайте интерфейс IPathFinder в своем проекте приложения

enter image description here

И там определите один элемент GetDBPath()

public interface IPathFinder
{

    Task<String> GetDBPath();

}

Теперь в вашем проекте Android создайте файл класса PathFinder.cs для реализации этого интерфейса.

enter image description here

И реализуйте метод (обратите внимание на атрибут зависимости над пространством имен!)

using System;
using SysIO = System.IO;
using System.Threading.Tasks;

using Java.IO;

using Xamarin.Forms;

using TestDB.Droid;

[assembly: Dependency(typeof(PathFinder))]
namespace TestDB.Droid
{
    public class PathFinder : IPathFinder
    {
        public async Task<string> GetDBPath()
        {

            String dbPath = String.Empty;

            if (await PermissonManager.GetPermission(PermissonManager.PermissionsIdentifier.Storage))
            {

                String systemPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.RootDirectory.Path).Path;

                String tempFolderPath = SysIO::Path.Combine(systemPath, "MytestDBFolder");
                if (!SysIO::File.Exists(tempFolderPath))
                {
                    new File(tempFolderPath).Mkdirs();
                }

                dbPath = SysIO::Path.Combine(tempFolderPath, "test.db");

                if (!SysIO::File.Exists(dbPath))
                {
                    Byte[] dbArray;
                    using (var memoryStream = new SysIO::MemoryStream())
                    {
                        var dbAsset = MainActivity.assets.Open("test.db");
                        dbAsset.CopyTo(memoryStream);
                        dbArray = memoryStream.ToArray();
                    }    

                    SysIO.File.WriteAllBytes(dbPath, dbArray);

                }    

            }    

            return dbPath;
        }
    }
}

В методе GetDBPath() первым следует отметить метод GetPermission. Это необходимо, начиная с Android API 23, для управления разрешениями приложений.

Создайте файл с именем PermissonManager в вашем проекте Android.

enter image description here

И добавьте код ниже

using System;
using System.Threading.Tasks;
using Android;
using Android.App;
using Android.Content.PM;
using Android.OS;
using Android.Support.V4.App;

namespace TestDB.Droid
{
    public class PermissonManager
    {

        public enum PermissionsIdentifier
        {
            Storage // Here you can add more identifiers.
        }

        private static String[] GetPermissionsRequired(PermissionsIdentifier identifier)
        {

            String[] permissions = null;


            if (identifier == PermissionsIdentifier.Storage)

                permissions = PermissionExternalStorage;


            return permissions;

        }

        private static Int32 GetRequestId(PermissionsIdentifier identifier)
        {

            Int32 requestId = -1;


            if (identifier == PermissionsIdentifier.Storage)

                requestId = ExternalStorageRequestId;


            return requestId;

        }


        public static TaskCompletionSource<Boolean> PermissionTCS;


        public static readonly String[] PermissionExternalStorage = new String[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage };

        public const Int32 ExternalStorageRequestId = 2;



        public static async Task<Boolean> GetPermission(PermissionsIdentifier identifier)
        {

            Boolean isPermitted = false;

            if ((Int32)Build.VERSION.SdkInt < 23)

                isPermitted = true;

            else

                isPermitted = await GetPermissionOnSdk23OrAbove(GetPermissionsRequired(identifier), GetRequestId(identifier));


            return isPermitted;

        }



        private static Task<Boolean> GetPermissionOnSdk23OrAbove(String[] permissions, Int32 requestId)
        {

            PermissionTCS = new TaskCompletionSource<Boolean>();

            if (MainApplication.CurrentContext.CheckSelfPermission(permissions[0]) == (Int32)Permission.Granted)

                PermissionTCS.SetResult(true);

            else

                ActivityCompat.RequestPermissions((Activity)MainApplication.CurrentContext, permissions, requestId);


            return PermissionTCS.Task;

        }


        public static void OnRequestPermissionsResult(Permission[] grantResults)
        {

            PermissionTCS.SetResult(grantResults[0] == Permission.Granted);

        }

    }
}

В этом классе вы заметите наличие MainApplication, который предоставляет CurrentContext. Вам также нужно будет добавить этот файл класса

enter image description here

И добавьте следующий код

using System;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;


namespace DemoDB.Droid
{
    [Application]
    public partial class MainApplication : Application, Application.IActivityLifecycleCallbacks
    {

        private static Context _currentContext = Application.Context;
        internal static Context CurrentContext
        {
            get => _currentContext;
            private set
            {
                _currentContext = value;
            }
        }


        internal static String FileProviderAuthority
        {
            get => MainApplication.CurrentContext.ApplicationContext.PackageName + ".fileprovider";
        }


        public MainApplication(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer)
        {

        }


        public override void OnCreate()
        {

            base.OnCreate();

            RegisterActivityLifecycleCallbacks(this);

        }


        public override void OnTerminate()
        {

            base.OnTerminate();

            UnregisterActivityLifecycleCallbacks(this);

        }


        public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
        {

            CurrentContext = activity;

        }


        public void OnActivityDestroyed(Activity activity)
        {

        }


        public void OnActivityPaused(Activity activity)
        {

        }


        public void OnActivityResumed(Activity activity)
        {

            CurrentContext = activity;

        }


        public void OnActivitySaveInstanceState(Activity activity, Bundle outState)
        {

        }


        public void OnActivityStarted(Activity activity)
        {

            CurrentContext = activity;

        }


        public void OnActivityStopped(Activity activity)
        {

        }    
    }
}

Теперь ваш PermissonManager почти готов. Теперь вам просто нужно переопределить OnRequestPermissionsResult в файле MainActivity.

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
    PermissonManager.OnRequestPermissionsResult(grantResults);
    base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}

Возвращаясь к методу GetPath(), вы видите таинственный вызов свойства MainActivity.assets. Это должно быть создано в MainActivity следующим образом

public static AssetManager assets;

protected override void OnCreate(Bundle savedInstanceState)
{
    TabLayoutResource = Resource.Layout.Tabbar;
    ToolbarResource = Resource.Layout.Toolbar;

    assets = this.Assets;

    base.OnCreate(savedInstanceState);

    Xamarin.Essentials.Platform.Init(this, savedInstanceState);
    global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
    LoadApplication(new App());
}

Почти готов! Теперь вы просто вызываете свою базу данных.

3. Используйте свою базу данных!

Из OnAppearing главной страницы сделайте простой вызов базы данных, чтобы создать ее и получить к ней доступ.

protected override async void OnAppearing()
{
    String databasePath = await Database.GetDatabaseFilePath();

    App.db = new Database(databasePath);

    var table = await App.db.DBInstance.CreateTableAsync<Category>();

    // here Category is a class that models the objects 
    // present in my pre-existent database
    List<Category> categories = new List<Category>();

    categories = await App.db.DBInstance.Table<Category>().ToListAsync();

    base.OnAppearing();
}

Вот и все.

Я надеюсь, что это полезно: P

3
deczaloth 12 Ноя 2019 в 13:36
Я скопировал почти все, но, к сожалению, у меня не получилось. Использование отладчика в самом конце databasePath равно "/storage/emulated/0/system/MytestDBFolder/questions.db", но количество категорий равно 0. Есть ли у вас какие-либо советы о том, как отлаживать, где именно мое соединение с БД не работает?
 – 
mm_archeris
12 Ноя 2019 в 20:17
@mm_archeris, если вы запустили "var table = await App.db.DBInstance.CreateTableAsync();" без исключений я ожидаю, что ваша БД в порядке. Кроме того, в 3-й части моего ответа в коде переопределения OnAppearing я написал: «// здесь категория — это класс, который моделирует объекты, // присутствующие в моей уже существующей базе данных». Это означает, что в базе данных, которую я использовал, уже есть элементы, она не пуста. В нем есть элементы, которые я назвал Категорией, среди прочего. Вы должны знать, каково содержимое файла базы данных, который вы используете, или если он пуст. В любом случае вы можете совершать обычные звонки.
 – 
deczaloth
12 Ноя 2019 в 22:59
@mm_archeris, как только вы вызовете «App.db = новая база данных (databasePath);» вы должны иметь возможность делать обычные вызовы App.db.DBInstance, такие как вставка, обновление и т. д. Чтобы узнать, как это сделать, используйте уже упомянутое вами руководство: learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/…
 – 
deczaloth
12 Ноя 2019 в 23:04
Но для EntityFrameworkCore? Должен ли я создать строку DBPath в классе DBContext и установить ее в проекте Android, например «var pF = new PathFinder(); dBContext.DBPath = await pF.GetDBPath();» перед DBEntitiesContext(); создается в приложении Xamarin (скажем, с помощью кнопки в Xamarin.Forms.ContentPage)? Похоже, это не работает, к сожалению. Без вашего решения ошибка заключалась в том, что «SQLite не может получить доступ к файлу базы данных, который был «db.db3». Теперь он выдает InvalidCastException после строки «return isPermitted» (что неверно) в GetPermission в классе PermissionManager.
 – 
Ivan Silkin
12 Окт 2021 в 20:17