روشی ساده برای استریم کردن پاسخ از نوع Json
معرفی کتابخانه FastMember برای کار با Reflection
public static class ServiceCollectionExtensions { public static IServiceCollection Remove<T>(this IServiceCollection services) { var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(T)); if (serviceDescriptor != null) services.Remove(serviceDescriptor); return services; } }
dotnet new webapi -o WebService
dotnet sln add WebService/WebService.csproj
dotnet add Web/Web.csproj reference DataLayer/DataLayer.csproj
cd YourFolder
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool update --global dotnet-ef dotnet ef migrations --startup-project WebService/ add Init -p DataLayer --context SqlServerContext dotnet ef --startup-project WebService/ database update Init -p DataLayer --context SqlServerContext
یکی از چالشهایی که در طراحی زیرساخت برای Domain هایی که تعداد زیادی عملیات CRUD را در back office سیستم خود دارند، داشتن مکانیزمی برای ذخیره سازی اطلاعات Master-Detail یا چه بسا Master-Detail-DetailOfDetail میباشد. در ادامه نحوه برخورد با چنین سناریوهایی را در EF Core و همچنین با استفاده از AutoMapper و FluentValidation بررسی خواهیم کرد.
موجودیتهای فرضی
public abstract class Entity : IHaveTrackingState { public long Id { get; set; } [NotMapped] public TrackingState TrackingState { get; set; } } public class Master : Entity { public string Title { get; set; } public ICollection<Detail> Details { get; set; } } public class Detail : Entity { public string Title { get; set; } public ICollection<DetailOfDetail> Details { get; set; } public Master Master { get; set; } public long MasterId { get; set; } } public class DetailOfDetail : Entity { public string Title { get; set; } public Detail Detail { get; set; } public long DetailId { get; set; } }
DbContext برنامه
public class ProjectDbContext : DbContext { public DbSet<Master> Masters { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseInMemoryDatabase("SharedDatabaseName"); } }
public interface IHaveTrackingState { TrackingState TrackingState { get; set; } //ICollection<string> ModifiedProperties { get; set; } } public enum TrackingState { Unchanged = 0, Added = 1, Modified = 2, Deleted = 3 }
با استفاده از پراپرتی TrackingState بالا، امکان مشخص کردن صریح State رکورد ارسالی توسط کلاینت مهیا میشود. قبلا نیز مطلبی در راستای STE یا همان Self-Tracking Entity تهیه شده است؛ و همچنین نظرات ارسالی این مطلب نیز میتواند مفید واقع شود.
DTOهای متناظر با موجودیتهای فرضی
public abstract class Model : IHaveTrackingState { public long Id { get; set; } public TrackingState TrackingState { get; set; } } public class MasterModel : Model { public string Title { get; set; } public ICollection<DetailModel> Details { get; set; } } public class DetailModel : Model { public string Title { get; set; } public ICollection<DetailOfDetailModel> Details { get; set; } } public class DetailOfDetailModel : Model { public string Title { get; set; } }
Mapper.Initialize(expression => { expression.CreateMap<MasterModel, Master>(MemberList.None).ReverseMap(); expression.CreateMap<DetailModel, Detail>(MemberList.None).ReverseMap(); expression.CreateMap<DetailOfDetailModel, DetailOfDetail>(MemberList.None).ReverseMap(); });
البته بهتر است این تنظیمات در درون Profileهای مرتبط با AutoMapper کپسوله شوند و در زمان مورد نیاز نیز برای انجام نگاشتها، واسط IMapper تزریق شده و استفاده شود.
تهیه داده ارسالی فرضی توسط کلاینت
var masterModel = new MasterModel { Title = "Master-Title", TrackingState = TrackingState.Added, Details = new List<DetailModel> { new DetailModel { Title = "Detail-Title", TrackingState = TrackingState.Added, Details = new List<DetailOfDetailModel> { new DetailOfDetailModel { Title = "DetailOfDetail-Title", TrackingState = TrackingState.Added, } } } } };
ذخیره سازی اطلاعات
در EF Core، متد جدید context.ChangeTracker.TrackGraph برای به روز رسانی وضعیت یک گراف از اشیاء مشابه به اطلاعات ارسالی ذکر شده در بالا، اضافه شده است. این مکانیزم مفهوم کاملا جدیدی در EF Core میباشد که امکان کنترل نهایی برروی اشیایی را که قرار است توسط Context ردیابی شوند، مهیا میکند. با پیمایش یک گراف، امکان اجرای عملیات مورد نظر شما را برروی تک تک اشیاء، مهیا میسازد.
using (var context = new ProjectDbContext()) { Console.WriteLine("################ Create Master and Details and DetailsOfDetail ##################"); Print(masterModel); var masterEntity = Mapper.Map<Master>(masterModel); context.ChangeTracker.TrackGraph( masterEntity, n => { var entity = (IHaveTrackingState) n.Entry.Entity; n.Entry.State = entity.TrackingState.ToEntityState(); }); context.SaveChanges(); }
در تکه کد بالا، پس از انجام عملیات نگاشت، توسط متد TrackGraph به صورت صریح، وضعیت موجودیتها مشخص شده است؛ این کار با تغییر State ارسالی توسط کلاینت به State قابل فهم توسط EF انجام شدهاست. برای این منظور دو متد الحاقی زیر را میتوان در نظر گرفت:
public static class TrackingStateExtensions { public static EntityState ToEntityState(this TrackingState trackingState) { switch (trackingState) { case TrackingState.Added: return EntityState.Added; case TrackingState.Modified: return EntityState.Modified; case TrackingState.Deleted: return EntityState.Deleted; case TrackingState.Unchanged: return EntityState.Unchanged; default: return EntityState.Unchanged; } } public static TrackingState ToTrackingState(this EntityState state) { switch (state) { case EntityState.Added: return TrackingState.Added; case EntityState.Modified: return TrackingState.Modified; case EntityState.Deleted: return TrackingState.Deleted; case EntityState.Unchanged: return TrackingState.Unchanged; default: return TrackingState.Unchanged; } } }
//GetForEditAsync var masterModel = context.Masters .ProjectTo<MasterModel>() .AsNoTracking().Single(a => a.Id == 1); //Client var detail1 = masterModel.Details.First(); detail1.Title = "Details-EditedTitle"; detail1.TrackingState = TrackingState.Modified; foreach (var detail in detail1.Details) { detail.TrackingState = TrackingState.Deleted; //detail.Title = "DetailOfDetails-EditedTitle"; }
متدی تحت عنوان GetForEditAsync که یک MasterModel را بازگشت میدهد، در نظر بگیرید؛ کلاینت از طریق API، این Object Graph را دریافت میکند و تغییرات خود را اعمال کرده و همانطور که مشخص میباشد به دلیل اینکه تنظیمات نگاشت بین Detail و DetailModel در ابتدای بحث نیز انجام شده است، این بار دیگر نیاز به استفاده از متد Include نمیباشد و این عملیات توسط متد ProjectTo خودکار میباشد. در نهایت داده ارسالی توسط کلاینت را دریافت کرده و به شکل زیر عملیات به روز رسانی انجام میشود:
using (var context = new ProjectDbContext()) { Console.WriteLine( "################ Unchanged Master and Modified Details and Deleted DetailsOfDetail ##################"); Print(masterModel); var masterEntity = Mapper.Map<Master>(masterModel); context.ChangeTracker.TrackGraph( masterEntity, n => { var entity = (IHaveTrackingState) n.Entry.Entity; n.Entry.State = entity.TrackingState.ToEntityState(); }); context.SaveChanges(); }
برای بحث اعتبارسنجی هم میتوان به شکل زیر عمل کرد:
public class MasterValidator : AbstractValidator<MasterModel> { public MasterValidator() { RuleFor(a => a.Title).NotEmpty(); RuleForEach(a => a.Details).SetValidator(new DetailValidator()); } } public class DetailValidator : AbstractValidator<DetailModel> { public DetailValidator() { RuleFor(a => a.Title).NotEmpty(); RuleForEach(a => a.Details).SetValidator(new DetailOfDetailValidator()); } } public class DetailOfDetailValidator : AbstractValidator<DetailOfDetailModel> { public DetailOfDetailValidator() { RuleFor(a => a.Title).NotEmpty(); } }
با استفاده از متد RuleForEach و SetValidator موجود در کتابخانه FluentValidation، امکان مشخص کردن اعتبارسنج برای Detail موجود در شیء Master را خواهیم داشت.
همچنین با توجه به این که برای عملیات Create و Edit از یک مدل (DTO) استفاده خواهیم کرد، شاید لازم باشد اعتبارسنجی خاصی را فقط در زمان ویرایش لازم داشته باشیم، که در این صورت میتوان از امکانات RuleSet استفاده کنید. در مطلب «طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها» با استفاده ValidateWithRuleAttribute امکان مشخص کردن RuleSet مورد نظر برای اعتبارسنجی ورودی متد سرویس نیز در نظر گرفته شده است.
منابع تکمیلی
- ChangeTracker.TrackGraph() in Entity Framework Core
- Disconnected entities
- https://msdn.microsoft.com/magazine/mt694083
- https://msdn.microsoft.com/magazine/mt767693
- https://blog.tonysneed.com/2017/10/01/trackable-entities-for-ef-core/
- Tracking Individually Modified Properties
ساخت بستههای نیوگت مخصوص NET Core.
نکتهی آنرا در اینجا میتوانید مطالعه کنید و خلاصهی آن به صورت ذیل است:
"frameworks": { "net40": { "frameworkAssemblies": { } }, "net45": { "frameworkAssemblies": { } }, "net46": { "frameworkAssemblies": { } }, "netstandard1.3": { "imports": "dnxcore50", "dependencies": { "NETStandard.Library": "1.6.1", "System.Globalization.Extensions": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0" } } },
همین مقدار تغییر به همراه نکتهی scripts -> postcompile ابتدای بحث جاری، سبب خواهد شد تا کتابخانهی جاری برای تمام فریم ورکهای یاد شده به صورت مجزا کامپایل شده و درون بستهی نیوگت نهایی قرار گیرد:
در این حالت ممکن است قسمتی از کدها مثلا برای دات نت 4 قابل استفاده نباشند و نیاز به تغییر داشته باشند. برای این حالت باید از if directives# جهت شرطی کردن کامپایلر کمک گرفت:
#if NET40 // This only compiles for the .NET Framework 4 targets #else // This compiles for all other targets #endif
Blazor 5x - قسمت 23 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 3 - کار با نقشهای کاربران
کار با Authentication State از طریق کدنویسی
فرض کنید در کامپوننت HotelRoomUpsert.razor نمیخواهیم دسترسیها را به کمک اعمال ویژگی attribute [Authorize]@ محدود کنیم؛ میخواهیم اینکار را از طریق کدنویسی مستقیم انجام دهیم:
// ... @*@attribute [Authorize]*@ @code { [CascadingParameter] public Task<AuthenticationState> AuthenticationState { get; set; } protected override async Task OnInitializedAsync() { var authenticationState = await AuthenticationState; if (!authenticationState.User.Identity.IsAuthenticated) { var uri = new Uri(NavigationManager.Uri); NavigationManager.NavigateTo($"/identity/account/login?returnUrl={uri.LocalPath}"); } // ...
- سپس یک پارامتر ویژه را از نوع CascadingParameter، به نام AuthenticationState تعریف کردیم. این خاصیت از طریق کامپوننت CascadingAuthenticationState که در قسمت قبل به فایل BlazorServer.App\App.razor اضافه کردیم، تامین میشود.
- در آخر در روال رویدادگردان OnInitializedAsync، بر اساس آن میتوان به اطلاعات User جاری وارد شدهی به سیستم دسترسی یافت و برای مثال اگر اعتبارسنجی نشده بود، با استفاده از NavigationManager، او را به صفحهی لاگین هدایت میکنیم.
- در اینجا روش ارسال آدرس صفحهی فعلی را نیز مشاهده میکنید. این امر سبب میشود تا پس از لاگین، کاربر مجددا به همین صفحه هدایت شود.
authenticationState، امکانات بیشتری را نیز در اختیار ما قرار میدهد؛ برای مثال با استفاده از متد ()authenticationState.User.IsInRole آن میتوان دسترسی به قسمتی را بر اساس نقشهای خاصی محدود کرد.
ثبت کاربر ادمین Identity
در ادامه میخواهیم دسترسی به کامپوننتهای مختلف را بر اساس نقشها، محدود کنیم. به همین جهت نیاز است تعدادی نقش و یک کاربر ادمین را به بانک اطلاعاتی برنامه اضافه کنیم. برای اینکار به پروژهی BlazorServer.Common مراجعه کرده و تعدادی نقش ثابت را تعریف میکنیم:
namespace BlazorServer.Common { public static class ConstantRoles { public const string Admin = nameof(Admin); public const string Customer = nameof(Customer); public const string Employee = nameof(Employee); } }
سپس در فایل BlazorServer.App\appsettings.json، مشخصات ابتدایی کاربر ادمین را ثبت میکنیم:
{ "AdminUserSeed": { "UserName": "vahid@dntips.ir", "Password": "123@456#Pass", "Email": "vahid@dntips.ir" } }
namespace BlazorServer.Models { public class AdminUserSeed { public string UserName { get; set; } public string Password { get; set; } public string Email { get; set; } } }
namespace BlazorServer.App { public class Startup { public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddOptions<AdminUserSeed>().Bind(Configuration.GetSection("AdminUserSeed")); // ...
using System; using System.Linq; using System.Threading.Tasks; using BlazorServer.Common; using BlazorServer.DataAccess; using BlazorServer.Models; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; namespace BlazorServer.Services { public class IdentityDbInitializer : IIdentityDbInitializer { private readonly ApplicationDbContext _dbContext; private readonly UserManager<IdentityUser> _userManager; private readonly RoleManager<IdentityRole> _roleManager; private readonly IOptions<AdminUserSeed> _adminUserSeedOptions; public IdentityDbInitializer( ApplicationDbContext dbContext, UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<AdminUserSeed> adminUserSeedOptions) { _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); _adminUserSeedOptions = adminUserSeedOptions ?? throw new ArgumentNullException(nameof(adminUserSeedOptions)); } public async Task SeedDatabaseWithAdminUserAsync() { if (_dbContext.Roles.Any(role => role.Name == ConstantRoles.Admin)) { return; } await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Admin)); await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Customer)); await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Employee)); await _userManager.CreateAsync( new IdentityUser { UserName = _adminUserSeedOptions.Value.UserName, Email = _adminUserSeedOptions.Value.Email, EmailConfirmed = true }, _adminUserSeedOptions.Value.Password); var user = await _dbContext.Users.FirstAsync(u => u.Email == _adminUserSeedOptions.Value.Email); await _userManager.AddToRoleAsync(user, ConstantRoles.Admin); } } }
پس از تعریف این سرویس، نیاز است آنرا به سیستم تزریق وابستگیهای برنامه اضافه کرد:
namespace BlazorServer.App { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddScoped<IIdentityDbInitializer, IdentityDbInitializer>(); // ...
using System; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Polly; namespace BlazorServer.DataAccess.Utils { public static class MigrationHelpers { public static void MigrateDbContext<TContext>( this IServiceProvider serviceProvider, Action<IServiceProvider> postMigrationAction ) where TContext : DbContext { using var scope = serviceProvider.CreateScope(); var scopedServiceProvider = scope.ServiceProvider; var logger = scopedServiceProvider.GetRequiredService<ILogger<TContext>>(); using var context = scopedServiceProvider.GetService<TContext>(); logger.LogInformation($"Migrating the DB associated with the context {typeof(TContext).Name}"); var retry = Policy.Handle<Exception>().WaitAndRetry(new[] { TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15) }); retry.Execute(() => { context.Database.Migrate(); postMigrationAction(scopedServiceProvider); }); logger.LogInformation($"Migrated the DB associated with the context {typeof(TContext).Name}"); } } }
کار متد الحاقی فوق، دریافت یک IServiceProvider است که به سرویسهای اصلی برنامه اشاره میکند. سپس بر اساس آن، یک Scoped ServiceProvider را ایجاد میکند تا درون آن بتوان با Context برنامه در طی مدت کوتاهی کار کرد و در پایان آن، سرویسهای ایجاد شده را Dispose کرد.
در این متد ابتدا Database.Migrate فراخوانی میشود تا اگر مرحلهای از Migrations برنامه هنوز به بانک اطلاعاتی اعمال نشده، کار اجرا و اعمال آن انجام شود. سپس یک متد سفارشی را از فراخوان دریافت کرده و اجرا میکند. برای مثال توسط آن میتوان IIdentityDbInitializer در فایل BlazorServer.App\Program.cs به صوت زیر فراخوانی کرد:
public static void Main(string[] args) { var host = CreateHostBuilder(args).Build(); host.Services.MigrateDbContext<ApplicationDbContext>( scopedServiceProvider => scopedServiceProvider.GetRequiredService<IIdentityDbInitializer>() .SeedDatabaseWithAdminUserAsync() .GetAwaiter() .GetResult() ); host.Run(); }
و همچنین کاربر پیشفرض سیستم را نیز میتوان مشاهده کرد:
که نقش ادمین و کاربر پیشفرض، به این صورت به هم مرتبط شدهاند (یک رابطهی many-to-many برقرار است):
محدود کردن دسترسی کاربران بر اساس نقشها
پس از ایجاد کاربر ادمین و تعریف نقشهای پیشفرض، اکنون محدود کردن دسترسی به کامپوننتهای برنامه بر اساس نقشها، سادهاست. برای این منظور فقط کافی است لیست نقشهای مدنظر را که میتوانند توسط کاما از هم جدا شوند، به ویژگی Authorize کامپوننتها معرفی کرد:
@attribute [Authorize(Roles = ConstantRoles.Admin)]
protected override async Task OnInitializedAsync() { var authenticationState = await AuthenticationState; if (!authenticationState.User.Identity.IsAuthenticated || !authenticationState.User.IsInRole(ConstantRoles.Admin)) { var uri = new Uri(NavigationManager.Uri); NavigationManager.NavigateTo($"/identity/account/login?returnUrl={uri.LocalPath}"); }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-23.zip
کتاب «مرجع کامل entity framework 4.1» نوشتهی آقای راد نزدیک به یک ماهی است که منتشر شده است. فرصتی پیدا شد تا این کتاب حدودا 260 صفحهای را مطالعه کنم و در ادامه توضیحاتی را پیرامون آن مطالعه خواهید کرد.
بررسی کتاب
در عنوان کتاب ذکر شده «مرجع کامل»؛ ولی خوب، 260 نمیتونه مرجع کامل باشه. بنابراین کمی رعایت اعتدال در کارهای بعدی لازم به نظر میرسد. همچنین یک مورد را هم همیشه در نشر کتب تخصصی در نظر داشته باشید: «ذکر شماره نگارش محصول» مورد نظر در عنوان کتاب، خیلی سریع کار شما را از مد افتاده خواهد کرد. خیالتان راحت باشد تا یک سال دیگر همینطور این شمارهها افزایش پیدا میکنند. خریداری هم که آنچنان اطلاعاتی از کل کار نداشته باشد، بر اساس همین شماره و بدون مطالعه متن، از خرید کتاب امتناع خواهد کرد.
- فصل اول این کتاب به معرفی تاریخچهی EF و لزوم استفاده از آن میپردازد. همچنین خلاصهای از قابلیتهای آنرا همانند روشهای model first ، database first و code first بیان میکند.
- تمرکز فصل دوم بر نحوهی استفاده از روشهای model first و database first است به همراه نحوهی تولید اسکریپت بانک اطلاعاتی در حالت model first.
- فصل سوم کتاب به مرور جزئیات طراح EF در ویژوال استودیو جهت کار بهتر با موجودیتها اختصاص دارد.
- در فصل چهارم با روشهای کوئری نویسی در EF آشنا خواهید شد. همچنین بر روی مباحث اجرای به تعویق افتاده و مفهوم آن هم بحث شده که بسیار ارزشمند است.
- فصل پنجم کتاب به مباحث ثبت، حذف و به روز رسانی اطلاعات توسط EF اختصاص دارد. همچنین یک سری مباحث همانند سطح اول caching در NHibernate که در EF هم وجود دارد، بررسی شده است که البته نام آن در اینجا Object state و entity state است.
- در فصل ششم در مورد نحوهی نگاشت رویههای ذخیره شده SQL Server به اشیاء دات نتی بحث شده همچنین نحوهی اجرا و استفاده از آنها
- فصل هفتم کتاب به ارتباطات بین موجودیتها یا همان مباحث one to many و امثال آن اختصاص دارد به همراه نحوهی تنظیمات آن در طراح EF در VS.NET
- در فصل هشتم، به قالبهای T4 پرداخته شده. ابتدا معرفی، سپس آشنایی با Syntax و نهایتا نحوهی دستکاری و سفارشی سازی قالبهای پیش فرض T4 مرتبط با EF ارائه شدهاند.
- فصل نهم به بررسی کاملتر مبحث model first که در فصل دوم معرفی شده میپردازد. ایجاد موجودیتها، نحوهی تعریف ارتباطات و نهایتا ایجاد بانک اطلاعاتی از روی آن
- فصل دهم آن به مباحث جدید EF در مورد Code first اختصاص دارد. این فصل واقعا ارزشمند است چون ... نتیجهی تحقیق بوده نه ترجمه. تقریبا با تمام تاریخچهی مرتبط با code first در EF، محلهای دریافت فایلها، ابزارهای کمکی، روشهای کوئری گرفتن،نحوهی ایجاد بانک اطلاعاتی از روی کد، تعیین اعتبار و غیره در طی یک فصل آشنا خواهید شد.
- در فصل یازدهم آن مروری بر WCF Data services و پروتکل OData صورت گرفته است. نحوهی ایجاد و سپس فراخوانی آن توسط یک کلاینت. در عنوان کتاب ذکر شده : «مرجع»، بنابراین به دنبال یک کتاب خودآموز قدم به قدم نباشید. این کتاب بیشتر به «معرفی» امکانات موجود در EF در طی 260 صفحه میپردازد که الزاما با توجه به تعداد صفحات کتاب، بعضی از موارد آن مانند این فصل آخر، از عمق لازم برخوردار نیستند ولی، حداقل سرنخ را به دست شما خواهند داد.
مزایا:
- به روز بودن مطالب آن
- آشنایی و تسلط مؤلف/مترجم به مطالبی که تهیه کرده. این مورد در فصل دهم آن مشهود است.
- زبان فارسی (بله! خیلی مهمه! هستند کسانی که چند گیگ، ببخشید چند صد گیگ (!)، eBook به زبان انگلیسی دارند ولی حتی یکی از آنها را هم تمام نکردهاند)
- متن روان و سلیس
- کیفیت خوب کتاب، صفحه بندی و امثال آن
معایب:
- قیمت نزدیک به 8000 تومان برای کتاب 260 صفحهای به نظر زیاد است. البته با بالا رفتن قیمتها (برای مثال 4 برابر شدن قیمت یک عدد نان لواش از سال قبل تا به امسال!)، بالاخره ... خوب این مسایل را هم به همراه خواهد داشت.
- تصاویر موجود در کتاب عموما بیش از اندازه کوچک شدهاند. این مورد خواندن تعدادی از آنها را با مشکل مواجه کرده است.
- در مورد متد الحاقی معروف Include در EF من مطلبی را در این کتاب پیدا نکردم. این مورد به بحث عدم نیاز به join نویسی صریح در EF مرتبط میشود.
- در مورد نحوهی استفاده از EF با سایر بانکهای اطلاعاتی بحث نشده. کتاب فقط به SQL Server منحصر است.
- در یکی از فصلها به الگوی Repository در حد نامبردن اشاره شده. این مورد برای خوانندهای که اطلاعاتی از موضوع ندارد، کافی نیست. میشد یک فصل را به آن اختصاص داد.
در کل خواندن کتاب «معرفی» EF 4.1 ، به کسانی که با Silverlight و WCF RIA Services سر و کار دارند (و کوئریهای آن برایشان کمی گنگ است) و همچنین عموم علاقمندانی که میخواهند جایگزینی برای ADO.NET (در یک سطح بالاتر از آن البته) پیدا کنند توصیه میشود.
در حاشیه!
شاید بپرسید چرا این کتاب در 260 صفحه و چرا فقط در 1000 نسخه منتشر شده است. چرا اینقدر تعداد کتابهای تخصصی کم است. چرا بیشتر تمایل به چاپ کتابهای نصب ویندوز و امثال آن است تا مثلا کتاب EF 4.1 یا خدای نکرده NHibernate ! پاسخ هم در یک جمله خلاصه میشود: «نگرانی ناشر از بازگشت سرمایه»
این شما هستید که با پشتیبانی خود میتوانید این امیدواری را به ناشرین کشور بدهید تا «جرات کنند» بیشتر به طرف کتابهای تخصصی بروند و این پشتیبانی با صرفا گفتن چقدر عالی، دست شما درد نکنه، خیلی خوب بود، باز هم از این کارها بکنید، معنا پیدا نمیکند! باید لطف کنید و «خرید کنید». هیچ راه دیگری هم ندارد. الان چند عدد کتاب ASP.NET MVC 3.0 در کشور به زبان فارسی وجود دارد؟ چند عدد کتاب تخصصی SQL Server 2008 R2 را میتوانید پیدا کنید؟ در مورد کتابخانه پردازش موازی دات نت 4 چطور؟ و ...
البته منهای نگرانی این بحث بازگشت سرمایه ، یک مورد دیگر هم سبب این نوع تاخیرها هست. یادم میاد کتاب الگوهای طراحی برنامه نویسی شیءگرا در سی شارپ رو که چند سال قبل به ناشر دادم نحوهی پرداخت آن به این صورت بود: نزدیک به 10 درصد پشت جلد، در طی چند قسط، آن هم 6 ماه پس از انتشار عمومی کتاب! خوب همین شد که من دیگر به طرف این کار نرفتم. چون واقعا نوشتن، یک «کار» کامل است. باید وقت گذاشت (6 ماه حداقل یا بیشتر)، تحقیق کرد، ریاضت کشید و دست آخر 6 ماه پس از انتشار کتاب ... با توجه به اینکه کتاب رو که الان شما به دست ناشر میدید شاید یکسال دیگر منتشر شود (بسته به تعداد کاری که در دست دارد).
در هر حال، با تمام این تفاسیر، هستند کسانی که «امیدوارانه» نسبت به نوشتن کتابهای تخصصی مانند «مرجع کامل entity framework 4.1» اقدام میکنند و شما هم حداقل کاری که میتوانید جهت حمایت از این نوع حرکات بکنید، «خرید است». در غیراینصورت مدام اینطرف اونطرف ننویسید که چرا کتاب WPF 4.0 یا WCF 4.0 به زبان فارسی نداریم. پشتیبانی نمیکنید؟! خوب ... نداریم! «همین!»
یک مورد دیگر هم هست البته. عدهای هستند که مثلا کلاسهای میلیونی، جهت آموزش این مباحث برگزار میکنند. خوب اینها هم مسلما خوشحال نخواهند شد که مثلا کتاب WCF 4.0 و مباحث SOA مرتبط با آن به زبان فارسی منتشر شود یا حتی در این زمینه پیش قدم شوند. این هم هست!
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.0.0-preview7.19362.6" PrivateAssets="all" />