はじめに
Azure FunctionsでEntity Frameworkを使ってDB操作をしてみました
Entity Frameworkを使うにあたり、テストコードが書きにくかったため、DI(Dependency Injection)というソフトウェアデザインを使いました
わかりやすい記事がありましたので、以下にリンクを記載します
(自分もDIについて完全に理解しているとは言えない😶)
サンプルコードをGitHubに公開してます🎉
環境
- Visual Studio Code
- Windows11 Pro
- SQL Server Management Service
作業手順
- Visual Studio CodeでAzure Functionsのプロジェクトを作成します
- Startup.csを作成します
- モデルを作成します
- DataContext環境を作成します
- 関数でDB操作を行います
- 稼働確認します
- テストコードを書きます
- テストします
①Visual Studio CodeでAzure Functionsのプロジェクトを作成します
Azureマークをクリックしその後、WorkSpaceのFunctionマークをクリックします。
その後、以下の順にクリックしていきます。
C#
.Net6.0
HTTP trigger
HTTPTrigger(機能名、今回はデフォルトのままです)
Company.Function(namespace、今回はデフォルトのままです)
Function
②Startup.csを作成します
nugetパッケージをインストールします
dotnet add package Microsoft.Azure.Functions.Extensions
dotnet add package Microsoft.NET.Sdk.Functions
dotnet add package Microsoft.Extensions.DependencyInjection --version 6.0.1
①で作成されたプロジェクト直下にStartup.csを作成します
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
[assembly: FunctionsStartup(typeof(Company.Function.Startup))]
namespace Company.Function
{
public class Startup : FunctionsStartup
{
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
base.ConfigureAppConfiguration(builder);
}
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddFilter(level => true);
});
// 後でDBContext記載
}
}
}
③モデルを作成します
nugetパッケージをインストールします
dotnet add package Microsoft.EntityFrameworkCore --version 6.0.20
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 6.0.20
①で作成されたプロジェクト直下にModelsフォルダを作成します
その下にUserMaster.csを作成します
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Company.Function.Models
{
[Table("UserMaster")]
public class UserMaster
{
[Column("Id")]
public int Id { get; set; }
[Column("UserName")]
public string Name { get; set; }
[Column("Email")]
public string Email { get; set; }
[Column("Tel")]
public string Tel { get; set; }
[Column("Address")]
public string Address { get; set; }
[Column("Password")]
public string Password { get; set; }
[Column("IsAdmin")]
public bool IsAdmin {get; set;}
[Column("EntryDateTime")]
public DateTime EntryDateTime {get; set;}
[Column("UpdateDateTime")]
public DateTime UpdateDateTime {get; set;}
[Column("IsDeleteFlag")]
public bool IsDeleteFlag {get; set;}
}
}
④DataContext環境を作成します
インタフェースを実装します
using System.Collections.Generic;
namespace Company.Function.Models
{
public interface IUserRepository
{
IEnumerable<UserMaster> GetAll();
void SaveChanges();
}
}
DbContextを作成します
using Microsoft.EntityFrameworkCore;
namespace Company.Function.Models
{
public class UserDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Persist Security Info=False;Trusted_Connection=True;database=Poc;server=localhost\SQLEXPRESS;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserMaster>().Property(p => p.Id);
}
public DbSet<UserMaster> UserItems { get; set; }
}
}
リポジトリを作成します
using System.Collections.Generic;
namespace Company.Function.Models
{
public class UserRepository : IUserRepository
{
private readonly UserDbContext _context;
public UserRepository(UserDbContext context)
{
_context = context;
}
public IEnumerable<UserMaster> GetAll()
{
return _context.UserItems;
}
public void SaveChanges()
{
_context.SaveChanges();
}
}
}
⑤関数でDB操作を行います
HTTPTrigger1.csを編集します。
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Company.Function.Models;
namespace Company.Function
{
public class HttpTrigger1
{
private readonly IUserRepository _repo;
public HttpTrigger1(IUserRepository repo)
{
_repo = repo;
}
[FunctionName("HttpTrigger1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
ILogger log)
{
var users = _repo.GetAll();
string responseMessage = JsonConvert.SerializeObject(users);
return new OkObjectResult(responseMessage);
}
}
}
Startup.csを編集します
using Company.Function.Models;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
[assembly: FunctionsStartup(typeof(Company.Function.Startup))]
namespace Company.Function
{
public class Startup : FunctionsStartup
{
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
base.ConfigureAppConfiguration(builder);
}
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddFilter(level => true);
});
builder.Services.AddDbContext<UserDbContext>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
}
}
}
⑥稼働確認します
ステータス200で返ってくることを確認します
Invoke-WebRequest -Uri "http://localhost:7071/api/HttpTrigger1" -Method Get
⑦テストコードを書きます
テストデータはDBにアクセスせずモックデータを作成してモックデータを操作します(リポジトリパターンという設計です)
Azure Functionsとは別の階層でテストプロジェクトを作成します
テストプロジェクト作成後にAzure Functionsを参照させます
dotnet new xunit -f net6.0 -o PocFunc.Test
cd PocFunc.Test
dotnet add reference <Azure Functionsプロジェクトパス>\PoCFunc.csproj
dotnet add package Moq --version 4.18.4
dotnet add package Microsoft.AspNetCore.Mvc
dotnet add package System.Data.Entity.Repository --version 2.0.0.1
モックデータデータを作成します
テストプロジェクト配下にModelsフォルダを作成します
Models配下にTestDbSet.csを作成します
using Moq;
using Company.Function.Models;
namespace PocFunc.Test.Models
{
public class TestDbSet
{
public Mock<IUserRepository> _repo = new Mock<IUserRepository>();
private readonly List<UserMaster> _data = new List<UserMaster>()
{
new UserMaster { Id = "1", Name = "Test User 1", Email = "1234567890", Tel = "1234567890", Address = "1234567890", Password = "1234567890", IsAdmin = false, EntryDateTime = DateTime.Now, UpdateDateTime = DateTime.Now, IsDeleteFlag = false },
// 以下に同じようなデータを追加する
};
public TestDbSet()
{
_repo.Setup(x => x.GetAll()).Returns(_data);
}
}
}
UnitTest1.csを編集します
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging.Abstractions;
using PocFunc.Test.Models;
using Company.Function;
namespace PocFunc.Test
{
public class UnitTest1
{
[Fact]
public void Test1()
{
var mock = new TestDbSet();
HttpTrigger1 httpTrigger1 = new HttpTrigger1(mock._repo.Object);
var logger = NullLoggerFactory.Instance.CreateLogger("Null Logger");
var result = httpTrigger1.Run(null, logger);
Assert.IsType<OkObjectResult>(result.Result);
}
}
}
⑧テストします
以下を実行して合格が1件あることを確認します
dotnet test <テストプロジェクトのパス>\PocFunc.Test.csproj
所感
Azure FunctionsでEntity Frameworkを使ってDB操作をしてみました
EFを使うことでコードベースで色々操作できて利便性が増したと感じました
この使い方がベストプラクティスかといわれるとどうかなと思うので、まだまだ勉強を続けたいと思います