ردیابی تغییرات در سمت کلاینت توسط Web API
فرض کنید میخواهیم از سرویسهای REST-based برای انجام عملیات CRUD روی یک Object graph استفاده کنیم. همچنین میخواهیم رویکردی در سمت کلاینت برای بروز رسانی کلاس موجودیتها پیاده سازی کنیم که قابل استفاده مجدد (reusable) باشد. علاوه بر این دسترسی دادهها توسط مدل Code-First انجام میشود.
در مثال جاری یک اپلیکیشن کلاینت (برنامه کنسول) خواهیم داشت که سرویسهای ارائه شده توسط پروژه Web API را فراخوانی میکند. هر پروژه در یک Solution مجزا قرار دارد، با این کار یک محیط n-Tier را شبیه سازی میکنیم.
مدل زیر را در نظر بگیرید.
همانطور که میبینید مدل مثال جاری مشتریان و شماره تماس آنها را ارائه میکند. میخواهیم مدلها و کد دسترسی به دادهها را در یک سرویس Web API پیاده سازی کنیم تا هر کلاینتی که به HTTP دسترسی دارد بتواند از آن استفاده کند. برای ساخت سرویس مذکور مراحل زیر را دنبال کنید.
- در ویژوال استودیو پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب پروژه را Web API انتخاب کنید. نام پروژه را به Recipe4.Service تغییر دهید.
- کنترلر جدیدی با نام CustomerController به پروژه اضافه کنید.
- کلاسی با نام BaseEntity ایجاد کنید و کد آن را مطابق لیست زیر تغییر دهید. تمام موجودیتها از این کلاس پایه مشتق خواهند شد که خاصیتی بنام TrackingState را به آنها اضافه میکند. کلاینتها هنگام ویرایش آبجکت موجودیتها باید این فیلد را مقدار دهی کنند. همانطور که میبینید این خاصیت از نوع TrackingState enum مشتق میشود. توجه داشته باشید که این خاصیت در دیتابیس ذخیره نخواهد شد. با پیاده سازی enum وضعیت ردیابی موجودیتها بدین روش، وابستگیهای EF را برای کلاینت از بین میبریم. اگر قرار بود وضعیت ردیابی را مستقیما از EF به کلاینت پاس دهیم وابستگیهای بخصوصی معرفی میشدند. کلاس DbContext اپلیکیشن در متد OnModelCreating به EF دستور میدهد که خاصیت TrackingState را به جدول موجودیت نگاشت نکند.
public abstract class BaseEntity { protected BaseEntity() { TrackingState = TrackingState.Nochange; } public TrackingState TrackingState { get; set; } } public enum TrackingState { Nochange, Add, Update, Remove, }
- کلاسهای موجودیت Customer و PhoneNumber را ایجاد کنید و کد آنها را مطابق لیست زیر تغییر دهید.
public class Customer : BaseEntity { public int CustomerId { get; set; } public string Name { get; set; } public string Company { get; set; } public virtual ICollection<Phone> Phones { get; set; } } public class Phone : BaseEntity { public int PhoneId { get; set; } public string Number { get; set; } public string PhoneType { get; set; } public int CustomerId { get; set; } public virtual Customer Customer { get; set; } }
- با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
- کلاسی با نام Recipe4Context ایجاد کنید و کد آن را مطابق لیست زیر تغییر دهید. در این کلاس از یکی از قابلیتهای جدید EF 6 بنام "Configuring Unmapped Base Types" استفاده کرده ایم. با استفاده از این قابلیت جدید هر موجودیت را طوری پیکربندی میکنیم که خاصیت TrackingState را نادیده بگیرند. برای اطلاعات بیشتر درباره این قابلیت EF 6 به این لینک مراجعه کنید.
public class Recipe4Context : DbContext { public Recipe4Context() : base("Recipe4ConnectionString") { } public DbSet<Customer> Customers { get; set; } public DbSet<Phone> Phones { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { // Do not persist TrackingState property to data store // This property is used internally to track state of // disconnected entities across service boundaries. // Leverage the Custom Code First Conventions features from Entity Framework 6. // Define a convention that performs a configuration for every entity // that derives from a base entity class. modelBuilder.Types<BaseEntity>().Configure(x => x.Ignore(y => y.TrackingState)); modelBuilder.Entity<Customer>().ToTable("Customers"); modelBuilder.Entity<Phone>().ToTable("Phones"); } }
- فایل Web.config پروژه را باز کنید و رشته اتصال زیر را به قسمت ConnectionStrings اضافه نمایید.
<connectionStrings> <add name="Recipe4ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
- فایل Global.asax را باز کنید و کد زیر را به متد Application_Start اضافه نمایید. این کد بررسی Entity Framework Model Compatibility را غیرفعال میکند و به JSON serializer دستور میدهد که self-referencing loop خواص پیمایشی را نادیده بگیرد. این حلقه بدلیل رابطه bidirectional بین موجودیتهای Customer و PhoneNumber بوجود میآید.
protected void Application_Start() { // Disable Entity Framework Model Compatibilty Database.SetInitializer<Recipe1Context>(null); // The bidirectional navigation properties between related entities // create a self-referencing loop that breaks Web API's effort to // serialize the objects as JSON. By default, Json.NET is configured // to error when a reference loop is detected. To resolve problem, // simply configure JSON serializer to ignore self-referencing loops. GlobalConfiguration.Configuration.Formatters.JsonFormatter .SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; ... }
- کلاسی با نام EntityStateFactory بسازید و کد آن را مطابق لیست زیر تغییر دهید. این کلاس مقدار خاصیت TrackingState که به کلاینتها ارائه میشود را به مقادیر متناظر کامپوننتهای ردیابی EF تبدیل میکند.
public static EntityState Set(TrackingState trackingState) { switch (trackingState) { case TrackingState.Add: return EntityState.Added; case TrackingState.Update: return EntityState.Modified; case TrackingState.Remove: return EntityState.Deleted; default: return EntityState.Unchanged; } }
- در آخر کد کنترلر CustomerController را مطابق لیست زیر بروز رسانی کنید.
public class CustomerController : ApiController { // GET api/customer public IEnumerable<Customer> Get() { using (var context = new Recipe4Context()) { return context.Customers.Include(x => x.Phones).ToList(); } } // GET api/customer/5 public Customer Get(int id) { using (var context = new Recipe4Context()) { return context.Customers.Include(x => x.Phones).FirstOrDefault(x => x.CustomerId == id); } } [ActionName("Update")] public HttpResponseMessage UpdateCustomer(Customer customer) { using (var context = new Recipe4Context()) { // Add object graph to context setting default state of 'Added'. // Adding parent to context automatically attaches entire graph // (parent and child entities) to context and sets state to 'Added' // for all entities. context.Customers.Add(customer); foreach (var entry in context.ChangeTracker.Entries<BaseEntity>()) { entry.State = EntityStateFactory.Set(entry.Entity.TrackingState); if (entry.State == EntityState.Modified) { // For entity updates, we fetch a current copy of the entity // from the database and assign the values to the orginal values // property from the Entry object. OriginalValues wrap a dictionary // that represents the values of the entity before applying changes. // The Entity Framework change tracker will detect // differences between the current and original values and mark // each property and the entity as modified. Start by setting // the state for the entity as 'Unchanged'. entry.State = EntityState.Unchanged; var databaseValues = entry.GetDatabaseValues(); entry.OriginalValues.SetValues(databaseValues); } } context.SaveChanges(); } return Request.CreateResponse(HttpStatusCode.OK, customer); } [HttpDelete] [ActionName("Cleanup")] public HttpResponseMessage Cleanup() { using (var context = new Recipe4Context()) { context.Database.ExecuteSqlCommand("delete from phones"); context.Database.ExecuteSqlCommand("delete from customers"); return Request.CreateResponse(HttpStatusCode.OK); } } }
- در ویژوال استودیو پروژه جدیدی از نوع Console Application بسازید و نام آن را به Recipe4.Client تغییر دهید.
- فایل program.cs را باز کنید و کد آن را مطابق لیست زیر تغییر دهید.
internal class Program { private HttpClient _client; private Customer _bush, _obama; private Phone _whiteHousePhone, _bushMobilePhone, _obamaMobilePhone; private HttpResponseMessage _response; private static void Main() { Task t = Run(); t.Wait(); Console.WriteLine("\nPress <enter> to continue..."); Console.ReadLine(); } private static async Task Run() { var program = new Program(); program.ServiceSetup(); // do not proceed until clean-up completes await program.CleanupAsync(); program.CreateFirstCustomer(); // do not proceed until customer is added await program.AddCustomerAsync(); program.CreateSecondCustomer(); // do not proceed until customer is added await program.AddSecondCustomerAsync(); // do not proceed until customer is removed await program.RemoveFirstCustomerAsync(); // do not proceed until customers are fetched await program.FetchCustomersAsync(); } private void ServiceSetup() { // set up infrastructure for Web API call _client = new HttpClient { BaseAddress = new Uri("http://localhost:62799/") }; // add Accept Header to request Web API content negotiation to return resource in JSON format _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue ("application/json")); } private async Task CleanupAsync() { // call the cleanup method from the service _response = await _client.DeleteAsync("api/customer/cleanup/"); } private void CreateFirstCustomer() { // create customer #1 and two phone numbers _bush = new Customer { Name = "George Bush", Company = "Ex President", // set tracking state to 'Add' to generate a SQL Insert statement TrackingState = TrackingState.Add, }; _whiteHousePhone = new Phone { Number = "212 222-2222", PhoneType = "White House Red Phone", // set tracking state to 'Add' to generate a SQL Insert statement TrackingState = TrackingState.Add, }; _bushMobilePhone = new Phone { Number = "212 333-3333", PhoneType = "Bush Mobile Phone", // set tracking state to 'Add' to generate a SQL Insert statement TrackingState = TrackingState.Add, }; _bush.Phones.Add(_whiteHousePhone); _bush.Phones.Add(_bushMobilePhone); } private async Task AddCustomerAsync() { // construct call to invoke UpdateCustomer action method in Web API service _response = await _client.PostAsync("api/customer/updatecustomer/", _bush, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { // capture newly created customer entity from service, which will include // database-generated Ids for all entities _bush = await _response.Content.ReadAsAsync<Customer>(); _whiteHousePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _bush.CustomerId); _bushMobilePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _bush.CustomerId); Console.WriteLine("Successfully created Customer {0} and {1} Phone Numbers(s)", _bush.Name, _bush.Phones.Count); foreach (var phoneType in _bush.Phones) { Console.WriteLine("Added Phone Type: {0}", phoneType.PhoneType); } } else Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase); } private void CreateSecondCustomer() { // create customer #2 and phone numbers _obama = new Customer { Name = "Barack Obama", Company = "President", // set tracking state to 'Add' to generate a SQL Insert statement TrackingState = TrackingState.Add, }; _obamaMobilePhone = new Phone { Number = "212 444-4444", PhoneType = "Obama Mobile Phone", // set tracking state to 'Add' to generate a SQL Insert statement TrackingState = TrackingState.Add, }; // set tracking state to 'Modifed' to generate a SQL Update statement _whiteHousePhone.TrackingState = TrackingState.Update; _obama.Phones.Add(_obamaMobilePhone); _obama.Phones.Add(_whiteHousePhone); } private async Task AddSecondCustomerAsync() { // construct call to invoke UpdateCustomer action method in Web API service _response = await _client.PostAsync("api/customer/updatecustomer/", _obama, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { // capture newly created customer entity from service, which will include // database-generated Ids for all entities _obama = await _response.Content.ReadAsAsync<Customer>(); _whiteHousePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _obama.CustomerId); _bushMobilePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _obama.CustomerId); Console.WriteLine("Successfully created Customer {0} and {1} Phone Numbers(s)", _obama.Name, _obama.Phones.Count); foreach (var phoneType in _obama.Phones) { Console.WriteLine("Added Phone Type: {0}", phoneType.PhoneType); } } else Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase); } private async Task RemoveFirstCustomerAsync() { // remove George Bush from underlying data store. // first, fetch George Bush entity, demonstrating a call to the // get action method on the service while passing a parameter var query = "api/customer/" + _bush.CustomerId; _response = _client.GetAsync(query).Result; if (_response.IsSuccessStatusCode) { _bush = await _response.Content.ReadAsAsync<Customer>(); // set tracking state to 'Remove' to generate a SQL Delete statement _bush.TrackingState = TrackingState.Remove; // must also remove bush's mobile number -- must delete child before removing parent foreach (var phoneType in _bush.Phones) { // set tracking state to 'Remove' to generate a SQL Delete statement phoneType.TrackingState = TrackingState.Remove; } // construct call to remove Bush from underlying database table _response = await _client.PostAsync("api/customer/updatecustomer/", _bush, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { Console.WriteLine("Removed {0} from database", _bush.Name); foreach (var phoneType in _bush.Phones) { Console.WriteLine("Remove {0} from data store", phoneType.PhoneType); } } else Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase); } else { Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase); } } private async Task FetchCustomersAsync() { // finally, return remaining customers from underlying data store _response = await _client.GetAsync("api/customer/"); if (_response.IsSuccessStatusCode) { var customers = await _response.Content.ReadAsAsync<IEnumerable<Customer>>(); foreach (var customer in customers) { Console.WriteLine("Customer {0} has {1} Phone Numbers(s)", customer.Name, customer.Phones.Count()); foreach (var phoneType in customer.Phones) { Console.WriteLine("Phone Type: {0}", phoneType.PhoneType); } } } else { Console.WriteLine("{0} ({1})", (int)_response.StatusCode, _response.ReasonPhrase); } } }
- در آخر کلاسهای Customer, Phone و BaseEntity را به پروژه کلاینت اضافه کنید. چنین کدهایی بهتر است در لایه مجزایی قرار گیرند و بین لایههای مختلف اپلیکیشن به اشتراک گذاشته شوند.
اگر اپلیکیشن کلاینت را اجرا کنید با خروجی زیر مواجه خواهید شد.
شرح مثال جاری
با اجرای اپلیکیشن Web API شروع کنید. این اپلیکیشن یک MVC Web Controller دارد که پس از اجرا شما را به صفحه خانه هدایت میکند. در این مرحله سایت در حال اجرا است و سرویسها قابل دسترسی هستند.
سپس اپلیکیشن کنسول را باز کنید و روی خط اول کد فایل program.cs یک breakpoint قرار داده و آن را اجرا کنید. ابتدا آدرس سرویس را نگاشت میکنیم و از سرویس درخواست میکنیم که اطلاعات را با فرمت JSON بازگرداند.
سپس توسط متد DeleteAsync که روی آبجکت HttpClient تعریف شده است اکشن متد Cleanup را روی سرویس فراخوانی میکنیم. این فراخوانی تمام دادههای پیشین را حذف میکند.
در قدم بعدی یک مشتری بهمراه دو شماره تماس میسازیم. توجه کنید که برای هر موجودیت مشخصا خاصیت TrackingState را مقدار دهی میکنیم تا کامپوننتهای Change-tracking در EF عملیات لازم SQL برای هر موجودیت را تولید کنند.
سپس توسط متد PostAsync که روی آبجکت HttpClient تعریف شده اکشن متد UpdateCustomer را روی سرویس فراخوانی میکنیم. اگر به این اکشن متد یک breakpoint اضافه کنید خواهید دید که موجودیت مشتری را بعنوان یک پارامتر دریافت میکند و آن را به context جاری اضافه مینماید. با اضافه کردن موجودیت به کانتکست جاری کل object graph اضافه میشود و EF شروع به ردیابی تغییرات آن میکند. دقت کنید که آبجکت موجودیت باید Add شود و نه Attach.
قدم بعدی جالب است، هنگامی که از خاصیت DbChangeTracker استفاده میکنیم. این خاصیت روی آبجکت context تعریف شده و یک <IEnumerable<DbEntityEntry را با نام Entries ارائه میکند. در اینجا بسادگی نوع پایه EntityType را تنظیم میکنیم. این کار به ما اجازه میدهد که در تمام موجودیت هایی که از نوع BaseEntity هستند پیمایش کنیم. اگر بیاد داشته باشید این کلاس، کلاس پایه تمام موجودیتها است. در هر مرحله از پیمایش (iteration) با استفاده از کلاس EntityStateFactory مقدار خاصیت TrackingState را به مقدار متناظر در سیستم ردیابی EF تبدیل میکنیم. اگر کلاینت مقدار این فیلد را به Modified تنظیم کرده باشد پردازش بیشتری انجام میشود. ابتدا وضعیت موجودیت را از Modified به Unchanged تغییر میدهیم. سپس مقادیر اصلی را با فراخوانی متد GetDatabaseValues روی آبجکت Entry از دیتابیس دریافت میکنیم. فراخوانی این متد مقادیر موجود در دیتابیس را برای موجودیت جاری دریافت میکند. سپس مقادیر بدست آمده را به کلکسیون OriginalValues اختصاص میدهیم. پشت پرده، کامپوننتهای EF Change-tracking بصورت خودکار تفاوتهای مقادیر اصلی و مقادیر ارسالی را تشخیص میدهند و فیلدهای مربوطه را با وضعیت Modified علامت گذاری میکنند. فراخوانیهای بعدی متد SaveChanges تنها فیلدهایی که در سمت کلاینت تغییر کرده اند را بروز رسانی خواهد کرد و نه تمام خواص موجودیت را.
در اپلیکیشن کلاینت عملیات افزودن، بروز رسانی و حذف موجودیتها توسط مقداردهی خاصیت TrackingState را نمایش داده ایم.
متد UpdateCustomer در سرویس ما مقادیر TrackingState را به مقادیر متناظر EF تبدیل میکند و آبجکتها را به موتور change-tracking ارسال میکند که نهایتا منجر به تولید دستورات لازم SQL میشود.
نکته: در اپلیکیشنهای واقعی بهتر است کد دسترسی دادهها و مدلهای دامنه را به لایه مجزایی منتقل کنید. همچنین پیاده سازی فعلی change-tracking در سمت کلاینت میتواند توسعه داده شود تا با انواع جنریک کار کند. در این صورت از نوشتن مقادیر زیادی کد تکراری جلوگیری خواهید کرد و از یک پیاده سازی میتوانید برای تمام موجودیتها استفاده کنید.
EF Code First #12
public class CustomMaxLengthConvention : Convention { public CustomMaxLengthConvention() { this.Properties<string>().Configure(p => p.HasMaxLength(450)); } } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.AddAfter<MaxLengthAttributeConvention>(new CustomMaxLengthConvention());
قابلیت Attribute Routing در ASP.NET MVC 5
EF Code First #1
الف) database first: مربوط به زمانیکه یک دیتابیس از پیش طراحی شده با ساختار جداول و ارتباطات آن موجود است. این روش، ساختار بانک اطلاعاتی شما رو مهندسی معکوس کرده و یک سری کد و دیاگرام را برای استفاده توسط موتور اصلی EF تولید میکنه.
ب) روش model first: در VS.NET میتونید طراحیهای مرتبط با جداول، کلاسها و روابط رو انجام بدید. کدهای اضافی برای کار با موتور EF و همچنین به روز رسانی بانک اطلاعاتی رو به صورت خودکار انجام خواهد داد.
ج) روش code first: در این روش دیگر خبری از طراح بصری نیست. کار با کدنویسی و طراحی کلاسها و ایجاد روابط بین آنها توسط برنامه نویس شروع میشود. نهایتا این کلاسها توسط موتور EF استفاده خواهند شد. امکان تبدیل این کلاسها به بانک اطلاعاتی متناظر و همچنین به روز رسانی خودکار بانک اطلاعاتی با تغییر ساختار کلاسها هم پیش بینی شده. روش code first بهترین حالت است برای کسانی که نمیخواهند از انبوهی از کدهای تولید شده به صورت خودکار (حاصل از مهندسی معکوس یک بانک اطلاعاتی موجود) استفاده کنند و میخواهند کنترل بیشتری بر روابط و اختصاصی سازی آنها داشته باشند. در این حالت میتونید بدون نیاز به یک بانک اطلاعاتی یک برنامه را کامل کنید (منهای مباحث تست سیستم).
روش code first در حال حاضر روشی است که بیشتر توسط تیم EF تبلیغ میشود و در حال توسعه است. مابقی رو هم کم کم دارند تبدیل میکنند به پوستهای برای حالت code first. مثلا ابزار تهیه کردند برای مهندسی معکوس یک بانک اطلاعاتی موجود به روش code first. کد نهایی تمیزتری داره؛ چون کلاسها را خودتان طراحی میکنید و توسط ابزارها به صورت خودکار تولید نمیشوند، کنترل بیشتر و نهایتا کیفیت بالاتری دارند. ساده است؛ درگیر نگهداری edmx modelها نخواهید بود. به روز رسانی بانک اطلاعاتی آن هم میتواند کاملا خودکار شود.
برای اطلاعات بیشتر در مورد مزایای این روش یا تاریخچه EF متن قسمت جاری را یکبار مطالعه کرده و قسمتهای «مروری سریع بر تاریخچه Entity framework code first» و «مزایای EF Code first» را بررسی کنید.
+ کل قسمت EF از اینجا به صورت یک فایل PDF قابل دریافت است. در مورد اکثر مواردی که عنوان کردید به صورت مجزا بحث شده و توضیحات کافی ارائه شدن.
ASP.NET MVC #4
من هم مانند دوستان خیلی علاقمندم که از setting استفاده کنم که متاسفانه لینک دانلود خراب است لطفا بررسی کنید.
ممنون ممنون
تهیه کوئری بر روی ایندکسهای Full Text Search
با استفاده از Contains predicate چه اطلاعاتی را میتوان جستجو کرد؟
متد Contains مخصوص FTS، قابلیت یافتن کلمات و عبارات، تطابق کامل با عبارت در حال جستجو و یا حتی جستجوهای فازی را دارد. همچنین حالات مختلف صرفی یا inflectional یک کلمه را نیز میتواند جستجو کند (مانند jump، jumps و jumped). البته این مورد وابسته است به زبانی که در حین ایجاد ایندکس مشخص میشود. امکان یافتن کلماتی نزدیک و مشابه به کلماتی دیگر نیز پیش بینی شدهاست. پیشوندها و پسوندها را نیز میتوان جستجو کرد. امکان تعیین وزن و اهمیت کلمات در حال جستجو وجود دارند (برای مثال در این جستجوی خاص، کلمهی ویژه اهمیت بیشتری نسبت به بقیه دارد). متد Contains امکان جستجوی Synonyms را نیز دارد. برای مثال یافتن رکوردهایی که معنایی مشابه need دارند اما دقیقا حاوی کلمهی need نیستند.
بررسی ریز جزئیات توانمندیهای Contains predicate
1) جستجوی کلمات ساده
-- Simple term SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'data');
باید دقت داشت که این نوع کوئریها، حساس به حروف کوچک و بزرگ نیستند.
همچنین عبارت وارد شده از نوع یونیکد است. به همین جهت برای جلوگیری از تغییر encoding رشته وارد شده (و تفسیر آن بر اساس Collation بانک اطلاعاتی)، یک N به ابتدای عبارت افزوده شدهاست.
2) جستجوی عبارات
-- Simple term - phrase SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'"data warehouse"');
3) استفاده از عملگرهای منطقی مانند OR و AND
-- Simple terms with logical OR SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'data OR index');
و یا نحوهی بکارگیری AND NOT در کوئری ذیل مشخص شدهاست:
-- Simple terms with logical AND NOT SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'data AND NOT mining');
به علاوه با استفاده از پرانتزها میتوان تقدم و تاخر عملگرهای منطقی را بهتر مشخص کرد:
-- Simple terms with mny logical operators, order defined with parentheses SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'data OR (fact AND warehouse)');
4) جستجوی پیشوندها
-- Prefix SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'"add*"');
5) جستجوهای Proximity
Proximity در اینجا به معنای یافتن واژههایی هستند که نزدیک (از لحاظ تعداد فاصله بر حسب کلمات) به واژهای دیگر میباشند.
-- Simple proximity SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'NEAR(problem, data)');
همچنین میتوان مشخص کرد که این نزدیک بودن دقیقا به چه معنایی است:
-- Proximity with max distance 5 words SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'NEAR((problem, data),5)'); -- Proximity with max distance 1 word SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'NEAR((problem, data),1)');
همچنین میتوان مشخص کرد که ترتیب جستجو باید دقیقا بر اساس نحوهی تعریف این کلمات در کوئری باشد:
-- Proximity with max distance and order SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'NEAR((problem, data),5, TRUE)'); GO
6) جستجوی بر روی بیش از یک فیلد
در قسمت قبل، FULLTEXT INDEX انتهای بحث را بر روی دو فیلد docexcerpt و doccontent تهیه کردیم. اگر نیاز باشد تا جستجوی انجام شده هر دو فیلد را شامل شود میتوان به نحو ذیل عمل کرد:
SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS((docexcerpt,doccontent), N'data');
یک نکته: اگر تعداد ستونهای ایندکس شده زیاد است و نیاز داریم تا بر روی تمام آنها FTS انجام شود، تنها کافی است پارامتر اول متد Contains را * وارد کنیم. * در اینجا به معنای تمام ستونهایی است که در حین تشکیل FULLTEXT INDEX ذکر شدهاند.
7) جستجوهای صرفی یا inflectional
FTS بر اساس زبان انتخابی، در حین تشکیل ایندکسهای خاص خودش، یک سری آنالیزهای دستوری را نیز بر روی واژهها انجام میدهد. همچنین امکان تعریف زبان مورد استفاده در حین استفاده از متد Contains نیز وجود دارد.
-- Inflectional forms -- The next query does not return any rows SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'presentation'); -- The next query returns a row SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'FORMSOF(INFLECTIONAL, presentation)'); GO
اکنون اگر کوئری دوم را که از FORMSOF جهت تعیین روش INFLECTIONAL استفاده کرده است، اجرا کنیم، به یک رکورد خواهیم رسید که در آن جمع واژهی presentation وجود دارد.
8) جستجو برای یافتن متشابهات
برای نمونه اگر SQL Server 2012 بر روی سیستم شما نصب باشد، محل نصب واژهنامههای Synonyms یا واژههایی همانند از لحاظ معنایی را در مسیر زیر میتوانید مشاهده کنید:
C:\...\MSSQL11.MSSQLSERVER\MSSQL\FTData
<XML ID="Microsoft Search Thesaurus"> <thesaurus xmlns="x-schema:tsSchema.xml"> <diacritics_sensitive>0</diacritics_sensitive> <expansion> <sub>Internet Explorer</sub> <sub>IE</sub> <sub>IE5</sub> </expansion> <replacement> <pat>NT5</pat> <pat>W2K</pat> <sub>Windows 2000</sub> </replacement> <expansion> <sub>run</sub> <sub>jog</sub> </expansion> <expansion> <sub>need</sub> <sub>necessity</sub> </expansion> </thesaurus> </XML>
فایل tsenu.xml به صورت پیش فرض برای زبان انگلیسی آمریکایی مورد استفاده قرار میگیرد. اگر محتویات آنرا برای مثال با محتویات XML ایی فوق جایگزین کنید (در حین ذخیره باید دقت داشت که encoding فایل نیاز است Unicode باشد)، سپس باید SQL Server را از این تغییر نیز مطلع نمائیم:
-- Load the US English file EXEC sys.sp_fulltext_load_thesaurus_file 1033; GO
البته اگر اینکار را انجام ندهیم، به صورت خودکار، اولین کوئری که از THESAURUS انگلیسی استفاده میکند، سبب بارگذاری آن خواهد شد.
-- Synonyms -- The next query does not return any rows SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'need'); -- The next query returns a row SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(docexcerpt, N'FORMSOF(THESAURUS, need)'); GO
در ادامه اگر کوئری دوم را که از FORMSOF جهت تعیین روش THESAURUS استفاده کرده است، اجرا کنیم، به یک رکورد خواهیم رسید که در آن واژهی necessity به کمک محتویات فایل tsenu.xml که پیشتر تهیه کردیم، بجای need وجود دارد.
9) جستجو بر روی خواص و متادیتای فایلها
-- Document properties SELECT id, title, docexcerpt FROM dbo.Documents WHERE CONTAINS(PROPERTY(doccontent,N'Authors'), N'Test');
کار با FREETEXT
-- FREETEXT SELECT * FROM dbo.Documents WHERE FREETEXT(docexcerpt, N'data presentation need');
در کوئری فوق، کلیه رکوردهایی که با سه کلمهی وارد شده (به صورت مجزا) به نحوی تطابق داشته باشند (تطابق کامل یا بر اساس تطابقهای معنایی یا دستوری) باز گردانده خواهند شد.
استفاده از افزونهی jsTree در ASP.NET MVC
برای شناسایی ایرادات در کد و بهبود کیفیت کدها میتوانید از ابزارهای دستهی lint استفاده کنید که تعدادی از معروفترین این ابزارها (jslint ,cpplint ,eslint ,nodelint و ...) هستند.
برای نصب چنین ابزاری مثل lint میتوانید به شکل زیر آن را نصب کنید:
npm install -g eslint
برای اولین بار نیاز است که آماده سازی ابتدایی برای این کتابخانه صورت بگیرد که با دستور زیر قابل انجام است:
eslint --init
? How would you like to configure ESLint? Answer questions about your style ? Are you using ECMAScript 6 features? Yes ? Are you using ES6 modules? Yes ? Where will your code run? Browser, Node ? Do you use CommonJS? No ? Do you use JSX? No ? What style of indentation do you use? Spaces ? What quotes do you use for strings? Double ? What line endings do you use? Windows ? Do you require semicolons? Yes ? What format do you want your config file to be in? JSON Successfully created .eslintrc.json file in D:\ali\electron
eslint . یا eslint index.js
میتوانید این دستور را در در خصوصیت test، در بخش اسکریپت، به شکل زیر وارد کنید:
{ "name": "electron", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "electron .", "test":"eslint ." }, "author": "", "license": "ISC" }
npm test
D:\ali\electron\index.js 1:26 error Strings must use doublequote quotes 16:8 error Strings must use doublequote quotes 19:3 error Expected indentation of 4 space characters but found 2 indent 22:3 error Expected indentation of 4 space characters but found 2 indent ✖ 4 problems (4 errors, 0 warnings)
برای نامگذاری فایلهای js، به جای استفاده از _ از - استفاده کنید. این قاعدهای است که گیت هاب در نامگذاری فایلهای js انجام میدهد. یعنی به جای عبارت dotnet_tips.js از عبارت dotnet-tips.js استفاده کنید. خاطرتان جمع باشد که هیچ فایل جاوااسکرپیتی رسمی در الکترون، خارج از این قاعده نامگذاری نشده است.
سعی کنید از جدیدترین قواعد موجود در ES6 استفاده کنید مانند:
let برای تعریف متغیرها
const برای تعریف ثابت ها
توابع جهتی به جای نوشتن عبارت function
استفاده از template stringها به جای چسباندن رشتهها از طریق عملگر +
برای نامگذاری متغیرها از همان قوانین تعریف شده برای node.js استفاده کنید که شامل موارد زیر است:
- موقعی که یک ماژول را به عنوان یک کلاس استفاده میکنید از متد CamelCase استفاده کنید مثل BrowserWindow
- موقعی که از یک ماژول که حاوی مجموعهای از دستورات و apiها است استفاده میکنید، از قانون mixedCase استفاده کنید مثل app یا globalShortcut
- موقعی که یک api به عنوان خصوصیت یک شیء مورد استفاده قرار میگیرد، از mixedCase استفاده کنید؛ به عنوان مثال win.webContents یا fs.readFileSync
- برای مابقی اشیا مثل دستورات process و یا تگ webview که به شکل متفاوتتری مورد استفاده قرار میگیرند، از همان عنوانهای خودشان استفاده میکنیم.
موقعی که api جدیدی را میسازید و قصد تعریف متدی را دارید بهتر است دو متد برای get و set تعریف شود، نسبت به حالتی که در کتابخانه جی کوئری مورد استفاده قرار میگیرد یعنی عبارتهای زیر
setText('test'); getText();
.text([text]);