ASP.NET MVC #4
معماری N-Tier چالشهای بخصوصی را برای قابلیتهای change-tracking در EF اضافه میکند. در ابتدا دادهها توسط یک آبجکت EF Context بارگذاری میشوند اما این آبجکت پس از ارسال دادهها به کلاینت از بین میرود. تغییراتی که در سمت کلاینت روی دادهها اعمال میشوند ردیابی (track) نخواهند شد. هنگام بروز رسانی، آبجکت Context جدیدی برای پردازش اطلاعات ارسالی باید ایجاد شود. مسلما آبجکت جدید هیچ چیز درباره Context پیشین یا مقادیر اصلی موجودیتها نمیداند.
در نسخههای قبلی Entity Framework توسعه دهندگان با استفاده از قالب ویژه ای بنام Self-Tracking Entities میتوانستند تغییرات موجودیتها را ردیابی کنند. این قابلیت در نسخه EF 6 از رده خارج شده است و گرچه هنوز توسط ObjectContext پشتیبانی میشود، آبجکت DbContext از آن پشتیبانی نمیکند.
در این سری از مقالات روی عملیات پایه CRUD تمرکز میکنیم که در اکثر اپلیکیشنهای n-Tier استفاده میشوند. همچنین خواهیم دید چگونه میتوان تغییرات موجودیتها را ردیابی کرد. مباحثی مانند همزمانی (concurrency) و مرتب سازی (serialization) نیز بررسی خواهند شد. در قسمت یک این سری مقالات، به بروز رسانی موجودیتهای منفصل (disconnected) توسط سرویسهای Web API نگاهی خواهیم داشت.
بروز رسانی موجودیتهای منفصل با Web API
سناریویی را فرض کنید که در آن برای انجام عملیات CRUD از یک سرویس Web API استفاده میشود. همچنین مدیریت دادهها با مدل Code-First پیاده سازی شده است. در مثال جاری یک کلاینت Console Application خواهیم داشت که یک سرویس Web API را فراخوانی میکند. توجه داشته باشید که هر اپلیکیشن در Solution مجزایی قرار دارد. تفکیک پروژهها برای شبیه سازی یک محیط n-Tier انجام شده است.
فرض کنید مدلی مانند تصویر زیر داریم.
همانطور که میبینید مدل جاری، سفارشات یک اپلیکیشن فرضی را معرفی میکند. میخواهیم مدل و کد دسترسی به دادهها را در یک سرویس Web API پیاده سازی کنیم، تا هر کلاینتی که از HTTP استفاده میکند بتواند عملیات CRUD را انجام دهد. برای ساختن سرویس مورد نظر مراحل زیر را دنبال کنید.
- در ویژوال استودیو پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب پروژه را Web API انتخاب کنید. نام پروژه را به Recipe1.Service تغییر دهید.
- کنترلر جدیدی از نوع WebApi Controller با نام OrderController به پروژه اضافه کنید.
- کلاس جدیدی با نام Order در پوشه مدلها ایجاد کنید و کد زیر را به آن اضافه نمایید.
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; } }
- با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
- حال کلاسی با نام Recipe1Context ایجاد کنید و کد زیر را به آن اضافه نمایید.
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); } }
- فایل Web.config پروژه را باز کنید و رشته اتصال زیر را به قسمت ConnectionStrings اضافه نمایید.
<connectionStrings> <add name="Recipe1ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
- فایل Global.asax را باز کنید و کد زیر را به آن اضافه نمایید. این کد بررسی Entity Framework Compatibility را غیرفعال میکند.
protected void Application_Start() { // Disable Entity Framework Model Compatibilty Database.SetInitializer<Recipe1Context>(null); ... }
- در آخر کد کنترلر Order را با لیست زیر جایگزین کنید.
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]"); } } }
در قدم بعدی اپلیکیشن کلاینت را میسازیم که از سرویس Web API استفاده میکند.
- در ویژوال استودیو پروژه جدیدی از نوع Console Application بسازید و نام آن را به Recipe1.Client تغییر دهید.
- کلاس موجودیت Order را به پروژه اضافه کنید. همان کلاسی که در سرویس Web API ساختیم.
نکته: قسمت هایی از اپلیکیشن که باید در لایههای مختلف مورد استفاده قرار گیرند - مانند کلاسهای موجودیتها - بهتر است در لایه مجزایی قرار داده شده و به اشتراک گذاشته شوند. مثلا میتوانید پروژه ای از نوع 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); }
Successfully updated order: OK
Sucessfully deleted order: OK
شرح مثال جاری
با اجرای اپلیکیشن 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 تفکیک شود و در لایه مجزایی قرار گیرد.
موجودیتهای زیر را در نظر بگیرید:
public class Customer { public Customer() { Orders = new ObservableCollection<Order>(); } public Guid Id { get; set; } public string Name { get; set; } public string Family { get; set; } public string FullName { get { return Name + " " + Family; } } public virtual IList<Order> Orders { get; set; } }
public class Product { public Product() { } public Guid Id { get; set; } public string Name { get; set; } public int Price { get; set; } } public class OrderDetail { public Guid Id { get; set; } public Guid ProductId { get; set; } public int Count { get; set; } public Guid OrderId { get; set; } public int Price { get; set; } public virtual Order Order { get; set; } public virtual Product Product { get; set; } public string ProductName { get { return Product != null ? Product.Name : string.Empty; } } }
public class Order { public Order() { OrderDetail = new ObservableCollection<OrderDetail>(); } public Guid Id { get; set; } public DateTime Date { get; set; } public Guid CustomerId { get; set; } public virtual Customer Customer { get; set; } public virtual IList<OrderDetail> OrderDetail { get; set; } public string CustomerFullName { get { return Customer == null ? string.Empty : Customer.FullName; } } public int TotalPrice { get { if (OrderDetail == null) return 0; return OrderDetail.Where(orderdetail => orderdetail.Product != null) .Sum(orderdetail => orderdetail.Price*orderdetail.Count); } } }
و نگاشت موجودیت ها:
public class CustomerConfiguration : EntityTypeConfiguration<Customer> { public CustomerConfiguration() { HasKey(c => c.Id); Property(c => c.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); } } public class ProductConfiguration : EntityTypeConfiguration<Product> { public ProductConfiguration() { HasKey(p => p.Id); Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); } } public class OrderDetailConfiguration : EntityTypeConfiguration<OrderDetail> { public OrderDetailConfiguration() { HasKey(od => od.Id); Property(od => od.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); } } public class OrderConfiguration: EntityTypeConfiguration<Order> { public OrderConfiguration() { HasKey(o => o.Id); Property(o => o.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); } }
و برای معرفی موجودیتها به Entity Framwork کلاس StoreDbContext را به صورت زیر تعریف میکنیم:
public class StoreDbContext : DbContext { public StoreDbContext() : base("name=StoreDb") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new CustomerConfiguration()); modelBuilder.Configurations.Add(new OrderConfiguration()); modelBuilder.Configurations.Add(new OrderDetailConfiguration()); modelBuilder.Configurations.Add(new ProductConfiguration()); } public DbSet<Customer> Customers { get; set; } public DbSet<Product> Products { get; set; } public DbSet<Order> Orders { get; set; } public DbSet<OrderDetail> OrderDetails { get; set; } }
جهت مقدار دهی اولیه به database تستی یک DataBaseInitializer به صورت زیر تعریف میکنیم:
public class MyTestDb : DropCreateDatabaseAlways<StoreDbContext> { protected override void Seed(StoreDbContext context) { var customer1 = new Customer { Name = "Vahid", Family = "Nasiri" }; var customer2 = new Customer { Name = "Mohsen", Family = "Jamshidi" }; var customer3 = new Customer { Name = "Mohsen", Family = "Akbari" }; var product1 = new Product {Name = "CPU", Price = 350000}; var product2 = new Product {Name = "Monitor", Price = 500000}; var product3 = new Product {Name = "Keyboard", Price = 30000}; var product4 = new Product {Name = "Mouse", Price = 20000}; var product5 = new Product {Name = "Power", Price = 70000}; var product6 = new Product {Name = "Hard", Price = 250000}; var order1 = new Order { Customer = customer1, Date = new DateTime(2013, 1, 1), OrderDetail = new List<OrderDetail> { new OrderDetail {Product = product1, Count = 1, Price = product1.Price}, new OrderDetail {Product = product2, Count = 1, Price = product2.Price}, new OrderDetail {Product = product3, Count = 1, Price = product3.Price}, } }; var order2 = new Order { Customer = customer1, Date = new DateTime(2013, 1, 5), OrderDetail = new List<OrderDetail> { new OrderDetail {Product = product1, Count = 2, Price = product1.Price}, new OrderDetail {Product = product3, Count = 4, Price = product3.Price}, } }; var order3 = new Order { Customer = customer1, Date = new DateTime(2013, 1, 9), OrderDetail = new List<OrderDetail> { new OrderDetail {Product = product1, Count = 4, Price = product1.Price}, new OrderDetail {Product = product3, Count = 5, Price = product3.Price}, new OrderDetail {Product = product5, Count = 6, Price = product5.Price}, } }; var order4 = new Order { Customer = customer2, Date = new DateTime(2013, 1, 9), OrderDetail = new List<OrderDetail> { new OrderDetail {Product = product4, Count = 1, Price = product4.Price}, new OrderDetail {Product = product3, Count = 1, Price = product3.Price}, new OrderDetail {Product = product6, Count = 1, Price = product6.Price}, } }; var order5 = new Order { Customer = customer2, Date = new DateTime(2013, 1, 12), OrderDetail = new List<OrderDetail> { new OrderDetail {Product = product4, Count = 1, Price = product4.Price}, new OrderDetail {Product = product5, Count = 2, Price = product5.Price}, new OrderDetail {Product = product6, Count = 5, Price = product6.Price}, } }; context.Customers.Add(customer3); context.Orders.Add(order1); context.Orders.Add(order2); context.Orders.Add(order3); context.Orders.Add(order4); context.Orders.Add(order5); context.SaveChanges(); }
و در ابتدای برنامه کد زیر را جهت مقداردهی اولیه به Database مان قرار میدهیم:
Database.SetInitializer(new MyTestDb());
در انتها ConnectionString را در App.Config به صورت زیر تعریف میکنیم:
<connectionStrings> <add name="StoreDb" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=StoreDBTest;Integrated Security = true" providerName="System.Data.SqlClient"/> </connectionStrings>
بسیار خوب، حالا همه چیز محیاست برای اجرای اولین پرس و جو:
using (var context = new StoreDbContext()) { var query = context.Customers; foreach (var customer in query) { Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family); } }
پرس و جوی تعریف شده لیست تمام Customerها را باز میگرداند. query فقط یک "عبارت" پرس و جو هست و زمانی اجرا میشود که از آن درخواست نتیجه شود. در مثال بالا این درخواست در اجرای حلقه foreach اتفاق میافتد و درست در این لحظه است که دستور SQL ساخته شده و به Database فرستاده میشود. EF در این حالت تمام دادهها را در یک لحظه باز نمیگرداند بلکه این ارتباط فعال است تا حلقه به پایان برسد و تمام دادهها از database واکشی شود. خروجی به صورت زیر خواهد بود:
Customer Name: Vahid, Customer Family: Nasiri Customer Name: Mohsen, Customer Family: Jamshidi Customer Name: Mohsen, Customer Family: Akbari
نکته: با هر بار درخواست نتیجه از query ، پرس و جوی مربوطه دوباره به database فرستاده میشود که ممکن است مطلوب ما نباشد و باعث افت سرعت شود. برای جلوگیری از تکرار این عمل کافیست با استفاده از متد ToList پرس و جو را در لحظه تعریف به اجرا در آوریم
var customers = context.Customers.ToList();
خط بالا دیگر یک عبارت پرس و جو نخواهد بود بلکه لیست تمام Customer هاست که به یکباره از database بازگشت داده شده است. در ادامه هرجا که از customers استفاده کنیم دیگر پرس و جویی به database فرستاده نخواهد شد.
پرس و جوی زیر مشتریهایی که نام آنها Mohsen هست را باز میگرداند:
private static void Query3() { using (var context = new StoreDbContext()) { var methodSyntaxquery = context.Customers .Where(c => c.Name == "Mohsen"); var sqlSyntaxquery = from c in context.Customers where c.Name == "Mohsen" select c; foreach (var customer in methodSyntaxquery) { Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family); } } // Output: // Customer Name: Mohsen, Customer Family: Jamshidi // Customer Name: Mohsen, Customer Family: Akbari }
همانطور که مشاهده میکنید پرس و جو به دو روش Method Syntax و Sql Syntax نوشته شده است.
روش Method Syntax روشی است که از متدهای الحاقی (Extention Method) و عبارتهای لامبدا (Lambda Expersion) برای نوشتن پرس و جو استفاده میشود. اما #C روش Sql Syntax را که همانند دستورات SQL هست، نیز فراهم کرده است تا کسانیکه آشنایی با این روش دارند، از این روش استفاده کنند. در نهایت این روش به Method Syntax تبدیل خواهد شد بنابراین پیشنهاد میشود که از همین روش استفاده شود تا با دست و پنجه نرم کردن با این روش، از مزایای آن در بخشهای دیگر کدنویسی استفاده شود.
اگر به نوع Customers که در DbContext تعریف شده است، دقت کرده باشید، خواهید دید که DbSet میباشد. DbSet کلاس و اینترفیسهای متفاوتی را پیاده سازی کرده است که در ادامه با آنها آشنا خواهیم شد:
- IQueryable<TEntity>, IEnumerable<TEntity>, IQueryable, IEnumerable: که امکان استفاده از متدهای نام آشنای LINQ را برای ما فراهم میکند. البته فراموش نشود که EF از Provider ای با نام LINQ To Entity برای تفسیر پرس و جوی ما و ساخت دستور SQL متناظر آن استفاده میکند. بنابراین تمامی متدهایی که در LINQ To Object استفاده میشوند در اینجا قابل استفاده نیستند. بطور مثال اگر در پرس و جو از LastOrDefault روی Customer استفاده شود در زمان اجرا با خطای زیر مواجه خواهیم شد و در نتیجه در استفاده از این متدها به این مسئله باید دقت شود.
- <IDbSet<TEntity: که دارای متدهای Add, Attach, Create, Find, Remove, Local میباشد و برای بحث ما Find و Local جهت ساخت پرس و جو استفاده میشوند که در ادامه توضیح داده خواهند شد.
- <DbQuery<TEntity: که دارای متدهای AsNoTracking و Include میباشد و در ادامه توضیح داده خواهند شد.
- دادههای موجود در حافظه را بررسی میکند یعنی آنهایی که Load و یا Attach شده اند.
- داده هایی که به DbContext اضافه (Add) ولی هنوز در database درج نشده اند.
- داده هایی که در database هستند ولی هنوز Load نشده اند.
private static void Query4() { using (var context = new StoreDbContext()) { var customer = context.Customers.Find(new Guid("2ee2fd32-e0e9-4955-bace-1995839d4367")); if (customer == null) Console.WriteLine("Customer not found"); else Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family); } }
private static void Query5() { using (var context = new StoreDbContext()) { try { var customer1 = context.Customers.Single(c => c.Name == "Unkown"); // Exception: Sequence contains no elements } catch (Exception ex) { Console.WriteLine(ex.Message); } try { var customer2 = context.Customers.Single(c => c.Name == "Mohsen"); // Exception: Sequence contains more than one element } catch (Exception ex) { Console.WriteLine(ex.Message); } var customer3 = context.Customers.SingleOrDefault(c => c.Name == "Unkown"); // customer3 == null var customer4 = context.Customers.Single(c => c.Name == "Vahid"); // customer4 != null } }
private static void Query6() { using (var context = new StoreDbContext()) { try { var customer1 = context.Customers.First(c => c.Name == "Unkown"); // Exception: Sequence contains no elements } catch (Exception ex) { Console.WriteLine(ex.Message); } var customer2 = context.Customers.FirstOrDefault(c => c.Name == "Unknown"); // customer2 == null var customer3 = context.Customers.First(c => c.Name == "Mohsen"); } }
Fixed In This Release of Visual Studio 2019 version 16.5
- Modified the find "List View" to work with the VsColorOutput extension.
- Fixed a regression introduced with version 16.5 where use of default indexed properties with value-types caused an internal compiler error.
- Modified find in files to preserve user settings between Visual Studio 2019 sessions.
- Fixed an issue where Goto next/previous entry failed to work in the find results list when "preview selected files in find results" is turned off.
- Fixed an issue with find when doing a regex search a pattern that did not contain regex special characters.
- Fixed the button placement in find in files tool window when its in a docked position. Changed alignments for better experience in docked mode.
- Fixed an issue where the find list view did not navigate to results when enter was hit.
- Fixed an issue where Goto next/previous entry failed to work in the find results list when "preview selected files in find results" is turned off.
Security Advisory Notice
CVE-2020-0899 Microsoft Visual Studio Elevation of Privilege Vulnerability
CVE-2020-0900 Visual Studio Extension Installer Service Elevation of Privilege Vulnerability
CVE-2020-5260 Git for Visual Studio Credential Leak Vulnerability due to insufficient validation on URLs
- DateTime.Now = Cannot provide the value: host value not found
- MSVC codegen error vector reverse_iterator x64 C++17 debug build
- C2440 error building atlenc.h with /permissive- in VS 16.4 Preview 1
- Last Visual Studio update broke NTLM authentication on Android (seems like new version of Momo would be a reason)
- Visual Studio 16.3.1 fails at compiling template code (e.g. from Basler Pylon SDK)
- Using TypeScript 3.7 for IntelliSense
- XCode 11.3 is too new all of a sudden after update to 8.4
- You uploaded an APK or Android App Bundle with invalid or missing signing information for some of its files. You need to create a valid signed APK or Android App Bundle.
- Unable to Run UWP project
- JavaScript IntelliSense not working after update 16.4.1
- Xib : Xcode is to new(rendering problem when using custom components)
- Storyboard error: 'Xcode is too new'
- Stability improvements for debugging watchOS applications.
- Adds Xcode 11.3 SDK support.
- Fixed crash when fstack-protector-strong flag is enabled.
- Fixed some inconsistent behavior with Debugger.
Security Advisory Notice
CVE-2020-0602 ASP.NET Core Denial of Service Vulnerability
CVE-2020-0603 ASP.NET Core Remote Code Execution Vulnerability
CVE-2020-0605 .NET Core Remote Code Execution Vulnerability
CVE-2020-0606 .NET Core Remote Code Execution Vulnerability