استفاده از EF در اپلیکیشن های N-Tier : قسمت اول
نویسنده: آرمین ضیاء
تاریخ: ۱۳۹۲/۱۱/۰۷ ۲:۳۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
سناریویی را فرض کنید که در آن برای انجام عملیات CRUD از یک سرویس Web API استفاده میشود. همچنین مدیریت دادهها با مدل Code-First پیاده سازی شده است. در مثال جاری یک کلاینت Console Application خواهیم داشت که یک سرویس Web API را فراخوانی میکند. توجه داشته باشید که هر اپلیکیشن در Solution مجزایی قرار دارد. تفکیک پروژهها برای شبیه سازی یک محیط n-Tier انجام شده است.
فرض کنید مدلی مانند تصویر زیر داریم.
همانطور که میبینید مدل جاری، سفارشات یک اپلیکیشن فرضی را معرفی میکند. میخواهیم مدل و کد دسترسی به دادهها را در یک سرویس Web API پیاده سازی کنیم، تا هر کلاینتی که از HTTP استفاده میکند بتواند عملیات CRUD را انجام دهد. برای ساختن سرویس مورد نظر مراحل زیر را دنبال کنید.
public class Order
{
public int OrderId { get; set; }
public string Product { get; set; }
public int Quantity { get; set; }
public string Status { get; set; }
public byte[] TimeStamp { get; set; }
} public class Recipe1Context : DbContext
{
public Recipe1Context() : base("Recipe1ConnectionString") { }
public DbSet<Order> Orders { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().ToTable("Orders");
// Following configuration enables timestamp to be concurrency token
modelBuilder.Entity<Order>().Property(x => x.TimeStamp)
.IsConcurrencyToken()
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
}
} <connectionStrings>
<add name="Recipe1ConnectionString"
connectionString="Data Source=.;
Initial Catalog=EFRecipes;
Integrated Security=True;
MultipleActiveResultSets=True"
providerName="System.Data.SqlClient" />
</connectionStrings> protected void Application_Start()
{
// Disable Entity Framework Model Compatibilty
Database.SetInitializer<Recipe1Context>(null);
...
} public class OrderController : ApiController
{
// GET api/order
public IEnumerable<Order> Get()
{
using (var context = new Recipe1Context())
{
return context.Orders.ToList();
}
}
// GET api/order/5
public Order Get(int id)
{
using (var context = new Recipe1Context())
{
return context.Orders.FirstOrDefault(x => x.OrderId == id);
}
}
// POST api/order
public HttpResponseMessage Post(Order order)
{
// Cleanup data from previous requests
Cleanup();
using (var context = new Recipe1Context())
{
context.Orders.Add(order);
context.SaveChanges();
// create HttpResponseMessage to wrap result, assigning Http Status code of 201,
// which informs client that resource created successfully
var response = Request.CreateResponse(HttpStatusCode.Created, order);
// add location of newly-created resource to response header
response.Headers.Location = new Uri(Url.Link("DefaultApi",
new { id = order.OrderId }));
return response;
}
}
// PUT api/order/5
public HttpResponseMessage Put(Order order)
{
using (var context = new Recipe1Context())
{
context.Entry(order).State = EntityState.Modified;
context.SaveChanges();
// return Http Status code of 200, informing client that resouce updated successfully
return Request.CreateResponse(HttpStatusCode.OK, order);
}
}
// DELETE api/order/5
public HttpResponseMessage Delete(int id)
{
using (var context = new Recipe1Context())
{
var order = context.Orders.FirstOrDefault(x => x.OrderId == id);
context.Orders.Remove(order);
context.SaveChanges();
// Return Http Status code of 200, informing client that resouce removed successfully
return Request.CreateResponse(HttpStatusCode.OK);
}
}
private void Cleanup()
{
using (var context = new Recipe1Context())
{
context.Database.ExecuteSqlCommand("delete from [orders]");
}
}
} نکته: قسمت هایی از اپلیکیشن که باید در لایههای مختلف مورد استفاده قرار گیرند - مانند کلاسهای موجودیتها - بهتر است در لایه مجزایی قرار داده شده و به اشتراک گذاشته شوند. مثلا میتوانید پروژه ای از نوع Class Library بسازید و تمام موجودیتها را در آن تعریف کنید. سپس لایههای مختلف این پروژه را ارجاع خواهند کرد.
فایل program.cs را باز کنید و کد زیر را به آن اضافه نمایید.
private HttpClient _client;
private Order _order;
private static void Main()
{
Task t = Run();
t.Wait();
Console.WriteLine("\nPress <enter> to continue...");
Console.ReadLine();
}
private static async Task Run()
{
// create instance of the program class
var program = new Program();
program.ServiceSetup();
program.CreateOrder();
// do not proceed until order is added
await program.PostOrderAsync();
program.ChangeOrder();
// do not proceed until order is changed
await program.PutOrderAsync();
// do not proceed until order is removed
await program.RemoveOrderAsync();
}
private void ServiceSetup()
{
// map URL for Web API cal
_client = new HttpClient { BaseAddress = new Uri("http://localhost:3237/") };
// add Accept Header to request Web API content
// negotiation to return resource in JSON format
_client.DefaultRequestHeaders.Accept.
Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
private void CreateOrder()
{
// Create new order
_order = new Order { Product = "Camping Tent", Quantity = 3, Status = "Received" };
}
private async Task PostOrderAsync()
{
// leverage Web API client side API to call service
var response = await _client.PostAsJsonAsync("api/order", _order);
Uri newOrderUri;
if (response.IsSuccessStatusCode)
{
// Capture Uri of new resource
newOrderUri = response.Headers.Location;
// capture newly-created order returned from service,
// which will now include the database-generated Id value
_order = await response.Content.ReadAsAsync<Order>();
Console.WriteLine("Successfully created order. Here is URL to new resource: {0}", newOrderUri);
}
else
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
private void ChangeOrder()
{
// update order
_order.Quantity = 10;
}
private async Task PutOrderAsync()
{
// construct call to generate HttpPut verb and dispatch
// to corresponding Put method in the Web API Service
var response = await _client.PutAsJsonAsync("api/order", _order);
if (response.IsSuccessStatusCode)
{
// capture updated order returned from service, which will include new quanity
_order = await response.Content.ReadAsAsync<Order>();
Console.WriteLine("Successfully updated order: {0}", response.StatusCode);
}
else
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
private async Task RemoveOrderAsync()
{
// remove order
var uri = "api/order/" + _order.OrderId;
var response = await _client.DeleteAsync(uri);
if (response.IsSuccessStatusCode)
Console.WriteLine("Sucessfully deleted order: {0}", response.StatusCode);
else
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
} با اجرای اپلیکیشن Web API شروع کنید. این اپلیکیشن یک کنترلر Web API دارد که پس از اجرا شما را به صفحه خانه هدایت میکند. در این مرحله اپلیکیشن در حال اجرا است و سرویسهای ما قابل دسترسی هستند.
حال اپلیکیشن کنسول را باز کنید. روی خط اول کد program.cs یک breakpoint تعریف کرده و اپلیکیشن را اجرا کنید. ابتدا آدرس سرویس Web API را پیکربندی کرده و خاصیت Accept Header را مقدار دهی میکنیم. با این کار از سرویس مورد نظر درخواست میکنیم که دادهها را با فرمت JSON بازگرداند. سپس یک آبجکت Order میسازیم و با فراخوانی متد PostAsJsonAsync آن را به سرویس ارسال میکنیم. این متد روی آبجکت HttpClient تعریف شده است. اگر به اکشن متد Post در کنترلر Order یک breakpoint اضافه کنید، خواهید دید که این متد سفارش جدید را بعنوان یک پارامتر دریافت میکند و آن را به لیست موجودیتها در Context جاری اضافه مینماید. این عمل باعث میشود که آبجکت جدید بعنوان Added علامت گذاری شود، در این مرحله Context جاری شروع به ردیابی تغییرات میکند. در آخر با فراخوانی متد SaveChanges دادهها را ذخیره میکنیم. در قدم بعدی کد وضعیت 201 (Created) و آدرس منبع جدید را در یک آبجکت HttpResponseMessage قرار میدهیم و به کلاینت ارسال میکنیم. هنگام استفاده از Web API باید اطمینان حاصل کنیم که کلاینتها درخواستهای ایجاد رکورد جدید را بصورت POST ارسال میکنند. درخواستهای HTTP Post بصورت خودکار به اکشن متد متناظر نگاشت میشوند.
در مرحله بعد عملیات بعدی را اجرا میکنیم، تعداد سفارش را تغییر میدهیم و موجودیت جاری را با فراخوانی متد PutAsJsonAsync به سرویس Web API ارسال میکنیم. اگر به اکشن متد Put در کنترلر سرویس یک breakpoint اضافه کنید، خواهید دید که آبجکت سفارش بصورت یک پارامتر دریافت میشود. سپس با فراخوانی متد Entry و پاس دادن موجودیت جاری بعنوان رفرنس، خاصیت State را به Modified تغییر میدهیم، که این کار موجودیت را به Context جاری میچسباند. حال فراخوانی متد SaveChanges یک اسکریپت بروز رسانی تولید خواهد کرد. در مثال جاری تمام فیلدهای آبجکت Order را بروز رسانی میکنیم. در شمارههای بعدی این سری از مقالات، خواهیم دید چگونه میتوان تنها فیلدهایی را بروز رسانی کرد که تغییر کرده اند. در آخر عملیات را با بازگرداندن کد وضعیت 200 (OK) به اتمام میرسانیم.
در مرحله بعد، عملیات نهایی را اجرا میکنیم که موجودیت Order را از منبع داده حذف میکند. برای اینکار شناسه (Id) رکورد مورد نظر را به آدرس سرویس اضافه میکنیم و متد DeleteAsync را فراخوانی میکنیم. در سرویس Web API رکورد مورد نظر را از دیتابیس دریافت کرده و متد Remove را روی Context جاری فراخوانی میکنیم. این کار موجودیت مورد نظر را بعنوان Deleted علامت گذاری میکند. فراخوانی متد SaveChanges یک اسکریپت Delete تولید خواهد کرد که نهایتا منجر به حذف شدن رکورد میشود.
در یک اپلیکیشن واقعی بهتر است کد دسترسی دادهها از سرویس Web API تفکیک شود و در لایه مجزایی قرار گیرد.