public IHttpActionResult Get() { // Anonymous and Weakly-Typed Objects return Ok(new { Id = 1, Title = "Hello from My Protected Controller!", Username = this.User.Identity.Name, Property2 = new Object1 { id = 1, name = "V" } }); // OR ... Strongly-Typed Objects return Ok(model); }
using (var store = new DocumentStore { Url = "http://localhost:8080" }.Initialize()) { using (var session = store.OpenSession()) { store.DatabaseCommands.PutAttachment(key: "file/1", etag: null, data: System.IO.File.OpenRead(@"D:\Prog\packages.config"), metadata: new RavenJObject { { "Description", "توضیحات فایل" } }); var question = new Question { By = "users/Vahid", Title = "Raven Intro", Content = "Test....", FileId = "file/1" }; session.Store(question); session.SaveChanges(); } }
RavenFS یک فایل سیستم مجازی توزیع شدهاست و برای فایلهای بزرگ چند گیگابایتی به طور بهینهای طراحی گردیدهاست تا کارآیی بانک اطلاعاتی را بالا ببرد و وجود فایلهای تکراری، از بین برود. این سیستم جدید شامل سیستم پیش فرض ایندکس گذاری میباشد و به شما این اجازه را میدهد تا بر روی متادیتاهای یک فایل از قبیل حجم، تاریخ آخرین نگارش و حتی متادیتاهای اختصاصی که شما در حین ذخیره سازی به آن اضافه میکنید، به جستوجو بپردازد. این سیستم جدید همچنین این امکان را به شما میدهد تا این اطلاعات را بین Nodeها، با کمترین میزان انتقالات جابجا کنید و دسترسی سریعتری را بین نودهای مختلف داشته باشید.
برای ذخیره سازی یک فایل ابتدا باید یک FileStore را همانند آنچه که برای DocumentStore داشتید تعریف کنید. Url که شامل همان رشته اتصالی بوده و DefaultFileSystem هم همانند DefaultDatabase که نام دیتابیس در آن ذکر میشد، در اینجا نام فایل سیستم ذکر میگردد:
var fileStore = new FilesStore() { Url = "http://localhost:8080", DefaultFileSystem = "SampleFs" }; fileStore.Initialize();
بعد از آن باید از طریق Store جدید یک سشن ایجاد شود و فایل مورد نظر را در قالب یک استریم بخوانیم:
var session = fileStore.OpenAsyncSession(); var stream = File.OpenRead("D:\\Apocalypse.Now.Redux.1979.BDRip.YIFY.mkv");
توجه داشته باشید که برای کار با فایل سیستم، همه متدهای session به صورت غیرهمزمان بوده و متد همزمانی وجود ندارد. سپس در مرحله بعد میخواهم متادیتاهای شخصی نیز به آن اضافه کنیم:
var metadata = new RavenJObject { {"User", "users/1345"}, {"Director","Francis Ford Coppola" }, {"Year","1979" } };
با استفاده از شیء RavenObject میتوانیم در قالب کلید و مقدار، مقادیر خود را ذخیره کنیم و بعد از آن همه موارد بالا که شامل فایل هدر، استریم و متادیتای اختصاصی است را رجیستر کنیم. اگر هم چندین فایل داریم میتوانید آنها را هم در همینجا رجیستر کنید:
session.RegisterUpload("mkv/sample.mkv", stream, metadata);
در مرحله بعدی تغییرات را تایید و عملیات آپلود آغاز میگردد:
await session.SaveChangesAsync();
همانطور که میبینید تمامی متدهای کاربردی این سشن به طور غیرهمزمان طراحی شدهاند.
کلیه عملیاتی که در بالا انجام شد:
var fileStore = new FilesStore() { Url = "http://localhost:8080", DefaultFileSystem = "SampleFs" }; fileStore.Initialize(); var session = fileStore.OpenAsyncSession(); var stream = File.OpenRead("D:\\Apocalypse.Now.Redux.1979.BDRip.YIFY.mkv"); var metadata = new RavenJObject { {"User", "users/1345"}, {"Director","Francis Ford Coppola" }, {"Year","1979" } }; session.RegisterUpload("Mkv/sample.mkv", stream, metadata); await session.SaveChangesAsync();
حالا اگر به نسخه سرور ravenDb مراجعه کنید میبینید که فایل طبق فایل هدر داده شده قرار گرفته است و اطلاعات مربوط به آن ذخیره شده است:
{ "User": "users/1345", "Country": "Iran", "City": "Kashan", "Raven-Synchronization-History": [ { "Version": 4, "ServerId": "42d0cccb-103d-4bf0-9f3d-6f635b1c8ba4" }, { "Version": 5, "ServerId": "42d0cccb-103d-4bf0-9f3d-6f635b1c8ba4" } ], "Raven-Synchronization-Version": "6", "Raven-Synchronization-Source": "42d0cccb-103d-4bf0-9f3d-6f635b1c8ba4" }
برای خواندن هم به شیوه زیر عمل میکنیم:
از طریق Store ایجاد شده، یک سشن جدید را باز میکنیم و فایل مورد نظر را از طریق یکی از متادیتاهای تعریف شده بازیابی میکنیم:
var fileStore = new FilesStore() { Url = "http://localhost:8080", DefaultFileSystem = "SampleFs" }; fileStore.Initialize(); var session = fileStore.OpenAsyncSession(); var file = await session.Query() .WhereEquals("Year", "1979") .FirstOrDefaultAsync();
var stream = await session.DownloadAsync("mkv/"+file.Name);
سپس استریم را روی دیسک سخت دخیره یا به هر مکانی که مد نظر است ارسال میکنیم:
var fs = File.Create("D:\\file2.mkv"); stream.CopyTo(fs); fs.Flush(); fs.Close();
await stream.CopyToAsync(fs);
سپس کل کد بازیابی را به شکل زیر مینویسیم:
var fileStore = new FilesStore() { Url = "http://localhost:8080", DefaultFileSystem = "SampleFs" }; fileStore.Initialize(); var session = fileStore.OpenAsyncSession(); var file = await session.Query() .WhereEquals("Year", "1979") .FirstOrDefaultAsync(); var stream = await session.DownloadAsync("mkv/"+file.Name); var fs = File.Create("D:\\file2.mkv"); await stream.CopyToAsync(fs);
Type Safety
string text = “String value”; int textLength = text.Length; int textMonth = text.Month; // won’t compile
public interface IGeometricShape { double Circumference { get; } double Area { get; } } public class Square : IGeometricShape { public double Side { get; set; } public double Circumference => 4 * Side; public double Area => Side * Side; } public class Circle : IGeometricShape { public double Radius { get; set; } public double Circumference => 2 * Math.PI * Radius; public double Area => Math.PI * Radius * Radius; } IGeometricShape circle = new Circle { Radius = 1 }; Square square = ((Square)circle); // no compiler error var side = square.Side;
Dynamic Binding
dynamic text = “String value”; int textLength = text.Length; int textMonth = text.Month; // throws exception at runtime
public dynamic GetAnonymousType() { return new { Name = “John”, Surname = “Doe”, Age = 42 }; } dynamic value = GetAnonymousType(); Console.WriteLine($”{value.Name} {value.Surname}, {value.Age}”);
string json = @" { ""name"": ""John"", ""surname"": ""Doe"", ""age"": 42 }"; dynamic value = JObject.Parse(json); Console.WriteLine($"{ value.name} { value.surname}, { value.age}");
ExpandoObject
dynamic person = new ExpandoObject(); person.Name = "John"; person.Surname = "Doe"; person.Age = 42; person.ToString = (Func<string>)(() => $”{person.Name} {person.Surname}, {person. Age}”); Console.WriteLine($"{ person.Name}{ person.Surname}, { person.Age}");
برای اینکه ببینیم در زمان اجرا چه اعضایی به این شی اضافه شده، میتوان نمونه ساخته شده از آن را به نوع <IDictionary<string, object تبدیل و در یک حلقه به آنها دسترسی پیدا کرد. از همین طریق هم میشود عضوی را حذف کرد.
var dictionary = (IDictionary<string, object>)person; foreach (var member in dictionary) { Console.WriteLine($”{member.Key} = {member.Value}”); } dictionary.Remove(“ToString”);
DynamicObject
از آنجایی که ExpandoObject برای سناریوهای ساده کاربرد دارد و کنترل کمتری بر روی اعضا و نمونههای ایجاد شدهی توسط آن داریم، میتوان از شیء DynamicObject استفاده کرد؛ البته نیاز به کدنویسی بیشتری دارد. پیادهسازی اعضا برای شیء DynamicObject در یک کلاس صورت میگیرد که در زیر آورده شدهاست:
class MyDynamicObject : DynamicObject { private readonly Dictionary<string, object> members = new Dictionary<string, object>(); public override bool TryGetMember(GetMemberBinder binder, out object result) { if (members.ContainsKey(binder.Name)) { result = members[binder.Name]; return true; } else { result = null; return false; } } public override bool TrySetMember(SetMemberBinder binder, object value) { members[binder.Name] = value; return true; } public bool RemoveMember(string name) { return members.Remove(name); } } dynamic person = new MyDynamicObject(); person.Name = “John”; person.Surname = “Doe”; person.Age = 42; person.AsString = (Func<string>)(() => $”{person.Name} {person.Surname}, {person. Age}”);
MainTableEvents(events => { events.MainTableAdded(args => { var grid = new PdfGrid(1) { WidthPercentage = 90 }; grid.AddSimpleRow((d, p) => { p.RunDirection = PdfRunDirection.RightToLeft; p.HorizontalAlignment = HorizontalAlignment.Justified; p.ShowBorder = true; d.Value = form.Ideas; // I want to convert HTML to PDF p.PdfFont = args.PdfFont; p.CellPadding = 5f; });
ایجاد برنامههای ابتدایی مورد نیاز
در ابتدا دو پوشهی جدید BlazorServerApp و WinFormsApp را ایجاد میکنیم. سپس از طریق خط فرمان در اولی دستور dotnet new blazorserver و در دومی دستور dotnet new winforms را اجرا میکنیم تا دو برنامهی خالی Blazor Server و همچنین Windows Forms، ایجاد شوند. برنامهی WinForms ایجاد شده مبتنی بر NET Core. و یا همان NET 6x. است؛ بجای اینکه مبتنی بر دات نت فریمورک 4x باشد.
ایجاد یک پروژهی کتابخانهی Razor
چون میخواهیم کدهای برنامهی BlazorServerApp ما در برنامهی WinForms قابل استفاده باشد، نیاز است فایلهای اصلی آنرا به یک پروژهی razor class library منتقل کنیم. به همین جهت برای این پروژه، یک پوشهی جدید را به نام BlazorClassLibrary ایجاد کرده و درون آن دستور dotnet new razorclasslib را اجرا میکنیم.
انتقال فایلهای پروژهی Blazor به پروژهی کتابخانهی Razor
در ادامه این فایلها را از پروژهی BlazorServerApp به پروژهی BlazorClassLibrary منتقل میکنیم:
- کل پوشهی Data
- کل پوشهی Pages
- کل پوشهی Shared
- فایل App.razor
- فایل Imports.razor_
- کل پوشهی wwwroot
پس از اینکار، نیاز است فایل csproj کتابخانهی class lib را اندکی ویرایش کرد تا بتواند فایلهای اضافه شده را کامپایل کند:
<Project Sdk="Microsoft.NET.Sdk.Razor"> <PropertyGroup> <AddRazorSupportForMvc>true</AddRazorSupportForMvc> </PropertyGroup> <ItemGroup> <FrameworkReference Include="Microsoft.AspNetCore.App" /> </ItemGroup> </Project>
- به علاوه فایل Error.cshtml.cs انتقالی، نیاز به افزودن فضای نام using Microsoft.Extensions.Logging را خواهد داشت.
- در فایل Imports.razor_ انتقالی نیاز است دو using آخر آنرا که به BlazorServerApp قبلی اشاره میکنند، به BlazorClassLibrary جدید ویرایش کنیم:
@using BlazorClassLibrary @using BlazorClassLibrary.Shared
@namespace BlazorClassLibrary.Pages
<link rel="stylesheet" href="_content/BlazorClassLibrary/css/bootstrap/bootstrap.min.css" /> <link href="_content/BlazorClassLibrary/css/site.css" rel="stylesheet" />
پس از این تغییرات، برای اینکه برنامهی BlazorServerApp موجود، به کار خود ادامه دهد، نیاز است ارجاعی از پروژهی class lib را به فایل csproj آن اضافه کنیم:
<Project Sdk="Microsoft.NET.Sdk.Web"> <ItemGroup> <ProjectReference Include="..\BlazorClassLibrary\BlazorClassLibrary.csproj" /> </ItemGroup> </Project>
ویرایش برنامهی WinForms جهت اجرای کدهای Blazor
تا اینجا برنامهی Blazor Server ما تمام فایلهای مورد نیاز خود را از BlazorClassLibrary دریافت میکند و بدون مشکل اجرا میشود. در ادامه میخواهیم کار هاست این class lib را در برنامهی WinForms نیز انجام دهیم. به همین جهت در ابتدا ارجاعی را به class lib به آن اضافه میکنیم:
<Project Sdk="Microsoft.NET.Sdk"> <ItemGroup> <ProjectReference Include="..\BlazorClassLibrary\BlazorClassLibrary.csproj" /> </ItemGroup> </Project>
<Project Sdk="Microsoft.NET.Sdk"> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Components.WebView.WindowsForms" Version="6.0.101-preview.11.2349" /> </ItemGroup> </Project>
در ادامه نیاز است فایل Form1.Designer.cs را به صورت دستی جهت افزودن این WebView اضافه شده، تغییر داد:
namespace WinFormsApp; partial class Form1 { private void InitializeComponent() { this.blazorWebView1 = new Microsoft.AspNetCore.Components.WebView.WindowsForms.BlazorWebView(); this.SuspendLayout(); this.blazorWebView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.blazorWebView1.Location = new System.Drawing.Point(13, 181); this.blazorWebView1.Name = "blazorWebView1"; this.blazorWebView1.Size = new System.Drawing.Size(775, 257); this.blazorWebView1.TabIndex = 20; this.Controls.Add(this.blazorWebView1); this.components = new System.ComponentModel.Container(); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(800, 450); this.Text = "Form1"; this.ResumeLayout(false); } private Microsoft.AspNetCore.Components.WebView.WindowsForms.BlazorWebView blazorWebView1; }
هاست برنامهی Blazor در برنامهی WinForm
پس از تغییرات فوق، نیاز است فایلهای wwwroot را از پروژهی class lib به پروژهی WinForms کپی کرد. از این جهت که این فایلها از طریق index.html جدیدی خوانده خواهند شد. پس از کپی کردن این پوشه، نیاز است فایل csproj پروژهی WinForm را به صورت زیر اصلاح کرد:
<Project Sdk="Microsoft.NET.Sdk.Razor"> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Components.WebView.WindowsForms" Version="6.0.101-preview.11.2349" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\BlazorClassLibrary\BlazorClassLibrary.csproj" /> </ItemGroup> <ItemGroup> <Content Update="wwwroot\**"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> </ItemGroup> </Project>
در ادامه داخل این پوشهی wwwroot که از پروژهی class lib کپی کردیم، نیاز است فایل index.html جدیدی را که قرار است blazor.webview.js را اجرا کند، به صورت زیر ایجاد کنیم:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>Blazor WinForms app</title> <base href="/" /> <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" /> <link href="css/site.css" rel="stylesheet" /> <link href="css/app.css" rel="stylesheet" /> <link href="WinFormsApp.styles.css" rel="stylesheet" /> </head> <body> <div id="app"></div> <div id="blazor-error-ui"> An unhandled error has occurred. <a href="">Reload</a> <a>🗙</a> </div> <script src="_framework/blazor.webview.js"></script> </body> </html>
- همچنین در این فایل باید مداخل css.های مورد نیاز را هم مجددا ذکر کرد.
مرحلهی آخر کار، استفاده از کامپوننت webview جهت نمایش فایل index.html فوق است:
using System; using System.Windows.Forms; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebView.WindowsForms; using Microsoft.Extensions.DependencyInjection; using BlazorServerApp.Data; using BlazorClassLibrary; namespace WinFormsApp; public partial class Form1 : Form { private readonly AppState _appState = new(); public Form1() { var serviceCollection = new ServiceCollection(); serviceCollection.AddBlazorWebView(); serviceCollection.AddSingleton<AppState>(_appState); serviceCollection.AddSingleton<WeatherForecastService>(); InitializeComponent(); blazorWebView1.HostPage = @"wwwroot\index.html"; blazorWebView1.Services = serviceCollection.BuildServiceProvider(); blazorWebView1.RootComponents.Add<App>("#app"); //blazorWebView1.Dock = DockStyle.Fill; } }
نکتهی مهم! حتما نیاز است WebView2 Runtime را جداگانه دریافت و نصب کرد. در غیر اینصورت در حین اجرای برنامه، با خطای نامفهوم زیر مواجه خواهید شد:
System.IO.FileNotFoundException: The system cannot find the file specified. (0x80070002)
در اینجا یک ServiceCollection را ایجاد کرده و توسط آن سرویسهای مورد نیاز کامپوننت WebView را تامین میکنیم. همچنین مسیر فایل index.html نیز توسط آن مشخص شدهاست. این تنظیمات شبیه به فایل Program.cs برنامهی Blazor هستند.
تا اینجا اگر برنامه را اجرا کنیم، چنین خروجی قابل مشاهدهاست:
اکنون برنامهی کامل Blazor Server ما توسط یک WinForms هاست شدهاست و کاربر برای کار با آن، نیاز به نصب IIS یا هیچ وب سرور خاصی ندارد.
تعامل بین برنامهی WinForm و برنامهی Blazor
میخواهیم یک دکمه را بر روی WinForm قرار داده و با کلیک بر روی آن، مقدار شمارشگر حاصل در برنامهی Blazor را نمایش دهیم؛ مانند تصویر فوق.
برای اینکار در کدهای فوق، ثبت سرویس جدید AppState را هم مشاهده میکنید:
serviceCollection.AddSingleton<AppState>(_appState);
namespace BlazorServerApp.Data; public class AppState { public int Counter { get; set; } }
builder.Services.AddSingleton<AppState>();
@inject BlazorServerApp.Data.AppState AppState // ... @code { private void IncrementCount() { // ... AppState.Counter++; } }
private void button1_Click(object sender, EventArgs e) { MessageBox.Show( owner: this, text: $"Current counter value is: {_appState.Counter}", caption: "Counter"); }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorDesktopHybrid.zip
بروز رسانی موجودیتهای منفصل توسط WCF
سناریویی را در نظر بگیرید که در آن عملیات CRUD توسط WCF پیاده سازی شده اند و دسترسی دادهها با مدل Code-First انجام میشود. فرض کنید مدل اپلیکیشن مانند تصویر زیر است.
همانطور که میبینید مدل ما متشکل از پستها و نظرات کاربران است. برای ساده نگاه داشتن مثال جاری، اکثر فیلدها حذف شده اند. مثلا متن پست ها، نویسنده، تاریخ و زمان انتشار و غیره. میخواهیم تمام کد دسترسی دادهها را در یک سرویس WCF پیاده سازی کنیم تا کلاینتها بتوانند عملیات CRUD را توسط آن انجام دهند. برای ساختن این سرویس مراحل زیر را دنبال کنید.
- در ویژوال استودیو پروژه جدیدی از نوع Class Library بسازید و نام آن را به Recipe2 تغییر دهید.
- با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
- سه کلاس با نامهای Post, Comment و Recipe2Context به پروژه اضافه کنید. کلاسهای Post و Comment موجودیتهای مدل ما هستند که به جداول متناظرشان نگاشت میشوند. کلاس Recipe2Context آبجکت DbContext ما خواهد بود که بعنوان درگاه عملیاتی EF عمل میکند. دقت کنید که خاصیتهای لازم WCF یعنی DataContract و DataMember در کلاسهای موجودیتها بدرستی استفاده میشوند. لیست زیر کد این کلاسها را نشان میدهد.
[DataContract(IsReference = true)] public class Post { public Post() { comments = new HashSet<Comments>(); } [DataMember] public int PostId { get; set; } [DataMember] public string Title { get; set; } [DataMember] public virtual ICollection<Comment> Comments { get; set; } } [DataContract(IsReference=true)] public class Comment { [DataMember] public int CommentId { get; set; } [DataMember] public int PostId { get; set; } [DataMember] public string CommentText { get; set; } [DataMember] public virtual Post Post { get; set; } } public class EFRecipesEntities : DbContext { public EFRecipesEntities() : base("name=EFRecipesEntities") {} public DbSet<Post> posts; public DbSet<Comment> comments; }
- یک فایل App.config به پروژه اضافه کنید و رشته اتصال زیر را به آن اضافه نمایید.
<connectionStrings> <add name="Recipe2ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
- حال یک پروژه WCF به Solution جاری اضافه کنید. برای ساده نگاه داشتن مثال جاری، نام پیش فرض Service1 را بپذیرید. فایل IService1.cs را باز کنید و کد زیر را با محتوای آن جایگزین نمایید.
[ServiceContract] public interface IService1 { [OperationContract] void Cleanup(); [OperationContract] Post GetPostByTitle(string title); [OperationContract] Post SubmitPost(Post post); [OperationContract] Comment SubmitComment(Comment comment); [OperationContract] void DeleteComment(Comment comment); }
- فایل Service1.svc.cs را باز کنید و کد زیر را با محتوای آن جایگزین نمایید. بیاد داشته باشید که پروژه Recipe2 را ارجاع کنید و فضای نام آن را وارد نمایید. همچنین کتابخانه EF 6 را باید به پروژه اضافه کنید.
public class Service1 : IService { public void Cleanup() { using (var context = new EFRecipesEntities()) { context.Database.ExecuteSqlCommand("delete from [comments]"); context. Database.ExecuteSqlCommand ("delete from [posts]"); } } public Post GetPostByTitle(string title) { using (var context = new EFRecipesEntities()) { context.Configuration.ProxyCreationEnabled = false; var post = context.Posts.Include(p => p.Comments).Single(p => p.Title == title); return post; } } public Post SubmitPost(Post post) { context.Entry(post).State = // if Id equal to 0, must be insert; otherwise, it's an update post.PostId == 0 ? EntityState.Added : EntityState.Modified; context.SaveChanges(); return post; } public Comment SubmitComment(Comment comment) { using (var context = new EFRecipesEntities()) { context.Comments.Attach(comment); if (comment.CommentId == 0) { // this is an insert context.Entry(comment).State = EntityState.Added); } else { // set single property to modified, which sets state of entity to modified, but // only updates the single property – not the entire entity context.entry(comment).Property(x => x.CommentText).IsModified = true; } context.SaveChanges(); return comment; } } public void DeleteComment(Comment comment) { using (var context = new EFRecipesEntities()) { context.Entry(comment).State = EntityState.Deleted; context.SaveChanges(); } } }
- در آخر پروژه جدیدی از نوع Windows Console Application به Solution جاری اضافه کنید. از این اپلیکیشن بعنوان کلاینتی برای تست سرویس WCF استفاده خواهیم کرد. فایل program.cs را باز کنید و کد زیر را با محتوای آن جایگزین نمایید. روی نام پروژه کلیک راست کرده و گزینه Add Service Reference را انتخاب کنید، سپس ارجاعی به سرویس Service1 اضافه کنید. رفرنسی هم به کتابخانه کلاسها که در ابتدای مراحل ساختید باید اضافه کنید.
class Program { static void Main(string[] args) { using (var client = new ServiceReference2.Service1Client()) { // cleanup previous data client.Cleanup(); // insert a post var post = new Post { Title = "POCO Proxies" }; post = client.SubmitPost(post); // update the post post.Title = "Change Tracking Proxies"; client.SubmitPost(post); // add a comment var comment1 = new Comment { CommentText = "Virtual Properties are cool!", PostId = post.PostId }; var comment2 = new Comment { CommentText = "I use ICollection<T> all the time", PostId = post.PostId }; comment1 = client.SubmitComment(comment1); comment2 = client.SubmitComment(comment2); // update a comment comment1.CommentText = "How do I use ICollection<T>?"; client.SubmitComment(comment1); // delete comment 1 client.DeleteComment(comment1); // get posts with comments var p = client.GetPostByTitle("Change Tracking Proxies"); Console.WriteLine("Comments for post: {0}", p.Title); foreach (var comment in p.Comments) { Console.WriteLine("\tComment: {0}", comment.CommentText); } } } }
Comment: I use ICollection<T> all the time
شرح مثال جاری
ابتدا با اپلیکیشن کنسول شروع میکنیم، که کلاینت سرویس ما است. نخست در یک بلاک {} using وهله ای از کلاینت سرویس مان ایجاد میکنیم. درست همانطور که وهله ای از یک EF Context میسازیم. استفاده از بلوکهای using توصیه میشود چرا که متد Dispose بصورت خودکار فراخوانی خواهد شد، چه بصورت عادی چه هنگام بروز خطا. پس از آنکه وهله ای از کلاینت سرویس را در اختیار داشتیم، متد Cleanup را صدا میزنیم. با فراخوانی این متد تمام دادههای تست پیشین را حذف میکنیم. در چند خط بعدی، متد SubmitPost را روی سرویس فراخوانی میکنیم. در پیاده سازی فعلی شناسه پست را بررسی میکنیم. اگر مقدار شناسه صفر باشد، خاصیت State موجودیت را به Added تغییر میدهید تا رکورد جدیدی ثبت کنیم. در غیر اینصورت فرض بر این است که چنین موجودیتی وجود دارد و قصد ویرایش آن را داریم، بنابراین خاصیت State را به Modified تغییر میدهیم. از آنجا که مقدار متغیرهای int بصورت پیش فرض صفر است، با این روش میتوانیم وضعیت پستها را مشخص کنیم. یعنی تعیین کنیم رکورد جدیدی باید ثبت شود یا رکوردی موجود بروز رسانی گردد. رویکردی بهتر آن است که پارامتری اضافی به متد پاس دهیم، یا متدی مجزا برای ثبت رکوردهای جدید تعریف کنیم. مثلا رکوردی با نام InsertPost. در هر حال، بهترین روش بستگی به ساختار اپلیکیشن شما دارد.
اگر پست جدیدی ثبت شود، خاصیت PostId با مقدار مناسب جدید بروز رسانی میشود و وهله پست را باز میگردانیم. ایجاد و بروز رسانی نظرات کاربران مشابه ایجاد و بروز رسانی پستها است، اما با یک تفاوت اساسی: بعنوان یک قانون، هنگام بروز رسانی نظرات کاربران تنها فیلد متن نظر باید بروز رسانی شود. بنابراین با فیلدهای دیگری مانند تاریخ انتشار و غیره اصلا کاری نخواهیم داشت. بدین منظور تنها خاصیت CommentText را بعنوان Modified علامت گذاری میکنیم. این امر منجر میشود که Entity Framework عبارتی برای بروز رسانی تولید کند که تنها این فیلد را در بر میگیرد. توجه داشته باشید که این روش تنها در صورتی کار میکند که بخواهید یک فیلد واحد را بروز رسانی کنید. اگر میخواستیم فیلدهای بیشتری را در موجودیت Comment بروز رسانی کنیم، باید مکانیزمی برای ردیابی تغییرات در سمت کلاینت در نظر میگرفتیم. در مواقعی که خاصیتهای متعددی میتوانند تغییر کنند، معمولا بهتر است کل موجودیت بروز رسانی شود تا اینکه مکانیزمی پیچیده برای ردیابی تغییرات در سمت کلاینت پیاده گردد. بروز رسانی کل موجودیت بهینهتر خواهد بود.
برای حذف یک دیدگاه، متد Entry را روی آبجکت DbContext فراخوانی میکنیم و موجودیت مورد نظر را بعنوان آرگومان پاس میدهیم. این امر سبب میشود که موجودیت مورد نظر بعنوان Deleted علامت گذاری شود، که هنگام فراخوانی متد SaveChanges اسکریپت لازم برای حذف رکورد را تولید خواهد کرد.
در آخر متد GetPostByTitle یک پست را بر اساس عنوان پیدا کرده و تمام نظرات کاربران مربوط به آن را هم بارگذاری میکند. از آنجا که ما کلاسهای POCO را پیاده سازی کرده ایم، Entity Framework آبجکتی را بر میگرداند که Dynamic Proxy نامیده میشود. این آبجکت پست و نظرات مربوط به آن را در بر خواهد گرفت. متاسفانه WCF نمیتواند آبجکتهای پروکسی را مرتب سازی (serialize) کند. اما با غیرفعال کردن قابلیت ایجاد پروکسیها (ProxyCreationEnabled=false) ما به Entity Framework میگوییم که خود آبجکتهای اصلی را بازگرداند. اگر سعی کنید آبجکت پروکسی را سریال کنید با پیغام خطای زیر مواجه خواهید شد:
The underlying connection was closed: The connection was closed unexpectedly
می توانیم غیرفعال کردن تولید پروکسی را به متد سازنده کلاس سرویس منتقل کنیم تا روی تمام متدهای سرویس اعمال شود.
در این قسمت دیدیم چگونه میتوانیم از آبجکتهای POCO برای مدیریت عملیات CRUD توسط WCF استفاده کنیم. از آنجا که هیچ اطلاعاتی درباره وضعیت موجودیتها روی کلاینت ذخیره نمیشود، متدهایی مجزا برای عملیات CRUD ساختیم. در قسمتهای بعدی خواهیم دید چگونه میتوان تعداد متدهایی که سرویس مان باید پیاده سازی کند را کاهش داد و چگونه ارتباطات بین کلاینت و سرور را سادهتر کنیم.
معرفی الگوی Repository
روش متداول کار با فناوریهای مختلف دسترسی به دادهها عموما بدین شکل است:
الف) یافتن رشته اتصالی رمزنگاری شده به دیتابیس از یک فایل کانفیگ (در یک برنامه اصولی البته!)
ب) باز کردن یک اتصال به دیتابیس
ج) ایجاد اشیاء Command برای انجام عملیات مورد نظر
د) اجرا و فراخوانی اشیاء مراحل قبل
ه) بستن اتصال به دیتابیس و آزاد سازی اشیاء
اگر در برنامههای یک تازه کار به هر محلی از برنامه او دقت کنید این 5 مرحله را میتوانید مشاهده کنید. همه جا! قسمت ثبت، قسمت جستجو، قسمت نمایش و ...
مشکلات این روش:
1- حجم کارهای تکراری انجام شده بالا است. اگر قسمتی از فناوری دسترسی به دادهها را به اشتباه درک کرده باشد، پس از مطالعه بیشتر و مشخص شدن نحوهی رفع مشکل، قسمت عمدهای از برنامه را باید اصلاح کند (زیرا کدهای تکراری همه جای آن پراکندهاند).
2- برنامه نویس هر بار باید این مراحل را به درستی انجام دهد. اگر در یک برنامه بزرگ تنها قسمت آخر در یکی از مراحل کاری فراموش شود دیر یا زود برنامه تحت فشار کاری بالا از کار خواهد افتاد (و متاسفانه این مساله بسیار شایع است).
3- برنامه منحصرا برای یک نوع دیتابیس خاص تهیه خواهد شد و تغییر این رویه جهت استفاده از دیتابیسی دیگر (مثلا کوچ برنامه از اکسس به اس کیوال سرور)، نیازمند بازنویسی کل برنامه میباشد.
و ...
همین برنامه نویس پس از مدتی کار به این نتیجه میرسد که باید برای اینکارهای متداول، یک لایه و کلاس دسترسی به دادهها را تشکیل دهد. اکنون هر قسمتی از برنامه برای کار با دیتابیس باید با این کلاس مرکزی که انجام کارهای متداول با دیتابیس را خلاصه میکند، کار کند. به این صورت کد نویسی یک نواختی با حذف کدهای تکراری از سطح برنامه و همچنین بدون فراموش شدن قسمت مهمی از مراحل کاری، حاصل میگردد. در اینجا اگر روزی قرار شد از یک دیتابیس دیگر استفاده شود فقط کافی است یک کلاس برنامه تغییر کند و نیازی به بازنویسی کل برنامه نخواهد بود.
این روزها تشکیل این لایه دسترسی به دادهها (data access layer یا DAL) نیز مزموم است! و دلایل آن در مباحث چرا به یک ORM نیازمندیم برشمرده شده است. جهت کار با ORM ها نیز نیازمند یک لایه دیگر میباشیم تا یک سری اعمال متداول با آنهارا کپسوله کرده و از حجم کارهای تکراری خود بکاهیم. برای این منظور قبل از اینکه دست به اختراع بزنیم، بهتر است به الگوهای طراحی برنامه نویسی شیء گرا رجوع کرد و از رهنمودهای آن استفاده نمود.
الگوی Repository یکی از الگوهای برنامه نویسی با مقیاس سازمانی است. با کمک این الگو لایهای بر روی لایه نگاشت اشیاء برنامه به دیتابیس تشکیل شده و عملا برنامه را مستقل از نوع ORM مورد استفاه میکند. به این صورت هم از تشکیل یک سری کدهای تکراری در سطح برنامه جلوگیری شده و هم از وابستگی بین مدل برنامه و لایه دسترسی به دادهها (که در اینجا همان NHibernate میباشد) جلوگیری میشود. الگوی Repository (مخزن)، کار ثبت، حذف، جستجو و به روز رسانی دادهها را با ترجمه آنها به روشهای بومی مورد استفاده توسط ORM مورد نظر، کپسوله میکند. به این شکل شما میتوانید یک الگوی مخزن عمومی را برای کارهای خود تهیه کرده و به سادگی از یک ORM به ORM دیگر کوچ کنید؛ زیرا کدهای برنامه شما به هیچ ORM خاصی گره نخورده و این عملیات بومی کار با ORM توسط لایهای که توسط الگوی مخزن تشکیل شده، صورت گرفته است.
طراحی کلاس مخزن باید شرایط زیر را برآورده سازد:
الف) باید یک طراحی عمومی داشته باشد و بتواند در پروژههای متعددی مورد استفاده مجدد قرار گیرد.
ب) باید با سیستمی از نوع اول طراحی و کد نویسی و بعد کار با دیتابیس، سازگاری داشته باشد.
ج) باید امکان انجام آزمایشات واحد را سهولت بخشد.
د) باید وابستگی کلاسهای دومین برنامه را به زیر ساخت ORM مورد استفاده قطع کند (اگر سال بعد به این نتیجه رسیدید که ORM ایی به نام XYZ برای کار شما بهتر است، فقط پیاده سازی این کلاس باید تغییر کند و نه کل برنامه).
ه) باید استفاده از کوئریهایی از نوع strongly typed را ترویج کند (مثل کوئریهایی از نوع LINQ).
بررسی مدل برنامه
مدل این قسمت (برنامه NHSample4 از نوع کنسول با همان ارجاعات متداول ذکر شده در قسمتهای قبل)، از نوع many-to-many میباشد. در اینجا یک واحد درسی توسط چندین دانشجو میتواند اخذ شود یا یک دانشجو میتواند چندین واحد درسی را اخذ نماید که برای نمونه کلاس دیاگرام و کلاسهای متشکل آن به شکل زیر خواهند بود:
using System.Collections.Generic;
namespace NHSample4.Domain
{
public class Course
{
public virtual int Id { get; set; }
public virtual string Teacher { get; set; }
public virtual IList<Student> Students { get; set; }
public Course()
{
Students = new List<Student>();
}
}
}
using System.Collections.Generic;
namespace NHSample4.Domain
{
public class Student
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Course> Courses { get; set; }
public Student()
{
Courses = new List<Course>();
}
}
}
کلاس کانفیگ برنامه جهت ایجاد نگاشتها و سپس ساخت دیتابیس متناظر
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Tool.hbm2ddl;
namespace NHSessionManager
{
public class Config
{
public static FluentConfiguration GetConfig()
{
return
Fluently.Configure()
.Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(x => x.FromConnectionStringWithKey("DbConnectionString"))
)
.Mappings(
m => m.AutoMappings.Add(
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample4.Domain.Course).Assembly))
.ExportTo(System.Environment.CurrentDirectory)
);
}
public static void CreateDb()
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool dropTables = false;//آیا جداول موجود دراپ شوند
new SchemaExport(GetConfig().BuildConfiguration()).Execute(script, export, dropTables);
}
}
}
الف) با توجه به اینکه برنامه از نوع ویندوزی است، برای مدیریت صحیح کانکشن استرینگ، فایل App.Config را به برنامه افروده و محتویات آنرا به شکل زیر تنظیم میکنیم (تا کلید DbConnectionString توسط متد GetConfig مورد استفاده قرارگیرد ):
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true"/>
</connectionStrings>
</configuration>
ب) در NHibernate سنتی (!) کار ساخت نگاشتها توسط یک سری فایل xml صورت میگیرد که با معرفی فریم ورک Fluent NHibernate و استفاده از قابلیتهای Auto Mapping آن، اینکار با سهولت و دقت هر چه تمامتر قابل انجام است که توضیحات نحوهی انجام آنرا در قسمتهای قبل مطالعه فرمودید. اگر نیاز بود تا این فایلهای XML نیز جهت بررسی شخصی ایجاد شوند، تنها کافی است از متد ExportTo آن همانگونه که در متد GetConfig استفاده شده، کمک گرفته شود. به این صورت پس از ایجاد خودکار نگاشتها، فایلهای XML متناظر نیز در مسیری که به عنوان آرگومان متد ExportTo مشخص گردیده است، تولید خواهند شد (دو فایل NHSample4.Domain.Course.hbm.xml و NHSample4.Domain.Student.hbm.xml را در پوشهای که محل اجرای برنامه است خواهید یافت).
با فراخوانی متد CreateDb این کلاس، پس از ساخت خودکار نگاشتها، database schema متناظر، در دیتابیسی که توسط کانکشن استرینگ برنامه مشخص شده، ایجاد خواهد شد که دیتابیس دیاگرام آنرا در شکل ذیل مشاهده مینمائید (جداول دانشجویان و واحدها هر کدام به صورت موجودیتی مستقل ایجاد شده که ارجاعات آنها در جدولی سوم نگهداری میشود).
پیاده سازی الگوی مخزن
اینترفیس عمومی الگوی مخزن به شکل زیر میتواند باشد:
using System;
using System.Linq;
using System.Linq.Expressions;
namespace NHSample4.NHRepository
{
//Repository Interface
public interface IRepository<T>
{
T Get(object key);
T Save(T entity);
T Update(T entity);
void Delete(T entity);
IQueryable<T> Find();
IQueryable<T> Find(Expression<Func<T, bool>> predicate);
}
}
سپس پیاده سازی آن با توجه به کلاس SingletonCore ایی که در قسمت قبل تهیه کردیم (جهت مدیریت صحیح سشن فکتوری)، به صورت زیر خواهد بود.
این کلاس کار آغاز و پایان تراکنشها را نیز مدیریت کرده و جهت سهولت کار اینترفیس IDisposable را نیز پیاده سازی میکند :
using System;
using System.Linq;
using NHSessionManager;
using NHibernate;
using NHibernate.Linq;
namespace NHSample4.NHRepository
{
public class Repository<T> : IRepository<T>, IDisposable
{
private ISession _session;
private bool _disposed = false;
public Repository()
{
_session = SingletonCore.SessionFactory.OpenSession();
BeginTransaction();
}
~Repository()
{
Dispose(false);
}
public T Get(object key)
{
if (!isSessionSafe) return default(T);
return _session.Get<T>(key);
}
public T Save(T entity)
{
if (!isSessionSafe) return default(T);
_session.Save(entity);
return entity;
}
public T Update(T entity)
{
if (!isSessionSafe) return default(T);
_session.Update(entity);
return entity;
}
public void Delete(T entity)
{
if (!isSessionSafe) return;
_session.Delete(entity);
}
public IQueryable<T> Find()
{
if (!isSessionSafe) return null;
return _session.Linq<T>();
}
public IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
if (!isSessionSafe) return null;
return Find().Where(predicate);
}
void Commit()
{
if (!isSessionSafe) return;
if (_session.Transaction != null &&
_session.Transaction.IsActive &&
!_session.Transaction.WasCommitted &&
!_session.Transaction.WasRolledBack)
{
_session.Transaction.Commit();
}
else
{
_session.Flush();
}
}
void Rollback()
{
if (!isSessionSafe) return;
if (_session.Transaction != null && _session.Transaction.IsActive)
{
_session.Transaction.Rollback();
}
}
private bool isSessionSafe
{
get
{
return _session != null && _session.IsOpen;
}
}
void BeginTransaction()
{
if (!isSessionSafe) return;
_session.BeginTransaction();
}
public void Dispose()
{
Dispose(true);
// tell the GC that the Finalize process no longer needs to be run for this object.
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposeManagedResources)
{
if (_disposed) return;
if (!disposeManagedResources) return;
if (!isSessionSafe) return;
try
{
Commit();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
Rollback();
}
finally
{
if (isSessionSafe)
{
_session.Close();
_session.Dispose();
}
}
_disposed = true;
}
}
}
using System;
using System.Collections.Generic;
using NHSample4.Domain;
using NHSample4.NHRepository;
namespace NHSample4
{
class Program
{
static void Main(string[] args)
{
//ایجاد دیتابیس در صورت نیاز
//NHSessionManager.Config.CreateDb();
//ابتدا یک دانشجو را اضافه میکنیم
Student student = null;
using (var studentRepo = new Repository<Student>())
{
student = studentRepo.Save(new Student() { Name = "Vahid" });
}
//سپس یک واحد را اضافه میکنیم
using (var courseRepo = new Repository<Course>())
{
var course = courseRepo.Save(new Course() { Teacher = "Shams" });
}
//اکنون یک واحد را به دانشجو انتساب میدهیم
using (var courseRepo = new Repository<Course>())
{
courseRepo.Save(new Course() { Students = new List<Student>() { student } });
}
//سپس شماره دروس استادی خاص را نمایش میدهیم
using (var courseRepo = new Repository<Course>())
{
var query = courseRepo.Find(t => t.Teacher == "Shams");
foreach (var course in query)
Console.WriteLine(course.Id);
}
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
همانطور که ملاحظه میکنید در این سطح دیگر برنامه هیچ درکی از ORM مورد استفاده ندارد و پیاده سازی نحوهی تعامل با NHibernate در پس کلاس مخزن مخفی شده است. کار آغاز و پایان تراکنشها به صورت خودکار مدیریت گردیده و همچنین آزاد سازی منابع را نیز توسط اینترفیس IDisposable مدیریت میکند. به این صورت امکان فراموش شدن یک سری از اعمال متداول به حداقل رسیده، میزان کدهای تکراری برنامه کم شده و همچنین هر زمانیکه نیاز بود، صرفا با تغییر پیاده سازی کلاس مخزن میتوان به ORM دیگری کوچ کرد؛ بدون اینکه نیازی به بازنویسی کل برنامه وجود داشته باشد.
دریافت سورس برنامه قسمت هشتم
ادامه دارد ...
معرفی Lex.Db
نکته مهم آن سازگاری با برنامههای دات نت 4 به بعد، همچنین برنامههای ویندوز 8، سیلورلایت 5، ویندوز فون 8 و همچنین اندروید (از طریق Mono) است. به علاوه چون با دات نت تهیه شده است، دیگر نیازی نیست دو نگارش 32 بیتی و 64 بیتی آن توزیع شوند و به این ترتیب مشکلات توزیع بانکهای اطلاعاتی native مانند SQLite را ندارد ( و مطابق ادعای نویسنده آلمانی آن، از SQLite سریعتر است).
API این بانک اطلاعاتی، هر دو نوع متدهای synchronous و asynchronous را شامل میشود؛ به همین جهت با برنامههای ویندوز 8 و سیلورلایت نیز سازگاری دارد.
Lex.Db از برنامههای چندریسمانی و همچنین استفاده از یک بانک اطلاعاتی آن توسط چندین پروسه همزمان نیز پشتیبانی میکند.
در ادامه مروری خواهیم داشت بر نحوه استفاده از آن در حالت طراحی رابطهای؛ از این جهت که فعلا به ظاهر این بانک اطلاعاتی روابط را پشتیبانی نمیکند، اما در عمل پیاده سازی آن مشکل نیست.
دریافت Lex.Db
برای دریافت Lex.Db، دستور ذیل را در خط فرمان پاورشل نیوگت وارد نمائید:
PM> Install-Package Lex.Db
مدلهای برنامه
public class Product { public int Id { get; set; } public string Name { get; set; } } public class Customer { public int Id { get; set; } public string Name { get; set; } public string City { get; set; } } public class Order { public int Id { get; set; } public int? CustomerFK { get; set; } public int[] ProductsFK { get; set; } }
آغاز بانک اطلاعاتی
public static class Database { public static DbInstance Instance { get; private set; } public static DbTable<Product> Products { get; private set; } public static DbTable<Order> Orders { get; private set; } public static DbTable<Customer> Customers { get; private set; } /// <summary> /// سازنده استاتیکی که در طول عمر برنامه فقط یکبار اجرا میشود /// </summary> static Database() { createDb(); getTables(); } private static void getTables() { Products = Instance.Table<Product>(); Customers = Instance.Table<Customer>(); Orders = Instance.Table<Order>(); } private static void createDb() { Instance = new DbInstance(Path.Combine(Environment.CurrentDirectory, "LexDbTests")); Instance.Map<Product>() .WithIndex("NameIdx", x => x.Name) .Automap(i => i.Id, true); Instance.Map<Order>() .Automap(i => i.Id, true); Instance.Map<Customer>() .WithIndex("NameIdx", x => x.Name) .WithIndex("CityIdx", x => x.City) .Automap(i => i.Id, true); Instance.Initialize(); } }
سپس توسط وهلهای از بانک اطلاعاتی که ایجاد کردیم، کار معرفی خواص مدلهای برنامه توسط متد Map و Automap انجام میشود. متد Automap خاصیت primary key کلاس را دریافت کرده و همچنین پارامتر دوم آن مشخص میکند که آیا این کلید اصلی به صورت خودکار ایجاد شود یا خیر. به علاوه در همینجا میتوان روی فیلدهای مختلف، ایندکس نیز ایجاد کرد. متد WithIndex یک نام دلخواه را دریافت کرده و سپس خاصیتی را که باید بر روی آن ایندکس ایجاد شود، دریافت میکند.
در نهایت متد Initialize باید فراخوانی گردد. البته اگر برنامه شما WinRT است، این متد Initialize Async خواهد بود.
جداول نیز بر اساس مدلهای برنامه از طریق متد Instance.Table در دسترس قرار گرفتهاند.
افزودن اطلاعات به بانک اطلاعاتی
private static void addData() { var customer1 = new Customer { Name = "customer1", City = "City1" }; var customer2 = new Customer { Name = "customer2", City = "City2" }; Database.Instance.Save(customer1, customer2); // automatic Id assignment after Save var product1 = new Product { Name = "product1" }; var product2 = new Product { Name = "product2" }; Database.Instance.Save(product1, product2); // automatic Id assignment after Save var order1 = new Order { CustomerFK = customer1.Id, ProductsFK = new[] { product1.Id } }; var order2 = new Order { CustomerFK = customer2.Id, ProductsFK = new[] { product1.Id, product2.Id } }; Database.Instance.Save(order1, order2); // automatic Id assignment after Save }
در اینجا نیازی به ذکر Id نمونههای ساخته شده نیست؛ از این جهت که در حین عملیات Save، به صورت خودکار انتساب خواهند یافت.
همچنین نحوه مقدار دهی کلیدهای خارجی نیز با استفاده از همین کلیدهای اصلی آماده شده است.
واکشی تمام اطلاعات
private static void loadAll() { var orders = Database.Orders.LoadAll(); foreach (var order in orders) { // نحوه دریافت اطلاعات مشتری بر اساس کلید خارجی ثبت شده var orderCustomer = Database.Customers.LoadByKey(order.CustomerFK.Value); Console.WriteLine("Order Id: {0}, Customer: {1} ({2}) {3}", order.Id, orderCustomer.Name, orderCustomer.Id, orderCustomer.City); // نحوه بازیابی لیستی از اشیاء مرتبط از طریق آرایهای از کلیدهای خارجی ثبت شده var orderProducts = Database.Products.LoadByKeys(order.ProductsFK); foreach (var product in orderProducts) { Console.WriteLine(" Product Id: {0}, Name: {1}", product.Id, product.Name); } } }
سپس با استفاده از متدهای LoadByKey و LoadByKeys، به سادگی میتوان اشیاء مرتبط با هر سفارش را نیز واکشی کرد.
استفاده از ایندکسها برای کوئری گرفتن
private static void queryingByAnIndex() { var name = "customer1"; var customersList = Database.Customers .IndexQueryByKey("NameIdx", name) .ToList(); foreach (var person in customersList) { Console.WriteLine(person.Name); } }
// Using Take and Skip var list1 = Database.Orders.Query<int>() // primary idx .Take(1).Skip(2).ToList(); // Querying Between Ranges var list2 = Database.Customers .IndexQuery<string>("NameIdx") .GreaterThan("a", orEqual: true).LessThan("d").ToList();
حذف رکوردها
private static void deletingRecords() { Database.Customers.DeleteByKey(key: 1); var customers = Database.Customers.LoadByKeys(new[] { 1, 2 }); Database.Customers.Delete(customers); }
و اگر خواستید کل بانک اطلاعاتی را خالی کنید، متد Database.Instance.Purge اینکار را انجام خواهد داد.
کدهای کامل این مثال را از اینجا نیز میتوانید دریافت کنید:
Program-LexDb.cs