اشتراکها
برای اعمال OwnsOne وقتی کلاسهای زیر را داشته باشیم چگونه باید عمل کرد؟
حالا اگر برای ownsOne طبق زیر عمل کنم:
در هنگام حذف Product آن را حذف نمیکند و ارور زیر را میدهد:
البته از EFCore2.2 استفاده میکنم.
namespace Loans.Models { public class Product { public Product() { Rating = new Rating(); } public Rating Rating { get; set; } public int Id { get; set; } public string Name { get; set; } public double Price { get; set; } public double OfferPrice { get; set; } public Group Group { get; set; } public int GroupId { get; set; } public List<Image> Images { get; set; } } public class Rating { public Rating() { } public Rating(double totalRating, int totalRaters, double averageRating) { TotalRating = totalRating; TotalRaters = totalRaters; AverageRating = averageRating; } public double TotalRating { get; set; } = 0.0; public int TotalRaters { get; set; } = 0; public double AverageRating { get; set; } = 0.0; } public class Group { public int Id { get; set; } public string Name { get; set; } public Group ParentGroup { get; set; } public int? ParentGroupId { get; set; } public List<Group> ChildrenGroups { get; set; } public List<Product> Products { get; set; } public Image Image { get; set; } } public class Image { public Guid Id { get; set; } public string Name { get; set; } public Group Group { get; set; } public int? GroupId { get; set; } public Product Product { get; set; } public int? ProductId { get; set; } } }
modelBuilder.Entity<Product>().OwnsOne(p => p.Rating)
"The entity of type 'Product' is sharing the table 'Products' with entities of type 'Rating ', but there is no entity of this type with the same key value ."
۱- متد IsActionAuthorized نام کامل متدی که قرار است اجرا شود را به عنوان پارامتر گرفته و در دیتابیس (در این پیاده سازی به وسیلهی EntityFramework) چک میکند که کاربری که Id اش در AuthManager. AuditUserId است (یعنی کاربری که درخواست اجرای متد را داده است) اجازه اجرای این متد را دارد یا نه. بسته به نیازمندی برنامه شما این دسترسی میتواند به طور ساده فقط مستقیما برای کاربر ثبت شود و یا ترکیبی از دسترسی خود کاربر و دسترسی گروه هایی که این کاربر در آن عضویت دارد باشد.
۲- EFAuthorizationManager کلاس ساده ایست
namespace Framework.ServiceLayer.UserManager { public class EFAuthorizationManager : IAuthorizationManager { public String AuditUserId { get; set; } IUnitOfWork _uow; public EFAuthorizationManager(IUnitOfWork uow) { _uow = uow; } public bool IsActionAuthorized(string actionName) { var res = _uow.Set<User>() .Any(u => u.Id == AuditUserId && u.AllowedActions.Any(a => a.Name == actionName)); return res; } public bool IsPageAuthorized(string pageURL) { //TODO: بررسی وجود دسترسی باید پیاده سازی شود //فقط برای تست return true; } } }
:خلاصه ای از کلاسهای مدل مرتبط را هم در زیر مشاهده میکنید
namespace Framework.DataModel { public class User : BaseEntity { public string UserName { get; set; } public string Password { get; set; } //... [Display(Name = "عملیات مجاز")] public virtual ICollection<Action> AllowedActions { get; set; } } public class Action:BaseEntity { public string Name { get; set; } public Entity RelatedEntity { get; set; } //... public virtual ICollection<User> AllowedUsers { get; set; } } public abstract class BaseEntity { [Key] public int Id { get; set; } //... } }
بازخوردهای دوره
جلوگیری از deadlock در برنامههای async
در حالت فوق اولین چیزی که به ذهنم رسید رندر کردن خروجی با استفاده از ChildAction بود یعنی به این صورت:
ویو:
اما به محض اجرای برنامه استثنای زیر صادر شد:
در نهایت بعد از کمی جستجو متوجه شدم که ChildActionها از async پشتیبانی نمیکنند(+)
[ChildActionOnly] public async Task<ActionResult> GetLastActivity() { var query = await _userManager.FindByIdAsync(User.Identity.GetUserId<int>()); var result = query.LastActivity; return PartialView("GetLastActivity", result); }
@model DateTime @Model
{"HttpServerUtility.Execute blocked while waiting for an asynchronous operation to complete."}
در هر صورت ممنون، از روشی که پیشنهاد دادید استفاده خواهم کرد.
بازخوردهای دوره
جلوگیری از deadlock در برنامههای async
با تشکر از شما، مطلب خیلی جالبی بود.
سپس در داخل ویو به راحتی در دسترس است. مورد فوق خروجی مورد نظر را ارائه میدهد اما با توجه به نکاتی که بیان کردید استفاده از خاصیت Result سبب بروز deadlock خواهد شد. از چه روشی برای این حالت بهتر است استفاده شود؟
یک سوال فرض کنید در یک برنامه وب میخواهیم در داخل ویو تاریخ آخرین مراجعه کاربر به سایت را نمایش دهیم، برای این کار یک متد الحاقی نوشتهام که توسط User.Identity.GetLastActivity در دسترس باشد:
public static DateTime GetLastActivity(this System.Security.Principal.IIdentity user) { var service = SmObjectFactory.Container.GetInstance<IApplicationUserManager>(); return service.FindByIdAsync(int.Parse(user.GetUserId())).Result.LastActivity; }
دلیل آن مرتبط است به روشی که از آن استفاده کردید. این قابلیت برای اینکه کار کند، نیاز به بافر کردن اطلاعات دارد، در حالیکه شما در حال دانلود یک فایل از یک سایت دیگر هستید. ترکیب اینها با هم، برای ارائهی resume کار نمیکنند. زمانیکه قرار است قابلیت resume وجود داشته باشد، یعنی مثلا کاربر درخواست دریافت اطلاعات را از بایت 1000 تا 1500، میدهد. File Stream Result چطور باید این درخواست را برای httpClient.GetStreamAsync که چنین قابلیتی را ندارد، ترجمه کند؟ اگر میخواهید آنرا برای حالت resume آزمایش کنید، از استریمی از نوع System.IO.File.OpenRead و یا new FileStream استفاده کنید:
public IActionResult FileStreamActionResult() { var fileStream = System.IO.File.OpenRead(@"D:\path\Controllers\HomeController.cs"); return new FileStreamResult(fileStream, "text/plain") { FileDownloadName = "HomeController.cs" }; }
نظرات مطالب
Delegate در سی شارپ
هدف، واگذاری مسئولیت در زمان اجرا است (delegation = واگذاری مسئولیت) به متدی که پیشتر پیاده سازی شدهاست. متدی که خارج از میدان دید شیء جاری است و پیاده سازی آن به استفاده کننده واگذار میشود؛ مانند همان مواردی که لینک داده شدند یا خلاصهی مطلب جاری، «روشی دیگر جهت معکوس سازی وابستگیها». البته این روزها بیشتر Func و Action بجای Delegates خام نگارشهای ابتدایی #C استفاده میشوند و کمتر کد جدیدی را میتوانید پیدا کنید که دیگر در آن تعریف public delegate وجود خارجی داشته باشد (این نوع کدها مربوط به دوران C# 2.0 هستند). مباحث async، تاریخچه، تکامل و نکات آنها را هم در اینجا پیگیری کنید.
یک نکتهی تکمیلی: اهمیت دقت داشتن به امضای متد FromSql در EF Core 2.0
در EF Core 2.0، اگر از String Interpolation معرفی شدهی در C# 6.0 استفاده شود، متد FromSql این نوع متغیرها را به صورت خودکار تبدیل به پارامترهای کوئریهای SQL میکند. برای مثال کوئری ذیل که در آن userName از طریق String Interpolation معرفی شدهاست:
یک چنین خروجی SQL ایی را تولید میکند:
همانطور که ملاحظه میکنید پارامتر p0@ به صورت خودکار بجای {userName} درج شدهاست.
اما ... اگر این کوئری را به نحو ذیل اجرا کنیم:
برنامه کرش میکند:
علت اینجا است که زمانیکه رشتهی Interpolation را مستقیما داخل متد FromSql درج میکنیم از overload زیر استفاده میکند:
در اینجا sql از نوع FormattableString معرفی شدهاست.
اما زمانیکه از var استفاده میکنیم، یعنی این رشته به نوع string تبدیل میشود. در این حالت دیگر از overload فوق استفاده نشده و عملیات تهیهی کوئریهای پارامتری انجام نخواهد شد. علت کرش برنامه هم همین مورد است. اگر در این حالت بخواهیم از userName استفاده کنیم، باید آنرا داخل '' محصور کنیم:
و در نهایت کوئری تولید شده نیز پارامتری نیست:
به عبارتی این نوع کوئری، مستعد به حملات تزریق اس کیوال است. چون بجای user 1 هر نوع ورودی دیگری را نیز میتوان درج کرد و به علت پارامتری نبودن، این نوع ورودیها میتوانند ساختار عبارت SQL نوشته شده را تغییر دهند.
بنابراین در حین کار با متد FromSql به overload در حال استفاده دقت داشته باشید. فقط حالت FormattableString آن است که کار تبدیل String Interpolation را به کوئریهای پارامتری انجام میدهد.
در EF Core 2.0، اگر از String Interpolation معرفی شدهی در C# 6.0 استفاده شود، متد FromSql این نوع متغیرها را به صورت خودکار تبدیل به پارامترهای کوئریهای SQL میکند. برای مثال کوئری ذیل که در آن userName از طریق String Interpolation معرفی شدهاست:
var userName = "user 1"; var user = context.Users.FromSql($"select top 1 * from Users where name = {userName} ").FirstOrDefault(); if (user != null) { Console.WriteLine(user.Name); }
SELECT TOP(1) [u].[UserId], [u].[IsAdmin], [u].[Name] FROM ( select top 1 * from Users where name = @p0 ) AS [u]
اما ... اگر این کوئری را به نحو ذیل اجرا کنیم:
var sql = $"select top 1 * from Users where name = {userName} "; user = context.Users.FromSql(sql).FirstOrDefault(); if (user != null) { Console.WriteLine(user.Name); }
.SqlException: Incorrect syntax near '1'
public static IQueryable<TEntity> FromSql<TEntity>(this IQueryable<TEntity> source, FormattableString sql) where TEntity : class;
اما زمانیکه از var استفاده میکنیم، یعنی این رشته به نوع string تبدیل میشود. در این حالت دیگر از overload فوق استفاده نشده و عملیات تهیهی کوئریهای پارامتری انجام نخواهد شد. علت کرش برنامه هم همین مورد است. اگر در این حالت بخواهیم از userName استفاده کنیم، باید آنرا داخل '' محصور کنیم:
var sql = $"select top 1 * from Users where name = '{userName}' ";
SELECT TOP(1) [u].[UserId], [u].[IsAdmin], [u].[Name] FROM ( select top 1 * from Users where name = 'user 1' ) AS [u]
بنابراین در حین کار با متد FromSql به overload در حال استفاده دقت داشته باشید. فقط حالت FormattableString آن است که کار تبدیل String Interpolation را به کوئریهای پارامتری انجام میدهد.
در قسمت قبل از Func و Actionها برای ساده سازی طراحیهای مبتنی بر اینترفیسهایی با یک متد استفاده کردیم. این مورد خصوصا در حالتهایی که قصد داریم به کاربر اجازهی فرمول نویسی بر روی اطلاعات موجود را بدهیم، بسیار مفید است.
مثال دوم) به استفاده کننده از API کتابخانه خود، اجازه فرمول نویسی بدهید
برای نمونه مثال ساده زیر را درنظر بگیرید که در آن قرار است یک سری عدد که از منبع دادهای دریافت شدهاند، بر روی صفحه نمایش داده شوند:
قصد داریم به برنامه نویس استفاده کننده از کتابخانه گزارشسازی خود، این اجازه را بدهیم که پیش از نمایش نهایی اطلاعات، بتواند توسط فرمولی که مشخص میکند، فرمت اعداد نمایش داده شده را تعیین کند.
روال کار اکثر ابزارهای گزارشسازی موجود، ارائه یک زبان اسکریپتی جدید برای حل این نوع مسایل است. اما با استفاده از Func و ... روشهای Code first (بجای روشهای Wizard first)، خیلی از این رنج و دردها را میتوان سادهتر و بدون نیاز به اختراع و یا آموزش زبان جدیدی حل کرد:
اینبار با استفاده از Func، امکان فرمول نویسی را به کاربر استفاده کننده از API ساده گزارش ساز فرضی خود دادهایم. Func تعریف شده در اینجا یک عدد int را در اختیار استفاده کننده قرار میدهد. در این بین، برنامه نویس میتواند هر نوع تغییر یا هر نوع فرمولی را که مایل است بر روی این عدد به کمک دستور زبان جاری مورد استفاده، اعمال کند و در آخر تنها باید نتیجه این عملیات را به صورت یک string بازگشت دهد. برای مثال:
البته سطر فوق ساده شده فراخوانی زیر است:
به این ترتیب اعداد نهایی با جدا کننده سه رقمی نمایش داده خواهند شد.
از این نوع طراحی، در ابزارها و کتابخانههای جدید گزارش سازی مخصوص ASP.NET MVC زیاد مشاهده میشوند.
مثال سوم) حذف کدهای تکراری برنامه
فرض کنید قصد دارید در برنامه وب خود مباحث caching را پیاده سازی کنید:
در هر قسمتی از برنامه که قصد داشته باشیم اطلاعاتی را در کش ذخیره کنیم، الگوی تکراری زیر باید طی شود:
ابتدا باید وضعیت کش جاری بررسی شود؛ اگر اطلاعاتی در آن موجود نبود، ابتدا از منبع دادهای مورد نظر خوانده شده و سپس در کش درج شود.
میتوان در این الگوی تکراری، خواندن اطلاعات را از منبع داده، به یک Func واگذار کرد و به این صورت کدهای ما به نحو زیر بازسازی خواهند شد:
و استفاده از آن نیز به نحو زیر خواهد بود:
پارامتر سوم متد CacheRead به صورت خودکار تنها زمانیکه اطلاعات کش متناظری با کلید Key1 وجود نداشته باشند، اجرا شده و نتیجه در کش ثبت میگردد. در اینجا دیگر از if و else و کدهای تکراری بررسی وضعیت کش خبری نیست.
مثال دوم) به استفاده کننده از API کتابخانه خود، اجازه فرمول نویسی بدهید
برای نمونه مثال ساده زیر را درنظر بگیرید که در آن قرار است یک سری عدد که از منبع دادهای دریافت شدهاند، بر روی صفحه نمایش داده شوند:
public static void PrintNumbers() { var numbers = new[] { 1,2,3,5,7,90 }; // from a data source foreach(var item in numbers) { Console.WriteLine(item); } }
روال کار اکثر ابزارهای گزارشسازی موجود، ارائه یک زبان اسکریپتی جدید برای حل این نوع مسایل است. اما با استفاده از Func و ... روشهای Code first (بجای روشهای Wizard first)، خیلی از این رنج و دردها را میتوان سادهتر و بدون نیاز به اختراع و یا آموزش زبان جدیدی حل کرد:
public static void PrintNumbers(Func<int,string> formula) { var numbers = new[] { 1,2,3,5,7,90 }; // from a data source foreach(var item in numbers) { var data = formula(item); Console.WriteLine(data); } }
PrintNumbers(number => string.Format("{0:n0}",number));
PrintNumbers((number) =>{ return string.Format("{0:n0}",number); });
از این نوع طراحی، در ابزارها و کتابخانههای جدید گزارش سازی مخصوص ASP.NET MVC زیاد مشاهده میشوند.
مثال سوم) حذف کدهای تکراری برنامه
فرض کنید قصد دارید در برنامه وب خود مباحث caching را پیاده سازی کنید:
using System; using System.Web; using System.Web.Caching; using System.Collections.Generic; namespace WebToolkit { public static class CacheManager { public static void CacheInsert(this HttpContextBase httpContext, string key, object data, int durationMinutes) { if (data == null) return; httpContext.Cache.Add( key, data, null, DateTime.Now.AddMinutes(durationMinutes), TimeSpan.Zero, CacheItemPriority.AboveNormal, null); } } }
var item = httpContext.Cache[key]; if (item == null) { item = ReadDataFromDataSource(); if (item == null) return null; CacheInsert(httpContext, key, item, durationMinutes); }
میتوان در این الگوی تکراری، خواندن اطلاعات را از منبع داده، به یک Func واگذار کرد و به این صورت کدهای ما به نحو زیر بازسازی خواهند شد:
using System; using System.Web; using System.Web.Caching; using System.Collections.Generic; namespace WebToolkit { public static class CacheManager { public static void CacheInsert(this HttpContextBase httpContext, string key, object data, int durationMinutes) { if (data == null) return; httpContext.Cache.Add( key, data, null, DateTime.Now.AddMinutes(durationMinutes), TimeSpan.Zero, CacheItemPriority.AboveNormal, null); } public static T CacheRead<T>(this HttpContextBase httpContext, string key, int durationMinutes, Func<T> ifNullRetrievalMethod) { var item = httpContext.Cache[key]; if (item == null) { item = ifNullRetrievalMethod(); if (item == null) return default(T); CacheInsert(httpContext, key, item, durationMinutes); } return (T)item; } } }
var user = HttpContext.CacheRead( "Key1", 15, () => _usersService.FindUser(userId));