[Azure] Redis Cache の実装

パフォーマンスの向上のためにCacheを導入。 Readが多いjsonデータを扱うのにお勧め。

例えば、今全国の地域を表示するページがあります。都道府県がDBに格納されているのですが、ま・・そんなに変わるものではないのでDBに格納しておきながらもCache上にも持たせてページ表示のときはCacheのデータを見る、こんな動きを想定しています。

# 参照先

https://azure.microsoft.com/ja-jp/services/cache/

実装方法はここを参照しました。

https://docs.microsoft.com/ja-jp/azure/azure-cache-for-redis/cache-web-app-aspnet-core-howto?tabs=core6x

まずは、キャッシュを管理するクライアントを作ります。Nuget上にそのライブラリーがありますのでそちらを使います。

https://www.nuget.org/packages/StackExchange.Redis/

# Before

今どうなっているかというと。 こんな感じです。DBにアクセスして指定した国ID傘下にあるエリア一覧とそのエリア内にある都道府県をJoinして出力しています。

```c#

        /// <summary>
        /// Get regions by area id.
        /// </summary>
        /// <param name="areaId"></param>
        /// <param name="keyword"></param>
        /// <param name="sort"></param>
        /// <param name="currentPage"></param>
        /// <param name="itemsPerPage"></param>
        /// <returns></returns>
        public PaginationModel<RegionModel> GetRegionsByAreaId(string areaId, string keyword, string sort, int currentPage, int itemsPerPage)
        {
            var regions = from r in _db.Regions
                          join c in _db.AreaRegions on r.Id equals c.RegionId
                          where c.AreaId == areaId
                          select r;

            if (!string.IsNullOrEmpty(keyword))
            {
                regions = regions.Where(c => c.EnglishName.Contains(keyword));
            }

            if (string.IsNullOrEmpty(sort))
            {
                regions = regions.OrderBy(c => c.DisplayOrder);
            }

            int totalItems = regions.Count();
            int totalPages = 0;

            if (totalItems > 0)
            {
                totalPages = (totalItems / itemsPerPage) + 1;
            }

            var result = new PaginationModel<RegionModel>()
            {
                Items = regions.ToList(),
                Sort = sort,
                CurrentPage = currentPage,
                ItemsPerPage = itemsPerPage,
                Keyword = keyword,
                TotalItems = totalItems,
                TotalPages = totalPages
            };

            return result;
        }
```


# After

今回はキャッシュあらかじめ検索結果を入れて置き、キャッシュにすでに同じ条件で出力したものが格納されているか確認、あればキャッシュから出す、この実装によってDBへの負荷を減らします。

該当したエリアやその傘下の都道府県情報が更新された際にはキャッシュも更新する必要がありますのでここは注意です。

下記のコードを別クラスで追加します。

```c#
  public class CacheServices
    {
        private static readonly string connectionStringUs = "";

        private static readonly Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
        {
            return ConnectionMultiplexer.Connect(connectionStringUs);
        });

        /// <summary>
        /// Returns ConnectionMultiplexer object
        /// </summary>
        public static ConnectionMultiplexer Connection => lazyConnection.Value;

    }
```

次に実際にエリアを取得するクラスにキャッシュから情報を取り出して出力するようにします。

```c#
   public async Task<PaginationModel<RegionModel>> GetRegionsByAreaIdAsync(string areaId, string keyword, string sort, int currentPage = 1, int itemsPerPage = 100)
        {

            // Only if keyword is null, let's try to find value in cahce.
            if (string.IsNullOrEmpty(keyword))
            {
                IDatabase cache = CacheServices.Connection.GetDatabase();
                var result = await cache.StringGetAsync("GetRegionsByAreaId-" + areaId);

                if (result.HasValue)
                {
                    var cacheReult = JsonSerializer.Deserialize<PaginationModel<RegionModel>>(result);

                    return cacheReult;
                }
                else
                {
                    // There was no data in cache, so fetch.
                    var dbResult = GetRegionsByAreaIdFromDb(areaId, keyword, sort, currentPage, itemsPerPage);

                    // And want to cache this.
                    string jsonString = JsonSerializer.Serialize(dbResult);
                    await cache.StringSetAsync("GetRegionsByAreaId-" + areaId, jsonString);

                    return dbResult;
                }
            }

            var dbresultWKeyword = GetRegionsByAreaIdFromDb(areaId, keyword, sort, currentPage, itemsPerPage);

            // And don't want to cache this.

            return dbresultWKeyword;
        }
```