<div class="bg-light border p-2 col-5 offset-1 mt-2" @onclick="@(() => AmenitySelectionChanged(Amenity.Name))"> <h4 class="text-secondary">Amenity - @Amenity.Id</h4> @Amenity.Name<br /> @Amenity.Description<br /> </div> @code { [Parameter] public BlazorAmenity Amenity { get; set; } [Parameter] public EventCallback<string> OnAmenitySelection { get; set; } protected async Task AmenitySelectionChanged(string name) { await OnAmenitySelection.InvokeAsync(name); } }
هنگام ایجاد یک Type جدید باید سعی کنیم دیتای داخلی Type را تا حد ممکن Immutable کنیم. حتی اگر نیاز داریم یک شیء را برگردانیم، بهتر است که یک instance جدید را برگردانیم، نه اینکه همان شیء موجود را تغییر دهیم. نتیحه این کار نهایتا به شفافیت بیشتر و Thread-Safe بودن منجر خواهد شد.
مثال:
public class Rectangle { public int Length { get; set; } public int Height { get; set; } public void Grow(int length, int height) { Length += length; Height += height; } } Rectangle r = new Rectangle(); r.Length = 5; r.Height = 10; r.Grow(10, 10);// r.Length is 15, r.Height is 20, same instance of r
// After public class ImmutableRectangle { int Length { get; } int Height { get; } public ImmutableRectangle(int length, int height) { Length = length; Height = height; } public ImmutableRectangle Grow(int length, int height) => new ImmutableRectangle(Length + length, Height + height); } ImmutableRectangle r = new ImmutableRectangle(5, 10); r = r.Grow(10, 10);// r.Length is 15, r.Height is 20, is a new instance of r
یکی از موارد با اهمیت در سبک کد نویسی تابعی را در مثال زیر ببینید:
public static void Main() { Console.WriteLine(GetSalutation(DateTime.Now.Hour)); } // imparitive, mutates state to produce a result /*public static string GetSalutation(int hour) { string salutation; // placeholder value if (hour < 12) salutation = "Good Morning"; else salutation = "Good Afternoon"; return salutation; // return mutated variable }*/ public static string GetSalutation(int hour) => hour < 12 ? "Good Morning" : "Good Afternoon";
در قسمت قبلی درباره توابع HOF صحبت کردیم. به طور خلاصه توابعی که یک تابع را به عنوان ورودی میگیرند و یک تابع را به عنوان خروجی برمیگردانند. به مثال زیر توجه کنید:
public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) { int count = 0; foreach (TSource element in source) { checked { if (predicate(element)) { count++; } } } return count; }
ترکیب توابع به عمل پیوند دادن چند تابع ساده، برای ایجاد توابعی پیچیده گفته میشود. دقیقا مانند عملی که در ریاضیات انجام میشود. خروجی هر تابع به عنوان ورودی تابع بعدی مورد استفاده قرار میگیرد و در آخر ما خروجی آخرین فراخوانی را به عنوان نتیجه دریافت میکنیم. ما میتوانیم در #C به روش برنامه نویسی تابعی، توابع را با یکدیگر ترکیب کنیم. به مثال زیر توجه کنید:
public static class Extensions { public static Func<T, TReturn2> Compose<T, TReturn1, TReturn2>(this Func<TReturn1, TReturn2> func1, Func<T, TReturn1> func2) { return x => func1(func2(x)); } } public class Program { public static void Main(string[] args) { Func<int, int> square = (x) => x * x; Func<int, int> negate = x => x * -1; Func<int, string> toString = s => s.ToString(); Func<int, string> squareNegateThenToString = toString.Compose(negate).Compose(square); Console.WriteLine(squareNegateThenToString(2)); } }
یکی از روشهای مهم در سبک برنامه نویسی تابعی، فراخوانی متدها به صورت زنجیرهای و پاس دادن خروجی یک متد به متد بعدی، به عنوان ورودی است. به عنوان مثال کلاس String Builder یک مثال خوب از این نوع پیاده سازی است. کلاس StringBuilder از پترن Fluent Builder استفاده میکند. ما میتوانیم با اکستنشن متد هم به همین نتیجه برسیم. نکته مهم در مورد کلاس StringBuilder این است که این کلاس، شیء string را mutate نمیکند؛ به این معنا که هر متد، تغییری در object ورودی نمیدهد و یک خروجی جدید را بر میگرداند.
string str = new StringBuilder() .Append("Hello ") .Append("World ") .ToString() .TrimEnd() .ToUpper();
public static class Extensions { public static StringBuilder AppendWhen(this StringBuilder sb, string value, bool predicate) => predicate ? sb.Append(value) : sb; } public class Program { public static void Main(string[] args) { // Extends the StringBuilder class to accept a predicate string htmlButton = new StringBuilder().Append("<button").AppendWhen(" disabled", false).Append(">Click me</button>").ToString(); } }
نوعهای اضافی درست نکنید ، به جای آن از کلمهی کلیدی yield استفاده کنید!
گاهی ما نیاز داریم لیستی از آیتمها را به عنوان خروجی یک متد برگردانیم. اولین انتخاب معمولا ایجاد یک شیء از جنس List یا به طور کلیتر Collection و سپس استفاده از آن به عنوان نوع خروجی است:
public static void Main() { int[] a = { 1, 2, 3, 4, 5 }; foreach (int n in GreaterThan(a, 3)) { Console.WriteLine(n); } } /*public static IEnumerable<int> GreaterThan(int[] arr, int gt) { List<int> temp = new List<int>(); foreach (int n in arr) { if (n > gt) temp.Add(n); } return temp; }*/ public static IEnumerable<int> GreaterThan(int[] arr, int gt) { foreach (int n in arr) { if (n > gt) yield return n; } }
در قسمت قبلی به طور کلی درباره برنامه نویسی Imperative صحبت کردیم. در مثال زیر یک نمونه از تبدیل یک متد که با استایل Imperative نوشته شده به declarative را میبینید. شما میتوانید ببینید که چقدر کوتاهتر و خواناتر شده:
List<int> collection = new List<int> { 1, 2, 3, 4, 5 }; // Imparative style of programming is verbose List<int> results = new List<int>(); foreach(var num in collection) { if (num % 2 != 0) results.Add(num); } // Declarative is terse and beautiful var results = collection.Where(num => num % 2 != 0);
Immutable Collection
در مورد اهمیت immutable قبلا صحبت کردیم؛ Immutable Collection ها، کالکشنهایی هستند که به جز زمانیکه ایجاد میشنود، اعضای آنها نمیتوانند تغییر کنند. زمانیکه یک آیتم به آن اضافه یا کم شود، یک لیست جدید، برگردانده خواهد شد. شما میتوانید انواع این کالکشنها را در این لینک ببینید.
به نظر میرسد که ایجاد یک کالکشن جدید میتواند سربار اضافی بر روی استفاده از حافظه داشته باشد، اما همیشه الزاما به این صورت نیست. به طور مثال اگر شما f(x)=y را داشته باشید، مقادیر x و y به احتمال زیاد یکسان هستند. در این صورت متغیر x و y، حافظه را به صورت مشترک استفاده میکنند. به این دلیل که هیچ کدام از آنها Mutable نیستند. اگر به دنبال جزییات بیشتری هستید این مقاله به صورت خیلی جزییتر در مورد نحوه پیاده سازی این نوع کالکشنها صحبت میکند. اریک لپرت یک سری مقاله در مورد Immutable ها در #C دارد که میتوانید آن هار در اینجا پیدا کنید.
Thread-Safe Collections
Collection | توضیحات |
ConcurrentDictionary | پیاده سازی thread safe از دیکشنری key-value |
ConcurrentQueue | پیاده سازی thread safe از صف (اولین ورودی ، اولین خروجی) |
ConcurrentStack | پیاده سازی thread safe از پشته (آخرین ورودی ، اولین خروجی) |
ConcurrentBag | پیاده سازی thread safe از لیست نامرتب |
این کلاسها در واقع همه مشکلات ما را حل نخواهند کرد؛ اما بهتر است که در ذهن خود داشته باشیم که بتوانیم به موقع و در جای درست از آنها استفاده کنیم.
در این قسمت از مقاله سعی شد با روشهای خیلی ساده، با مفاهیم اولیه برنامه نویسی تابعی درگیر شویم. در ادامه مثالهای بیشتری از الگوهایی که میتوانند به ما کمک کنند، خواهیم داشت.
نحوه ایجاد یک اسلایدشو به صورت داینامیک
using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Linq; using System.Web; public class GallerySite { int iD; public int ID { get { return iD; } set { iD = value; } } string imagePath; public string ImagePath { get { return imagePath; } set { imagePath = value; } } string imageText; public string ImageText { get { return imageText; } set { imageText = value; } } public List<GallerySite> GetImage() { List<GallerySite> _Gallery = new List<GallerySite>() { }; SqlConnection cnn = new SqlConnection("Data Source=.;Initial Catalog=RoshanZamirDataBase;Integrated Security=True"); cnn.Open(); SqlCommand cmd = new SqlCommand("Select * From Gallery", cnn); SqlDataReader datareadfewr = cmd.ExecuteReader(); if (datareadfewr.HasRows) { while (datareadfewr.Read()) { _Gallery.Add(new GallerySite() { ID = Convert.ToInt32(datareadfewr["ID"]), ImagePath = (string)datareadfewr["ImagePath"], ImageText=(string)datareadfewr["ImageText"] }); } } return _Gallery; } } ................................................................................................................................. <link href="orbit-1.2.3.css" rel="stylesheet" /> <script src="jquery-1.8.3.min.js"></script> <script src="jquery.orbit-1.2.3.min.js"></script> <script type="text/javascript"> $(function () { $.ajax({ url: "Handler.ashx", contentType: "application/json; charset=utf-8", success: function (data) { $.each(data, function (i, b) { var str = '<img src="' + b.ImagePath + '" alt="' + b.ImageText + '"/>'; $("#featured").append(str); }); $('#featured').orbit(); }, dataType: "json" }); }); </script> ........................................................................................................................... Handler : public class Handler : IHttpHandler { public void ProcessRequest (HttpContext context) { var _Gallery = new GallerySite(); var List = _Gallery.GetImage(); string str = JsonConvert.SerializeObject(List); context.Response.Write(str); } public bool IsReusable { get { return false; } } }
public class TestProfile1 : Profile { protected override void Configure() { // این تنظیم سراسری هست و به تمام خواص زمانی اعمال میشود this.CreateMap<DateTime, string>().ConvertUsing(new DateTimeToPersianDateTimeConverter()); this.CreateMap<User, UserViewModel>(); // Other mappings } public override string ProfileName { get { return this.GetType().Name; } } }
public interface IMapFrom<T> { } public interface IHaveCustomMappings { void CreateMappings(IConfiguration configuration); }
public class PersonViewModel : IMapFrom<Person> { public string Name { get; set; } public string LastName { get; set; } }
public class PersonViewModel : IHaveCustomMapping { public string Name { get; set; } // دیگر پراپرتیها public void CreateMappings(IConfiguration configuration) { configuration.CreateMap<ApplicationUser, PersonViewModel>() .ForMember(m => m.Name, opt => opt.MapFrom(u => u.ApplicationUser.UserName)); // دیگر نگاشتها } }
public void Execute() { var types = Assembly.GetExecutingAssembly().GetExportedTypes(); LoadStandardMappings(types); LoadCustomMappings(types); }
متد LoadStandardMappings:
private static void LoadStandardMappings(IEnumerable <Type> types) { var maps = (from t in types from i in t.GetInterfaces() where i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom< >) && !t.IsAbstract && !t.IsInterface select new { Source = i.GetGenericArguments()[0], Destination = t }).ToArray(); foreach(var map in maps) { Mapper.CreateMap(map.Source, map.Destination); } }
- ابتدا تمامی typeهای تعریف شده در پروژه به متد فوق پاس داده خواهند شد.
- برای هر type تمامی اینترفیسهایی که توسط این type پیادهسازی شده باشند را دریافت خواهیم کرد.
- سپس هر type که اینترفیس IMapFrom را پیادهسازی کرده باشد را پردازش میکنیم.
- سپس از نوعهای Abstract و Interface صرفنظر خواهیم کرد.
- انواع مبدا و مقصد را برای AutoMapper فراهم خواهیم کرد.
- در نهایت AutoMapper براساس آنها نگاشت را ایجاد خواهد کرد.
متد LoadCustomMapping:
private static void LoadCustomMappings(IEnumerable <Type> types) { var maps = (from t in types from i in t.GetInterfaces() where typeof(IHaveCustomMappings).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface select(IHaveCustomMappings) Activator.CreateInstance(t)).ToArray(); foreach(var map in maps) { map.CreateMappings(Mapper.Configuration); } }
توضیح کدهای فوق:
1- EF Code First #8
2- مباحث تکمیلی مدلهای خود ارجاع دهنده در EF Code First
3- نگاهی به اجزای تعاملی Twitter Bootstrap
هدف از مطلب جاری نحوه نمایش منویهای چند سطحی میباشد، ابتدا مثال کامل زیر را در نظر بگیرید :
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Menu.Models.Entities { public class Category { public int Id { get; set; } public string Name { get; set; } public int? ParentId { get; set; } public virtual Category Parent { get; set; } public virtual ICollection<Category> Children { get; set; } } } public class MyContext : DbContext { public DbSet<Category> Category { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { // Self Referencing Entity modelBuilder.Entity<Category>() .HasOptional(x => x.Parent) .WithMany(x => x.Children) .HasForeignKey(x => x.ParentId) .WillCascadeOnDelete(false); base.OnModelCreating(modelBuilder); } }
هدف از این بحث، نحوه نمایش گروه محصولات داخل منو به صورت چند سطحی میباشد، جهت نمایش میبایست از تکنیک recursive function استفاده کنید، ابتدا در نظر داشته باشید که ساختار منوی تشکیل شده میبایست بدین صورت باشد :
این حالت میتواند تا n سطح پیش برود، حال نحوه نمایش در View مربوطه باید به صورت زیر باشد :
@using Menu.Helper @model IEnumerable<.Models.Entities.Category> @ShowTree(Model) @helper ShowTree(IEnumerable<Menu.Models.Entities.Category> categories) { foreach (var item in categories) { <li class="@(item.Children.Any() ? "dropdown-submenu" : "")"> @Html.ActionLink(item.Name, actionName: "Category", controllerName: "Product", routeValues: new { Id = item.Id, productName = item.Name.ToSeoUrl() }, htmlAttributes: null) @if (item.Children.Any()) { <ul> @ShowTree(item.Children) </ul> } </li> } }
یک نکته :
در خط 9 این مورد را که آیا آیتم جاری فرزندی دارد چک کرده ایم اگر داشته باشد کلاس dropdown-submenu را به li جاری اضافه میکند.
تغییرات الگوریتمهای هش کردن اطلاعات
با حذف و تغییرنام کلاسهایی مانند SHA256Managed (و تمام کلاسهای Managed_) در NET Core.، معادل کدهایی مانند:
using (var sha256 = new SHA256Managed()) { // Crypto code here... }
public static string GetHash(string text) { using (var sha256 = SHA256.Create()) { var hashedBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text)); return BitConverter.ToString(hashedBytes).Replace("-", "").ToLower(); } }
"dependencies": { "System.Security.Cryptography.Algorithms": "4.2.0" },
به علاوه اگر نیاز به محاسبهی هش حاصل از جمع چندین byte array را دارید، در اینجا میتوان از الگوریتمهای IncrementalHash به صورت ذیل استفاده کرد:
using (var md5 = IncrementalHash.CreateHash(HashAlgorithmName.MD5)) { md5.AppendData(byteArray1, 0, byteArray1.Length); md5.AppendData(byteArray2, 0, byteArray2.Length); var hash = md5.GetHashAndReset(); }
تولید اعداد تصادفی Thread safe در NET Core.
روشهای زیادی برای تولید اعداد تصادفی در برنامههای دات نت وجود دارند؛ اما مشکل اکثر آنها این است که thread safe نیستند و نباید از آنها در برنامههای چند ریسمانی (مانند برنامههای وب)، به نحو متداولی استفاده کرد. در این بین تنها کلاسی که thread safe است، کلاس RNGCryptoServiceProvider میباشد؛ آن هم با یک شرط:
private static readonly RNGCryptoServiceProvider Rand = new RNGCryptoServiceProvider();
بنابراین اگر در کدهای خود چنین تعریفی را دارید:
var rand = new RNGCryptoServiceProvider();
در NET Core. این کلاس به طور کامل حذف شدهاست و معادل جدید آن کلاس RandomNumberGenerator است که به صورت ذیل قابل استفاده است (و در عمل تفاوتی بین کدهای آن با کدهای RNGCryptoServiceProvider نیست):
public interface IRandomNumberProvider { int Next(); int Next(int max); int Next(int min, int max); } public class RandomNumberProvider : IRandomNumberProvider { private readonly RandomNumberGenerator _rand = RandomNumberGenerator.Create(); public int Next() { var randb = new byte[4]; _rand.GetBytes(randb); var value = BitConverter.ToInt32(randb, 0); if (value < 0) value = -value; return value; } public int Next(int max) { var randb = new byte[4]; _rand.GetBytes(randb); var value = BitConverter.ToInt32(randb, 0); value = value % (max + 1); // % calculates remainder if (value < 0) value = -value; return value; } public int Next(int min, int max) { var value = Next(max - min) + min; return value; } }
public class Startup { public void ConfigureServices(IServiceCollection services) { services.TryAddSingleton<IRandomNumberProvider, RandomNumberProvider>();
نیاز به الگوریتمهای رمزنگاری متقارن قوی و معادل بهتر آنها در ASP.NET Core
ASP.NET Core به همراه یکسری API جدید است به نام data protection APIs که روشهایی را برای پیاده سازی بهتر الگوریتمهای هش کردن اطلاعات و رمزنگاری اطلاعات، ارائه میدهند و برای مثال ASP.NET Core Identity و یا حتی Anti forgery token آن، در پشت صحنه دقیقا از همین API برای انجام کارهای رمزنگاری اطلاعات استفاده میکنند.
برای مثال اگر بخواهید کتابخانهای را طراحی کرده و در آن از الگوریتم AES استفاده نمائید، نیاز است تنظیم اضافهتری را جهت دریافت کلید عملیات نیز اضافه کنید. اما با استفاده از data protection APIs نیازی به اینکار نیست و مدیریت ایجاد، نگهداری و انقضای این کلید به صورت خودکار توسط سیستم data protection انجام میشود. کلیدهای این سیستم موقتی هستند و طول عمری محدود دارند. بنابراین باتوجه به این موضوع، روش مناسبی هستند برای تولید توکنهای Anti forgery و یا تولید محتوای رمزنگاری شدهی کوکیها. بنابراین نباید از آن جهت ذخیره سازی اطلاعات ماندگار در بانکهای اطلاعاتی استفاده کرد.
فعال سازی این سیستم نیازی به تنظیمات اضافهتری در ASP.NET Core ندارد و جزو پیش فرضهای آن است. در کدهای ذیل، نمونهای از استفادهی از این سیستم را ملاحظه میکنید:
public interface IProtectionProvider { string Decrypt(string inputText); string Encrypt(string inputText); } namespace Providers { public class ProtectionProvider : IProtectionProvider { private readonly IDataProtector _dataProtector; public ProtectionProvider(IDataProtectionProvider dataProtectionProvider) { _dataProtector = dataProtectionProvider.CreateProtector(typeof(ProtectionProvider).FullName); } public string Decrypt(string inputText) { var inputBytes = Convert.FromBase64String(inputText); var bytes = _dataProtector.Unprotect(inputBytes); return Encoding.UTF8.GetString(bytes); } public string Encrypt(string inputText) { var inputBytes = Encoding.UTF8.GetBytes(inputText); var bytes = _dataProtector.Protect(inputBytes); return Convert.ToBase64String(bytes); } } }
public class Startup { public void ConfigureServices(IServiceCollection services) { services.TryAddSingleton<IProtectionProvider, ProtectionProvider>();
مستندات مفصل این API را در اینجا میتوانید مطالعه کنید.
معادل الگوریتم Rijndael در NET Core.
همانطور که عنوان شد، طول عمر کلیدهای data protection API محدود است و به همین جهت برای کارهایی چون تولید توکنها، رمزنگاری کوئری استرینگها و یا کوکیهای کوتاه مدت، بسیار مناسب است. اما اگر نیاز به ذخیره سازی طولانی مدت اطلاعات رمزنگاری شده وجود داشته باشد، یکی از الگوریتمهای مناسب اینکار، الگوریتم AES است.
الگوریتم Rijndael نگارش کامل دات نت، اینبار نام اصلی آن یا AES را در NET Core. پیدا کردهاست و نمونهای از نحوهی استفادهی از آن، جهت رمزنگاری و رمزگشایی اطلاعات، به صورت ذیل است:
public string Decrypt(string inputText, string key, string salt) { var inputBytes = Convert.FromBase64String(inputText); var pdb = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(salt)); using (var ms = new MemoryStream()) { var alg = Aes.Create(); alg.Key = pdb.GetBytes(32); alg.IV = pdb.GetBytes(16); using (var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write)) { cs.Write(inputBytes, 0, inputBytes.Length); } return Encoding.UTF8.GetString(ms.ToArray()); } } public string Encrypt(string inputText, string key, string salt) { var inputBytes = Encoding.UTF8.GetBytes(inputText); var pdb = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(salt)); using (var ms = new MemoryStream()) { var alg = Aes.Create(); alg.Key = pdb.GetBytes(32); alg.IV = pdb.GetBytes(16); using (var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write)) { cs.Write(inputBytes, 0, inputBytes.Length); } return Convert.ToBase64String(ms.ToArray()); } }
من یه وظیفه نوشتم که مییاد هر 1 دقیقه 1 بار یه متن رو در Table درج میکنه.
این کد کلاس
using Quartz; using System.Data; using System.Data.OleDb; using System.Configuration; namespace WebApplication1 { public class TestQuartzClass:IJob { public void Execute(IJobExecutionContext context) { //Page myPage = (Page)HttpContext.Current.Handler; //TextBox MyTextBox=(TextBox)myPage.FindControl("txt"); string sql = "Insert into tbl_Test (Content) values (@Content)"; ExecuteNoneQuery(System.Data.CommandType.Text, sql, new OleDbParameter[]{ //new OleDbParameter("@Content", MyTextBox.Text) new OleDbParameter("@Content","Hello world!") }); } public int ExecuteNoneQuery(CommandType commandType, string commandText, params OleDbParameter[] commandParameters) { using (OleDbConnection con = new OleDbConnection(ConfigurationManager.ConnectionStrings["ConStr"].ConnectionString)) { OleDbCommand cmd = new OleDbCommand(); cmd.Connection = con; cmd.CommandType = commandType; cmd.CommandText = commandText; cmd.Parameters.AddRange(commandParameters); con.Open(); int retVal = cmd.ExecuteNonQuery(); con.Close(); return retVal; } } } }
و این هم کد صفحه ای که با کلیک دکمه وظیفه شروع به کار میکنه
using System; using Quartz; using Quartz.Impl; namespace WebApplication1 { public partial class WebForm1 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } public static void ConfigureQuartzJobs() { // construct a scheduler factory ISchedulerFactory schedFact = new StdSchedulerFactory(); // get a scheduler IScheduler sched = schedFact.GetScheduler(); sched.Start(); IJobDetail job = JobBuilder.Create<TestQuartzClass>() .WithIdentity("SendJob") .Build(); var trigger = TriggerBuilder.Create() .WithIdentity("SendTrigger") .WithSimpleSchedule(x => x.WithIntervalInMinutes(1).RepeatForever()) //.StartAt(startTime) .StartNow() .Build(); sched.ScheduleJob(job, trigger); } protected void btn_Click(object sender, EventArgs e) { ConfigureQuartzJobs(); } } }
همونطور که میبینید متن رو به شکل زیر پاس دادم.
new OleDbParameter("@Content","Hello world!")
و برای اینکه در کلاس بتونم به کنترلهای صفحه دسترسی داشته باشم کدهای زیر رو به متد Exceute اضافه کردم
Page myPage = (Page)HttpContext.Current.Handler; TextBox MyTextBox=(TextBox)myPage.FindControl("txt");
ولی به محض اینکه این کدها رو اضافه میکنم دیگه برنامه کار نمیکنه.
متن داخل تکست باکس رو هم قصد داشتم به شکل زیر پاس بدم.
new OleDbParameter("@Content", MyTextBox.Text)
لطفاً راهنمایی کنید.
من یک نمونه هم به منظور تست آماده کردم که از لینک زیر میتونید دانلود کنید:
http://www.4shared.com/rar/1Fu_jpOOba/WebApplication1.html
مطالب پیشین مرتبط با لوسین را در اینجا میتوانید پیگیری کنید. آخرین نگارش آن که تا این تاریخ، 4.8 بتا است، با داتنت(Core) سازگار است و روش برپایی آغازین آن ... تغییرات قابل توجهی داشتهاست که خلاصهی آنها را در این مطلب بررسی خواهیم کرد.
1) بستههای جدید مورد نیاز
برای کار با لوسین جدید، نیاز است حداقل سهبستهی زیر را نصب کنیم تا به امکانات پایهای و کوئری گیریهای پیشرفتهی آن دسترسی داشته باشیم:
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00016"/> <PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016"/> <PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016"/>
2) تهیه نگاشتهای لازم
فرض کنید شیء اصلی ما چنین ساختاری را دارد:
public class WhatsNewItemModel { public required int Id { set; get; } public required string OriginalTitle { set; get; } }
مرحلهی بعد کار با لوسین، تبدیل اشیاء سفارشی خود به شیء Document لوسین و برعکس است. به همین جهت به دو مپر برای این کارها نیاز است:
الف) نگاشتگر یک شیء سفارشی، به شیء Document
public static class LuceneDocumentMapper { public static Document MapToLuceneDocument(this WhatsNewItemModel post) { ArgumentNullException.ThrowIfNull(post); return [ new TextField(nameof(WhatsNewItemModel.OriginalTitle), post.OriginalTitle, Field.Store.YES), // Document StringField instances are sort of keywords, they are not analyzed, they indexed as is (in its original case). new StringField(nameof(WhatsNewItemModel.Id), post.Id.ToString(CultureInfo.InvariantCulture), Field.Store.YES), ]; } }
در اینجا یک متدالحاقی را تهیه کردهایم تا شیءای از نوع WhatsNewItemModel ما را به یک شیء Document لوسین، تبدیل کند.
چند نکته در اینجا حائز اهمیت هستند:
- در نگارش جدید لوسین، با اشیاء TextField و StringField جدید سروکار داریم و شیء قدیمی Field نگارشهای قبلی لوسین، منسوخ شده درنظر گرفته میشود.
- زمانی از شیء TextField استفاده میکنیم که قرار است توسط لوسین، تحلیل شده و در جستجوهای پیچیده استفاده شود.
- اگر فقط قرار است، مقداری را در این ایندکس ذخیره کنیم و قصد تحلیل آنها را نداریم و حداکثر یک کوئری سادهی یافتن اصل آنها، مدنظر ما است، باید از اشیاء StringField برای معرفی و نگاشت آنها استفاده کنیم (شبیه به کار با واژههای کلیدی).
- پرچم Field.Store.YES به این معنا است که اصل محتوای تحلیل شده نیز در ایندکس لوسین، درج شود. اگر این پرچم را به NO تنظیم کنیم، فقط تحلیل آن صورت گرفته و نتیجهی آن ذخیره میشود، که برای جستجوها مفید است؛ اما مقدار این فیلد دیگر قابل بازیابی نخواهد بود.
ب) نگاشتگر یک شیء Document لوسین، به یک شیء سفارشی
در زمان کوئری گرفتن از لوسین، خروجی نهایی یک شیء Document آن است که باید به شیء سفارشی مدنظر ما نگاشت شود:
public static class LuceneDocumentMapper { public static LuceneSearchResult MapToLuceneSearchResult(this Document document) { ArgumentNullException.ThrowIfNull(document); return new LuceneSearchResult { Id = document.Get(nameof(WhatsNewItemModel.Id), CultureInfo.InvariantCulture).ToInt(), OriginalTitle = document.Get(nameof(WhatsNewItemModel.OriginalTitle), CultureInfo.InvariantCulture) }; } }
نمونهای از این نگاشت را در متد الحاقی فوق مشاهده میکنید که توسط متد Get شیء Document قابل انجام است. بدیهی است خروجی این متد، یک رشتهاست و در صورت نیاز باید توسط ما کار تبدیلات ثانویه آنها انجام شود.
3) نیاز به یک تحلیلگر مناسب
لوسین برای تولید ایندکسهای جستجوی تمام متنی خود، از یک سری Analyzer استفاده میکنید که اگر سری پیشین مطالب مرتبط را مطالعه کنید، به نمونهی StandardAnalyzer آن خواهید رسید که هنوز هم معتبر و قابل استفادهاست و یا میتوان همانند سایت جاری، از یک LowerCaseHtmlStripAnalyzer استفاده کرد که این کارها را همزمان انجام میدهد:
الف) از یک لیست PersianStopwords.List برای حذف واژههای کم اهمیت زبان فارسی استفاده میکند. برای مثال ما نمیخواهیم که واژهی «ما» را با اهمیت شمرده و ایندکس کند و امثال آن.
ب) LowerCaseFilter را به متون دریافتی اعمال میکند. این کار در پشت صحنهی StandardAnalyzer توکار لوسین هم اعمال میشود. اگر با این موضوع آشنا نباشید، ممکن است در حین کوئری گرفتن، به نتیجهای نرسید! چون متن ارسالی به لوسین را ابتدا باید lower-case کنید و سپس آنرا کوئری بگیرید.
ج) HTMLStripCharFilter توکار لوسین هم به آن اعمال شدهاست. از این جهت که متن مقالات ما به همراه تگهای HTML ای هم هستند. این فیلتر کار حذف کردن آنها را در حین تحلیل، انجام میدهد و دیگر نیازی نیست تا ما خودمان متن ارسالی به لوسین را تمیز کنیم.
نکتهی مهم: این تحلیلگر ویژه، فقط باید به فیلدهایی از نوع TextField اعمال شود. اگر آنرا به StringField ها اعمال کنیم، دیگر قادر به کوئری گرفتن از آنها نخواهیم بود! چون تحلیلگر StringFieldها باید از نوع توکار KeywordAnalyzer ثبت و معرفی شود. این نوع فیلدها، حالت واژههای کلیدی را دارند (به همان صورتی که هست ثبت میشوند) و قرارنیست که توسط لوسین تحلیل ویژهای شوند. به همین جهت برای رسیدن به یک تحلیلگر ترکیبی که بتواند این دو نوع فیلد را با هم پوشش دهد و کار معرفی چندین نوع تحلیلگر را یکجا انجام دهد، نیاز به یک PerFieldAnalyzerWrapper جدید داریم:
_keywordAnalyzer = new KeywordAnalyzer(); _lowerCaseHtmlStripAnalyzer = new LowerCaseHtmlStripAnalyzer(LuceneVersion); _analyzer = new PerFieldAnalyzerWrapper(_lowerCaseHtmlStripAnalyzer, new Dictionary<string, Analyzer> { { nameof(WhatsNewItemModel.Id), _keywordAnalyzer } });
PerFieldAnalyzerWrapper در حقیقت برای تمام فیلدهایی که در قسمت دیکشنری فوق، ذکر نشدهاند، از LowerCaseHtmlStripAnalyzer استفاده میکند. برای مابقی موارد از KeywordAnalyzer کمک خواهد گرفت.
4) روش صحیح راه اندازی reader و writer های ایندکس لوسین جدید
کار با لوسین به حدی سریع است که از کیفیت آن شگفت زده خواهید شد! اما ... بهشرطی که بدانید دقیقا به چه صورتی باید نویسنده و خوانندهی ایندکسهای آنرا مدیریت کنید. اکثر مثالهایی را که بر روی اینترنت پیدا میکنید، به همراه متدهایی هستند که مدام در حال گشودن و dispose این نویسندهها و خوانندههای ایندکس هستند که ... این مثالها، روش کار صحیح با لوسین نیستند! و به شدت آنرا کند میکنند.
نکتهی مهمی که این مثالها به آن توجهی نکردهاند، «thread-safe» بودن نویسنده و خوانندهی ایندکس لوسین است. یعنی میتوان یک نمونه از اینها را در ابتدای کار برنامه ایجاد کرد و تا آخر کار برنامه، بدون نیاز به نمونه سازی مجدد و باز و بسته کردن آنها، بارها مورد استفادهی مجدد قرار داد و هیچ تداخلی هم ندارند و از قسمتهای مختلف برنامه هم قابل دسترسی هستند.
به همین جهت باید یک سرویس مرکزی را برای اینکار تدارک دید که طول عمر آن، حتما Singleton باشد تا بتواند نویسنده و خوانندهی ایندکس لوسین را فقط یکبار نمونه سازی و ایجاد کرده و تا پایان کار برنامه، زنده نگه دارد (کدهای کامل این کلاس را در اینجا میتوانید مطالعه کنید):
public class FullTextSearchService : IFullTextSearchService { private const LuceneVersion LuceneVersion = Lucene.Net.Util.LuceneVersion.LUCENE_48; private readonly Analyzer _analyzer; private readonly IAppFoldersService _appFoldersService; private readonly FSDirectory _fsDirectory; // IndexWriter instances are completely thread safe, meaning multiple threads can call any of its methods, concurrently. private readonly IndexWriter _indexWriter; private readonly KeywordAnalyzer _keywordAnalyzer; private readonly ILogger<FullTextSearchService> _logger; private readonly LowerCaseHtmlStripAnalyzer _lowerCaseHtmlStripAnalyzer; // Safely shares IndexSearcher instances across multiple threads, while periodically reopening. private readonly SearcherManager _searcherManager; private bool _isDisposed; public FullTextSearchService(IAppFoldersService appFoldersService, ILogger<FullTextSearchService> logger) { _appFoldersService = appFoldersService ?? throw new ArgumentNullException(nameof(appFoldersService)); _logger = logger; _keywordAnalyzer = new KeywordAnalyzer(); _lowerCaseHtmlStripAnalyzer = new LowerCaseHtmlStripAnalyzer(LuceneVersion); _analyzer = new PerFieldAnalyzerWrapper(_lowerCaseHtmlStripAnalyzer, new Dictionary<string, Analyzer> { // Document StringField instances are sort of keywords, they are not analyzed, they indexed as is (in its original case). // But StandardAnalyzer applies lower case filter to a query. // We can fix this by using KeywordAnalyzer with our query parser. { nameof(WhatsNewItemModel.Id), _keywordAnalyzer }, { nameof(WhatsNewItemModel.DocumentTypeIdHash), _keywordAnalyzer }, { nameof(WhatsNewItemModel.DocumentContentHash), _keywordAnalyzer } }); _fsDirectory = FSDirectory.Open(_appFoldersService.LuceneIndexFolderPath); _indexWriter = new IndexWriter(_fsDirectory, new IndexWriterConfig(LuceneVersion, _analyzer)); _searcherManager = new SearcherManager(_indexWriter, applyAllDeletes: true, searcherFactory: null); }
این سرویس، یک سرویس Singleton است که نحوهی آغاز و شروع به کار با اشیاء لوسین را در سازندهی آن مشاهده میکنید.
توضیحات:
الف) در اینجا، روش نمونه سازی PerFieldAnalyzerWrapper را که پیشتر در مورد آن بحث شد، مشاهده میکنید.
ب) سپس یک IndexWriter، نمونه سازی میشود که از تحلیلگر ترکیبی ما استفاده میکند.
ج) در ادامه یک SearcherManager جدید را مشاهده میکنید که با IndexWriter برنامه هماهنگ است و هر زمانیکه سندی به لوسین اضافه میشود، قادر به کوئری گرفتن از آن هم خواهیم بود.
نکتهی مهم: طول عمر تمام این موارد، با طول عمر کلاس سرویس جاری، یکی است. یعنی تنها یکبار در طول عمر برنامه نمونه سازی شده و تا پایان کار آن، زنده نگه داشته میشوند.
5) روش افزودن یک سند به ایندکس لوسین و سپس به روز رسانی آن
اکنون با استفاده از نگاشتگرهایی که در ابتدای بحث تهیه کردیم و همچنین شیء IndexWriter فوق، به صورت زیر میتوان یک شیء سفارشی خود را به ایندکس لوسین اضافه کنیم:
_indexWriter.AddDocument(post.MapToLuceneDocument()); _indexWriter.Flush(triggerMerge: true, applyAllDeletes: true); _indexWriter.Commit();
و یا اگر خواستیم سند موجودی را به روز کنیم، روش کار به شکل زیر است:
_indexWriter.UpdateDocument(new Term(nameof(WhatsNewItemModel.Id), post.Id.ToString()), post.MapToLuceneDocument());
new Term، در حقیقت یک کوئری جدید را سبب میشود که توسط آن سندی یافت شده، در پشت صحنه حذف میشود و سپس سند جدیدی بجای آن درج خواهد شد. در اینجا باید دقت داشت که چون Id ثبت شده از نوع StringField است، نباید حالت lower-case آنرا جستجو کرد و باید دقیقا به همان نحوی که ثبت شده، جستجو شود.
6) روش کار با searcherManager جدید لوسین
همانطور که عنوان شد، لوسین جدید به همراه یک searcherManager هم هست که کار آن، ارائهی thread-safe دسترسی به خوانندهی ایندکس لوسین است. نحوهی عمومی کار با آن را در ادامه مشاهده میکنید:
private TResult DoSearch<TResult>(Func<IndexSearcher, TResult> action, TResult defaultValue) { _searcherManager.MaybeRefreshBlocking(); var indexSearcher = _searcherManager.Acquire(); try { return action(indexSearcher); } catch (FileNotFoundException) { // It's not indexed yet. return defaultValue; } finally { _searcherManager.Release(indexSearcher); } }
با استفاده از searcherManager، در طول مدت زمان کوتاهی، بر روی ایندکس قفلگذاری شده و یک indexSearcher امن، در اختیار متدهای استفاده کنندهی از آن قرار میگیرند و در پایان کار، این قفل رها میشود.
برای مثال یک نمونه روش استفاده از این indexSearcher امن، به صورت زیر است:
public int GetNumberOfDocuments() => DoSearch(indexSearcher => indexSearcher.IndexReader.NumDocs, defaultValue: 0);
مابقی مثالهای آنرا میتوانید در کلاس FullTextSearchService مشاهده کنید که به همراه یافتن «مطالب مشابه»، جستجوهای صفحه بندی شده، جستجوهای مرتب شدهی بر اساس یک فیلد، امکان دسترسی به تمام اسناد ذخیره شدهی در ایندکس لوسین و امثال آن است که کلیات آن با قبل تفاوتی نکردهاست و مطالب و نکات آنرا پیشتر در مقالات سری لوسین بررسی کردهایم. تنها تفاوت مهمی که در اینجا وجود دارد، نحوهی برپایی و راه اندازی تحلیلگر، خواننده و نویسندهی ایندکس آن است که در این مطلب بررسی شدند؛ وگرنه کلیات جستجوی پیشرفتهی آن، مانند قبل است و تفاوت خاصی نکردهاست.
1) دریافت کتابخانههای لازم
نیاز به کتابخانههای Lucene.NET و همچنین Lucene.Net Contrib است که هر دو مورد را به سادگی توسط NuGet میتوانید دریافت و نصب کنید.
Highlighter استفاده شده، در کتابخانه Lucene.Net Contrib قرار دارد. به همین جهت این مورد را نیز باید جداگانه دریافت کرد.
2) تهیه منبع داده
در اینجا جهت سادگی کار فرض کنید که لیستی از مطالب را به فرمت زیر دراختیار داریم:
public class Post { public int Id { set; get; } public string Title { set; get; } public string Body { set; get; } }
3) تبدیل اطلاعات به فرمت Lucene.NET
همانطور که عنوان شد نیاز است هر رکورد از اطلاعات خود را به شیء Document نگاشت کنیم. نمونهای از اینکار را در متد ذیل مشاهده مینمائید:
static Document MapPostToDocument(Post post) { var postDocument = new Document(); postDocument.Add(new Field("Id", post.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); postDocument.Add(new Field("Title", post.Title, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS)); postDocument.Add(new Field("Body", post.Body, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS)); return postDocument; }
کار با ایجاد یک وهله از شیء Document شروع شده و سپس اطلاعات به صوت فیلدهایی به این سند اضافه میشوند.
توضیحات آرگومانهای مختلف سازنده کلاس Field:
- در ابتدا نام فیلد مورد نظر ذکر میگردد.
- سپس مقدار متناظر با آن فیلد، به صورت رشته باید معرفی شود.
- آرگومان سوم آن مشخص میکند که اصل اطلاعات نیز علاوه بر ایندکس شدن باید در فایلهای Lucene ذخیره شوند یا خیر. توسط Field.Store.YES مشخص میکنیم که بله؛ علاقمندیم تا اصل اطلاعات نیز از طریق Lucene قابل بازیابی باشند. این مورد جهت نمایش سریع نتایج جستجوها میتواند مفید باشد. اگر قرار نیست اطلاعاتی را از این فیلد خاص به کاربر نمایش دهید میتوانید از گزینه Field.Store.NO استفاده کنید. همچنین امکان فشرده سازی اطلاعات ذخیره شده با انتخاب گزینه Field.Store.COMPRESS نیز میسر است.
- توسط آرگومان چهارم آن تعیین خواهیم کرد که اطلاعات فیلد مورد نظر ایندکس شوند یا خیر. مقدار Field.Index.NOT_ANALYZED سبب عدم ایندکس شدن فیلد Id میشوند (چون قرار نیست روی id در قسمت جستجوی عمومی سایت، جستجویی صورت گیرد). به کمک مقدار Field.Index.ANALYZED، مقدار معرفی شده، ایندکس خواهد شد.
- پارامتر پنجم آنرا جهت سرعت عمل در نمایان سازی/برجسته کردن و highlighting عبارات جستجو شده در متنهای یافت شده معرفی کردهایم. الگوریتمهای متناظر با این روش در فایلهای Lucene.Net Contrib قرار دارند.
یک نکته
اگر اطلاعاتی را که قرار است ایندکس کنید از نوع HTML میباشند، بهتر است تمام تگهای آنرا پیش از افزودن به لوسین حذف کنید. به این ترتیب نتایج جستجوی دقیقتری را میتوان شاهد بود. برای این منظور میتوان از متد ذیل کمک گرفت:
public static string RemoveHtmlTags(string text) { return string.IsNullOrEmpty(text) ? string.Empty : Regex.Replace(text, @"<(.|\n)*?>", string.Empty); }
4) تهیه Full text index به کمک Lucene.NET
تا اینجا توانستیم اطلاعات خود را به فرمت اسناد لوسین تبدیل کنیم. اکنون ثبت و تبدیل آنها به فایلهای Full text search لوسین به سادگی زیر است:
static readonly Lucene.Net.Util.Version _version = Lucene.Net.Util.Version.LUCENE_29; public static void CreateIdx(IEnumerable<Post> dataList) { var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex")); var analyzer = new StandardAnalyzer(_version); using (var writer = new IndexWriter(directory, analyzer, create: true, mfl: IndexWriter.MaxFieldLength.UNLIMITED)) { foreach (var post in dataList) { writer.AddDocument(MapPostToDocument(post)); } writer.Optimize(); writer.Commit(); writer.Close(); directory.Close(); } }
ذکر version در اینجا ضروری است؛ از این جهت که اگر ایندکسی با فرمت مثلا LUCENE_29 تهیه شود ممکن است با نگارش بعدی این کتابخانه سازگار نباشد و در صورت ارتقاء، نتایج جستجوی انجام شده، کاملا بیربط نمایش داده شوند. با ذکر صریح نگارش، دیگر این اتفاق رخ نخواهد داد.
نکته
StandardAnalyzer توکار لوسین، امکان دریافت لیستی از واژههایی که نباید ایندکس شوند را نیز دارا است. اطلاعات بیشتر در اینجا.
5) به روز رسانی ایندکسها
به کمک سه متد ذیل میتوان اطلاعات ایندکسهای موجود را به روز یا حذف کرد:
public static void UpdateIndex(Post post) { var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex")); var analyzer = new StandardAnalyzer(_version); using (var indexWriter = new IndexWriter(directory, analyzer, create: false, mfl: IndexWriter.MaxFieldLength.UNLIMITED)) { var newDoc = MapPostToDocument(post); indexWriter.UpdateDocument(new Term("Id", post.Id.ToString()), newDoc); indexWriter.Commit(); indexWriter.Close(); directory.Close(); } } public static void DeleteIndex(Post post) { var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex")); var analyzer = new StandardAnalyzer(_version); using (var indexWriter = new IndexWriter(directory, analyzer, create: false, mfl: IndexWriter.MaxFieldLength.UNLIMITED)) { indexWriter.DeleteDocuments(new Term("Id", post.Id.ToString())); indexWriter.Commit(); indexWriter.Close(); directory.Close(); } } public static void AddIndex(Post post) { var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex")); var analyzer = new StandardAnalyzer(_version, getStopWords()); using (var indexWriter = new IndexWriter(directory, analyzer, create: false, mfl: IndexWriter.MaxFieldLength.UNLIMITED)) { var searchQuery = new TermQuery(new Term("Id", post.Id.ToString())); indexWriter.DeleteDocuments(searchQuery); var newDoc = MapPostToDocument(post); indexWriter.AddDocument(newDoc); indexWriter.Commit(); indexWriter.Close(); directory.Close(); } }
محل فراخوانی این متدها هم میتواند در کنار متدهای به روز رسانی اطلاعات اصلی در بانک اطلاعاتی برنامه باشند. اگر رکوردی اضافه یا حذف شده، ایندکس متناظر نیز باید به روز شود.
6) جستجو در اطلاعات ایندکس شده و نمایش آنها به همراه نمایان/برجسته سازی عبارات جستجو شده
قسمت نهایی کار با لوسین و اطلاعات ایندکسهای تهیه شده، کوئری گرفتن از آنها است. متدهای کامل مورد نیاز را در ذیل مشاهده میکنید:
public static void Query(string term) { var directory = FSDirectory.Open(new DirectoryInfo(Environment.CurrentDirectory + "\\LuceneIndex")); using (var searcher = new IndexSearcher(directory, readOnly: true)) { var analyzer = new StandardAnalyzer(_version); var parser = new MultiFieldQueryParser(_version, new[] { "Body", "Title" }, analyzer); var query = parseQuery(term, parser); var hits = searcher.Search(query, 10).ScoreDocs; if (hits.Length == 0) { term = searchByPartialWords(term); query = parseQuery(term, parser); hits = searcher.Search(query, 10).ScoreDocs; } FastVectorHighlighter fvHighlighter = new FastVectorHighlighter(true, true); foreach (var scoreDoc in hits) { var doc = searcher.Doc(scoreDoc.doc); string bestfragment = fvHighlighter.GetBestFragment( fvHighlighter.GetFieldQuery(query), searcher.GetIndexReader(), docId: scoreDoc.doc, fieldName: "Body", fragCharSize: 400); var id = doc.Get("Id"); var title = doc.Get("Title"); var score = scoreDoc.score; Console.WriteLine(bestfragment); } searcher.Close(); directory.Close(); } } private static Query parseQuery(string searchQuery, QueryParser parser) { Query query; try { query = parser.Parse(searchQuery.Trim()); } catch (ParseException) { query = parser.Parse(QueryParser.Escape(searchQuery.Trim())); } return query; } private static string searchByPartialWords(string bodyTerm) { bodyTerm = bodyTerm.Replace("*", "").Replace("?", ""); var terms = bodyTerm.Trim().Replace("-", " ").Split(' ') .Where(x => !string.IsNullOrEmpty(x)) .Select(x => x.Trim() + "*"); bodyTerm = string.Join(" ", terms); return bodyTerm; }
اکثر سایتها را که بررسی کنید، جستجوی بر روی یک فیلد را توضیح دادهاند. در اینجا نحوه جستجو بر روی چند فیلد را به کمک MultiFieldQueryParser ملاحظه میکنید.
نکتهی مهمی را هم که در اینجا باید به آن دقت داشت، حساس بودن لوسین به کوچکی و بزرگی نام فیلدهای معرفی شده است و در صورت عدم رعایت این مساله، جستجوی شما نتیجهای را دربر نخواهد داشت.
در ادامه برای parse اطلاعات، از متد کمکی parseQuery استفاده شده است. ممکن است به ParseException بخاطر یک سری حروف خاص بکارگرفته شده در عبارات مورد جستجو برسیم. در اینجا میتوان توسط متد QueryParser.Escape، اطلاعات دریافتی را اصلاح کرد.
سپس نحوه استفاده از کوئری تهیه شده و متد Search را ملاحظه میکنید. در اینجا بهتر است تعداد رکوردهای بازگشت داده شده را تعیین کرد (به کمک آرگومان دوم متد جستجو) تا بیجهت سرعت عملیات را پایین نیاورده و همچنین مصرف حافظه سیستم را نیز بالا نبریم.
ممکن است تعداد hits یا نتایج حاصل صفر باشد؛ بنابراین بد نیست خودمان دست به کار شده و به کمک متد searchByPartialWords، ورودی کاربر را بر اساس زبان جستجوی ویژه لوسین اندکی بهینه کنیم تا بتوان به نتایج بهتری دست یافت.
در آخر نحوه کار با ScoreDocs یافت شده را ملاحظه میکنید. اگر محتوای فیلد را در حین ایندکس سازی ذخیره کرده باشیم، به کمک متد doc.Get میتوان به اطلاعات کامل آن نیز دست یافت.
همچنین نکته دیگری را که در اینجا میتوان ملاحظه کرد استفاده از FastVectorHighlighter میباشد. به کمک این Highlighter ویژه میتوان نتایج جستجو را شبیه به نتایج نمایش داده شده توسط موتور جستجوی گوگل درآورد. برای مثال اگر شخصی ef code first را جستجو کرد، توسط متد GetBestFragment، بهترین جزئی که شامل بیشترین تعداد حروف جستجو شده است، یافت گردیده و همچنین به کمک تگهای B، ضخیم نمایش داده خواهند شد.
Curry and Partial methods
public static class CurryMethodExtensions { public static Func< A, Func< B, Func< C, R > > > Curry< A, B, C, R >( this Func< A, B, C, R > f ) { return a => b => c => f( a, b, c ); } }
Func< int, int, int, int > addNumbers = ( x, y, z ) => x + y + z; var f1 = addNumbers.Curry(); Func< int, Func< int, int > > f2 = f1( 3 ); Func< int, int > f3 = f2( 4 ); Console.WriteLine( f3( 5 ) );
public static class CurryMethodExtensions { public static Func< C, R > Partial< A, B, C, R >( this Func< A, B, C, R > f, A a, B b ) { return c => f( a, b, c ); } }
Func< int, int, int, int > sumNumbers = ( x, y, z ) => x + y + z; Func< int, int > f4 = sumNumbers.Partial( 3, 4 ); Console.WriteLine( f4( 5 ) );
WeakReference
var obj = new WeakReferenceTest { FirstName = "Vahid" }; var w = new WeakReference(obj); obj = null; GC.Collect(); var weakReferenceTest = w.Target as WeakReferenceTest; if ( weakReferenceTest != null ) Console.WriteLine( weakReferenceTest.FirstName );
Lazy<T>
public abstract class ThreadSafeLazyBaseSingleton< T > where T : new() { static readonly Lazy< T > lazy = new Lazy< T >( () => new T() ); public static T Instance => lazy.Value; }
BigInteger
var positiveString = "91389681247993671255433422114345532000000"; var negativeString = "-9031583741089631207100208803453423537140000"; var posBigInt = BigInteger.Parse( positiveString ); Console.WriteLine( posBigInt ); var negBigInt = BigInteger.Parse( negativeString ); Console.WriteLine( negBigInt );