Professional Search with C# and Elasticsearch – Part 1
Professional Search with C# and Elasticsearch – Part 3
Professional Search with C# and Elasticsearch – Part 2
یک پوشه به نام Elastic ایجاد می کنیم :
Table of Contents
ElasticSong.cs : نتایج جستجو در پوشه الستیک
این کلاس به عنوان یک مدل برای ذخیره و بازیابی اطلاعات در Elasticsearch به کار میرود.
نکته :
استفاده از نوع Keyword
در Elasticsearch برای فیلدهایی که به عنوان واژههای کلیدی مورد استفاده قرار میگیرند، به دلایل زیر میتواند مناسب باشد:
Exact Match (تطابق دقیق): فیلدهایی که با نوع
Keyword
تعریف میشوند، به طور دقیق مطابق با مقادیر ورودی هستند. این بدان معناست که در جستجوها، تطابق دقیق انجام میشود و هیچ تغییری در مقدار ورودی انجام نمیشود. این ویژگی برای فیلدهایی که نیاز به تطابق دقیق دارند (مانند شناسهها یا واژههای کلیدی) مناسب است.Sorting (مرتبسازی): اگر نیاز به مرتبسازی بر اساس این فیلد باشد، فیلدهای
Keyword
بهترین گزینه هستند. زیرا در مرتبسازی مقادیر رشتهای، ترتیب معنایی ممکن است که بر اساس مقادیر Unicode انجام میشود، اماKeyword
ترتیب معنایی واقعی را حفظ میکند.Aggregations (جمعآوری و گزارشگیری): اگر بخواهید بر اساس این فیلد گزارشها و جمعآوریها انجام دهید،
Keyword
مناسب است. این نوع فیلد به شما این امکان را میدهد که به سادگی بر اساس مقادیر دقیق فیلد گروهبندی و جمعآوری کنید.
using Nest;
namespace Songs.Api.Elastic;
public class ElasticSong
{
[Number(NumberType.Long)]
public long Id { get; set; }
[Text]
public string Title { get; set; }
[Text]
public string AlbumTitle { get; set; }
[Keyword]
public string AlbumReleaseDate { get; set; }
[Text]
public string ArtistName { get; set; }
[Keyword]
public string Genre { get; set; }
}
namespace Songs.Api.Entities;
public class Genre
{
public int Id { get; set; }
public string Name { get; set; } = default!;
}
InitializeIndexService.cs (در پوشه الستیک)لود اولیه دیتا در الستیک
پر کردن اطلاعات Elastic از دیتابیس
using Microsoft.EntityFrameworkCore;
using Nest;
using Songs.Api.Persistence;
namespace Songs.Api.Elastic;
public interface IInitializeIndexService
{
Task Run();
}
public class InitializeIndexService : IInitializeIndexService
{
private readonly IAppDbContext _context; //برای دسترسی به دیتابیس
private readonly IElasticClient _elasticClient; //برای ارتباط با Elasticsearch
private readonly IConfiguration _configuration; //برای خواندن تنظیمات از فایلهای پیکربندی
public InitializeIndexService(IAppDbContext context, IElasticClient elasticClient, IConfiguration configuration)
{
_context = context;
_elasticClient = elasticClient;
_configuration = configuration;
}
//این متد به عنوان Initialize کردن ایندکس و افزودن اطلاعات به Elasticsearch عمل میکند. این متد به صورت ناهمزمان (async Task) اجرا میشود.
public async Task Run()
{
//ابتدا، نام ایندکس از تنظیمات (Elastic:Index) خوانده میشود.
var index = _configuration.GetValue("Elastic:Index");
//سپس، ایندکس موجود در Elasticsearch حذف میشود تا در صورت وجود، با اطلاعات جدید جایگزین شود.
await _elasticClient.Indices.DeleteAsync(index);
//سپس با استفاده از AutoMap()، یک Mapping اتوماتیک برای مدل ElasticSong انجام میشود و ایندکس مجدداً با استفاده از این نقشه ساخته میشود.
var response = await _elasticClient.Indices.CreateAsync(index,
x => x.Map(xx => xx.AutoMap()));
// if(!response.IsValid)
// do something
//سپس اطلاعات مربوط به موزیکها از دیتابیس خوانده میشوند. این شامل اطلاعات موزیک، آلبوم و هنرمند مرتبط با هر موزیک است.
var songs = await _context.Songs.AsNoTracking()
.Include(x => x.Album)
.ThenInclude(x => x!.Genre)
.Include(x => x.Album)
.ThenInclude(x => x!.Artist)
.ToListAsync();
//اطلاعات موزیکها به اطلاعات متناظر در مدل ElasticSong تبدیل شده و به Elasticsearch ارسال میشوند.
var elasticSongs = songs.Select(x => x.ToElasticSong());
await _elasticClient.BulkAsync(x => x
.Index(index)
.IndexMany(elasticSongs));
}
}
Mappers.cs
برای تعریف Mapper ها
using Nest;
using Songs.Api.Contracts;
using Songs.Api.Elastic;
using Songs.Api.Entities;
namespace Songs.Api;
public static class Mappers
{
public static ElasticSong ToElasticSong(this Song song)
=> new()
{
Id = song.Id,
Title = song.Title,
AlbumTitle = song.Album!.Title,
AlbumReleaseDate = song.Album.ReleaseDate.ToString("yyyy-MM-dd"),
ArtistName = song.Album.Artist!.Name,
Genre = song.Album.Genre!.Name
};
public static SearchParameters ToSearchParameters(this SearchSongsRequest request)
=> new(request.SearchText, request.Genre, request.PageSize * (request.PageNumber - 1) , request.PageSize);
public static SongResponse ToSongResponse(this ElasticSong song)
=> new(song.Id, song.Title, song.AlbumTitle, song.AlbumReleaseDate, song.ArtistName, song.Genre, DateOnly.Parse(song.AlbumReleaseDate));
public static SongResponseWithScore ToSongResponseWithScore(this IHit song)
=> new(song.Source.Id, song.Source.Title, song.Source.AlbumTitle, song.Source.AlbumReleaseDate,
song.Source.ArtistName, song.Source.Genre, DateOnly.Parse(song.Source.AlbumReleaseDate), song.Score ?? 0);
}
تنظیمات Program.cs
این کد یک `scope` ایجاد میکند و سپس از این scope یک سرویس (`initializeIndexService`) را استخراج کرده و متد `Run()` آن را اجرا میکند.
builder.Services.AddScoped();
////////////////////////////
using var scope = app.Services.CreateScope();
var initializeIndexService = scope.ServiceProvider.GetRequiredService();
await initializeIndexService.Run();
این کد یک `scope` ایجاد میکند و سپس از این scope یک سرویس (`initializeIndexService`) را استخراج کرده و متد `Run()` آن را اجرا میکند.
در زیر توضیحاتی در مورد این کد آورده شده است:
1. **`CreateScope`:** این متد بر روی `app.Services` اجرا میشود. `app.Services` نشاندهندهی مجموعهای از خدمات و وابستگیها در ASP.NET Core است. با استفاده از `CreateScope` یک اسکوپ جدید ایجاد میشود. این اسکوپ به عنوان یک زمینه محدود برای انجام عملیاتهای مرتبط با یک کلیدواژه (`using`) مورد استفاده قرار میگیرد.
2. **`scope.ServiceProvider.GetRequiredService<IInitializeIndexService>()`:** از `ServiceProvider` درون اسکوپ برای به دست آوردن یک سرویس از نوع `IInitializeIndexService` استفاده شده است. `GetRequiredService` برای درخواست یک سرویس از `ServiceProvider` به کار میرود. در اینجا، سرویس `IInitializeIndexService` که توسط `InitializeIndexService` پیادهسازی شده است، به متغیر `initializeIndexService` اختصاص داده میشود.
3. **`await initializeIndexService.Run();`:** سپس متد `Run()` از سرویس `initializeIndexService` فراخوانی میشود. از `await` برای اجرای این متد به صورت ناهمزمان استفاده شده است.
به طور کلی، این کد به منظور ایجاد یک محیط محدود برای انجام عملیات مرتبط با سرویس `IInitializeIndexService` و اجرای متد `Run()` آن در ابتدای اجرای برنامه مورد استفاده قرار گرفته است. این متد ممکن است برای مقدماتی کردن یک ایندکس Elasticsearch و اضافه کردن اطلاعات به آن استفاده شده باشد، همانطور که از کد قبلی پیداست.
استفاده از `scope` و ایجاد یک اسکوپ (`scope`) به دلایل زیر میتواند مفید باشد:
1. **مدیریت منابع:**
– ایجاد اسکوپ (`scope`) به شما این امکان را میدهد که منابع مورد نیاز برای انجام یک عملیات (مانند دسترسی به سرویسها) را به صورت محلی و در یک اسکوپ معین مدیریت کنید.
– پس از اتمام کار در داخل محدوده، منابع مرتبط با آن به صورت خودکار آزاد میشوند و از حافظه و منابع دیگر مرتبط با آن اسکوپ خارج میشوند. این موجب جلوگیری از نگهداری منابع بیاستفاده و کاهش حافظه مصرفی میشود.
2. **مدیریت زمان عمر (Lifetime) سرویسها:**
– اسکوپ (`scope`) معین میکند که چه مدت یک سرویس (Service) در دسترس است. به عبارت دیگر، این تاکید میکند که یک سرویس برای چه زمانی به صورت تکنسخه (singleton) در دسترس است و میتواند از آن استفاده کند.
3. **مقداردهی اولیه وابستگیها:**
– با ایجاد محدوده، میتوانید به راحتی وابستگیهای مورد نیاز برای یک عملیات را مقداردهی اولیه کنید و سپس برنامه مربوط به آن عملیات اجرا شود.
4. **استفاده از `using`:**
– استفاده از دستور `using` به صورت خودکار منابع را آزاد میکند. این بدان معناست که بعد از اجرای دستورات داخل بلاک `using`، منابع مرتبط با اسکوپ خودکار آزاد میشوند.
در مثال کدی که ارائه داده شده، ایجاد اسکوپ (`scope`) به عنوان یک شیوهٔ مدیریت منابع مربوط به دسترسی به سرویس `IInitializeIndexService` انجام شده است. این اسکوپ به کنترل بهتری از منابع و بهبود مدیریت منابع کمک کرده است.