読者です 読者をやめる 読者になる 読者になる

かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Azure MobileApps + Xamarin.Forms開発の始め方(.NETバックエンド + Prism.Forms)「DBの変更」

過去記事

DBの変更

JavaScriptがバックエンドのときはいい感じにDBの変更をしてくれる(それはそれで怖いっちゃ怖いけど)んですが、.NETバックエンドだとそういう感じにはなっていません。 というか、どうやるのか書いてません。書いてませんがEntityFramework 6.0を使ってるのでおそらくマイグレーションするのでしょう。

Entity Framework Code First Migrations

とりあえず、今回はローカルDBは考えてなかったのでWeb.configの接続文字列にクラウドの接続文字列を追加しましょう(これは悪手なので後で正しいやり方をフォローする記事を書く(予定))

そして、パッケージマネージャーコンソールを開いて、既定のプロジェクトをMobile Appsのサーバープロジェクトに変更して以下のコマンドを打ち込みます。

PM> Enable-Migrations

試しに、UserIdを追加したいので、TodoItemUserId列を追加します。

using Microsoft.Azure.Mobile.Server;

namespace SampleMobileApp.DataObjects
{
    public class TodoItem : EntityData
    {
        public string Text { get; set; }

        public bool Complete { get; set; }

        public string UserId { get; set; }
    }
}

そして、TodoControllerにUserIdでのフィルタリング等を追加します。 ただ、このロジックはIDを知ってる人なら、その情報にはアクセスできるという前提で動いています。(それ以上堅牢なロジックの組み方がわからなかったので、これが限界なのかも)

using System.Linq;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.OData;
using Microsoft.Azure.Mobile.Server;
using SampleMobileApp.DataObjects;
using SampleMobileApp.Models;
using System.Security.Claims;

namespace SampleMobileApp.Controllers
{
    [Authorize]
    public class TodoItemController : TableController<TodoItem>
    {
        protected override void Initialize(HttpControllerContext controllerContext)
        {
            base.Initialize(controllerContext);
            MobileServiceContext context = new MobileServiceContext();
            DomainManager = new EntityDomainManager<TodoItem>(context, Request);
        }

        // GET tables/TodoItem
        public IQueryable<TodoItem> GetAllTodoItems()
        {
            var sid = (this.User as ClaimsPrincipal)?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
            return Query().Where(x => x.UserId == sid);
        }

        // GET tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public SingleResult<TodoItem> GetTodoItem(string id)
        {
            return Lookup(id);
        }

        // PATCH tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public Task<TodoItem> PatchTodoItem(string id, Delta<TodoItem> patch)
        {
            return UpdateAsync(id, patch);
        }

        // POST tables/TodoItem
        public async Task<IHttpActionResult> PostTodoItem(TodoItem item)
        {
            item.UserId = (this.User as ClaimsPrincipal)?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
            TodoItem current = await InsertAsync(item);
            return CreatedAtRoute("Tables", new { id = current.Id }, current);
        }

        // DELETE tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public Task DeleteTodoItem(string id)
        {
            return DeleteAsync(id);
        }
    }
}

そして、マイグレーションを実行します。以下のコマンドをパッケージマネージャーコンソールでたたきましょう。

Add-Migration AddUserId

こんな感じのファイルが作られます。

namespace SampleMobileApp.Migrations
{
    using System;
    using System.Data.Entity.Migrations;
    
    public partial class AddUserId : DbMigration
    {
        public override void Up()
        {
            AddColumn("dbo.TodoItems", "UserId", c => c.String());
        }
        
        public override void Down()
        {
            DropColumn("dbo.TodoItems", "UserId");
        }
    }
}

いい感じですね。ついでに、自動でマイグレーションしてくれるようにしましょう。

App_Startの下のStartup.MobileApp.csにあるMobileServiceInitializerを以下のように書き換えます。

class MobileServiceInitializer : MigrateDatabaseToLatestVersion<MobileServiceContext, SampleMobileApp.Migrations.Configuration>
{
}

これで自動でマイグレーション走るようになります。これが嫌なケースの場合は、別途マイグレーションのSQLを生成する方法を最初に示したマイグレーションについて書かれたページで参照してみてください。

発行して実行すると以下のようになります。

f:id:okazuki:20160921223917p:plain

何も違いがわかりませんね。なかなか複数アカウント持ってないので確認しづらいところではあります…。ということで、DBを見てみましょう。

f:id:okazuki:20160921224341p:plain

UserId列が追加されて、値が入ってますね!!そして、DBにいろいろ値が入ってるけどちゃんとフィルタリングした結果が出てるっぽいですね!やったね!