paging هم یکی از همونها بود تازه داره پخته میشه
این syntax من رو یاد mysql انداخت
SELECT column FROM table LIMIT 10 OFFSET 10
SELECT column FROM table LIMIT 10 OFFSET 10
Soon the next major version of the framework will come out, which breaks backward compatibility. Apparently, even developers of the framework realized that there is something wrong and decided to rewrite the framework almost from scratch. They don’t add new functionality, breaking backward compatibility, they rewrite almost everything.
پروژه DNTFrameworkCore که قصد پشتیبانی از آن را دارم، یک زیرساخت سبک وزن و توسعه پذیر با پشتیبانی از طراحی چند مستاجری، با تمرکز بر کاهش زمان و افزایش کیفیت توسعه سیستمهای تحت وب مبتنی بر ASP.NET Core، توسعه داده شده است.
اهدافی که این زیرساخت دنبال میکند
امکانات این زیرساخت در زمان نگارش مطلب جاری
نحوه استفاده از بستههای نیوگت مرتبط
PM> Install-Package DNTFrameworkCore -Version 1.0.0
services.AddDNTFramework() .AddDataAnnotationValidation() .AddModelValidation() .AddValidationOptions(options => { /*options.IgnoredTypes.Add(typeof());*/ }) .AddMemoryCache() .AddAuditingOptions(options => { // options.Enabled = true; // options.EnabledForAnonymousUsers = false; // options.IgnoredTypes.Add(typeof()); // options.Selectors.Add(new NamedTypeSelector("SelectorName", type => type == typeof())); }).AddTransactionOptions(options => { // options.Timeout=TimeSpan.FromMinutes(3); //options.IsolationLevel=IsolationLevel.ReadCommitted; });
متدهای الحاقی بالا برای ثبت سرویسها و تنظیمات مرتبط با مکانیزمهای اعتبارسنجی خودکار، مدیریت تراکنشها، لاگ آماری، Eventing و سایر امکانات ذکر شده، در IoC Container توکار ASP.NET Core استفاده خواهند شد.
PM> Install-Package DNTFrameworkCore.EntityFramework -Version 1.0.0
services.AddDNTUnitOfWork<ProjectDbContext>();
بسته نیوگت بالا شامل پیاده سازی مبتنی بر EF Core برای واسطهای تعریف شده در بسته نیوگت DNTFrameworkCore، میباشد؛ از جمله آن میتوان به CrudService پایه اشاره کرد. متد الحاقی AddDNTUnitOfWork برای ثبت و معرفی واسطهای IUnitOfWork و ITransactionProvider به عنوان مهیا کننده تراکنش به همراه پیاده سازهای آنها و همچنین ثبت یک سری Hook تعریف شده برای ردیابی تغییرات، در سیستم تزریق وابستگی، استفاده خواهد شد.
همچنین با نصب بسته بالا، امکان استفاده از مهیا کننده Logging با امکان ذخیره سازی در بانک اطلاعاتی را خواهید داشت:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); }) .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); logging.AddEntityFramework<ProjectDbContext>(options => options.MinLevel = LogLevel.Warning); }) .UseStartup<Startup>();
متد جنریک الحاقی AddEntityFramework برای ثبت مهیا کننده مذکور استفاده میشود.
PM> Install-Package DNTFrameworkCore.Web -Version 1.0.0
بسته نیوگت بالا شامل یکسری سرویس برای اعمال دسترسیهای پویا، CrudController مبتنی بر ASP.NET Core Web API، فیلتر مدیریت سراسری خطاهای برنامه و سایر امکاناتی که در ادامه مقالات با جزئیات بیشتری بررسی خواهیم کرد، میباشد. برای ثبت سرویسهای تعریف شده میتوانید از متد الحاقی AddDNTCommonWeb و به منظور تغییر محل ذخیره سازی کلیدهای موقت رمزنگاری مرتبط با Data Protection API و انتقال آنها به بانک اطلاعاتی، استفاده کنید.
services.AddDNTCommonWeb() .AddDNTDataProtection();
نکته: برای انتقال کلیدهای موقت رمزنگاری به بانک اطلاعاتی، نیاز است تا از متد الحاقی زیر که در بسته نیوگت DNTFrameworkCore.EntityFramework موجود میباشد، به شکل زیر استفاده کنید:
services.AddDNTProtectionRepository<ProjectDbContext>();
PM> Install-Package DNTFrameworkCore.EntityFramework.SqlServer -Version 1.0.0
بسته بالا از امکانات مخصوص SqlServer برای اعمال قفل منطقی برای مدیریت مباحث همزمانی استفاده میکند؛ همچنین PreUpdateHook مرتبط با تولید خودکار کد منحصر به فرد، در این کتابخانه پیاده سازی شده است. به شکل زیر میتوانید سرویسهای مرتبط با آن را به سیستم تزریق وابستگیهای معرفی کنید:
services.AddDNTNumbering(options => { options.NumberedEntityMap[typeof(Task)] = new NumberedEntityOption { Start = 100, Prefix = "Task-", IncrementBy = 5 }; });
به عنوان مثال برای شماره گذاری موجودیت Task، لازم است تنظیمات مرتبط آن را به شکل بالا به سیستم شماره گذاری معرفی کنید.
اگر قصد استفاده از کتابخانه FluentValidation را داشته باشید، میبایست بسته زیر را نیز نصب کنید:
PM> Install-Package DNTFrameworkCore.FluentValidation -Version 1.0.0
برای ثبت و معرفی Adapter مرتبط، به سیستم اعتبارسنجی خودکار معرفی شده، لازم است از طریق متد الحاقی AddFluentModelValidation به شکل زیر اقدام کنید:
services.AddDNTFramework() .AddDataAnnotationValidation() .AddModelValidation() .AddFluentModelValidation() .AddValidationOptions(options => { /*options.IgnoredTypes.Add(typeof());*/ }) .AddMemoryCache() .AddAuditingOptions(options => { // options.Enabled = true; // options.EnabledForAnonymousUsers = false; // options.IgnoredTypes.Add(typeof()); // options.Selectors.Add(new NamedTypeSelector("SelectorName", type => type == typeof())); }).AddTransactionOptions(options => { // options.Timeout=TimeSpan.FromMinutes(3); //options.IsolationLevel=IsolationLevel.ReadCommitted; });
برای شروع پروژه جدید، نصب این بستهها کفایت میکند. اگر نیاز به طراحی MultiTenancy دارید، بسته زیر را برای شناسایی Tenant جاری و از این قبیل کارها نیز میبایست نصب کنید:
PM> Install-Package DNTFrameworkCore.Web.MultiTenancy -Version 1.0.0
services.AddMultiTenancy<TenantResolver>();
app.UseMultiTenancy();
services.Scan(scan => scan .FromCallingAssembly() .AddClasses(classes => classes.AssignableTo<ISingletonDependency>()) .AsMatchingInterface() .WithSingletonLifetime() .AddClasses(classes => classes.AssignableTo<IScopedDependency>()) .AsMatchingInterface() .WithScopedLifetime() .AddClasses(classes => classes.AssignableTo<ITransientDependency>()) .AsMatchingInterface() .WithTransientLifetime() .AddClasses(classes => classes.AssignableTo(typeof(IDomainEventHandler<>))) .AsImplementedInterfaces() .WithTransientLifetime()); foreach (var descriptor in services.Where(s => typeof(IApplicationService).IsAssignableFrom(s.ServiceType)) .ToList()) { services.Decorate(descriptor.ServiceType, (target, serviceProvider) => ProxyGenerator.CreateInterfaceProxyWithTargetInterface( descriptor.ServiceType, target, serviceProvider.GetRequiredService<ValidationInterceptor>(), (IInterceptor) serviceProvider.GetRequiredService<TransactionInterceptor>())); }
using System; using PdfRpt; using PdfRpt.Core.Contracts; using PdfRpt.Core.Helper; using PdfRpt.FluentInterface; namespace PdfReportSamples.AdHocColumns { public class AdHocColumnsPdfReport { public IPdfReportData CreatePdfReport() { return new PdfReport().DocumentPreferences(doc => { doc.RunDirection(PdfRunDirection.RightToLeft); doc.Orientation(PageOrientation.Portrait); doc.PageSize(PdfPageSize.A4); doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" }); }) .DefaultFonts(fonts => { fonts.Path(AppPath.ApplicationPath + "\\fonts\\irsans.ttf", Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf"); }) .PagesFooter(footer => { footer.DefaultFooter(printDate: DateTime.Now.ToString("MM/dd/yyyy")); }) .PagesHeader(header => { header.DefaultHeader(defaultHeader => { defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png"); defaultHeader.Message("گزارش جدید ما"); }); }) .MainTableTemplate(template => { template.BasicTemplate(BasicTemplate.SilverTemplate); }) .MainTablePreferences(table => { table.ColumnsWidthsType(TableColumnWidthType.Relative); }) .MainTableDataSource(dataSource => { dataSource.GenericDataReader( providerName: "System.Data.SQLite", connectionString: "Data Source=" + AppPath.ApplicationPath + "\\data\\blogs.sqlite", sql: @"SELECT [url] as 'آدرس', [name] as 'نام', [NumberOfPosts] as 'تعداد مطالب', [AddDate] as 'تاریخ ارسال' FROM [tblBlogs] WHERE [NumberOfPosts]>=@p1", parametersValues: new object[] { 10 } ); }) .MainTableSummarySettings(summary => { summary.OverallSummarySettings("جمع کل"); summary.PreviousPageSummarySettings("نقل از صفحه قبل"); summary.PageSummarySettings("جمع صفحه"); }) .MainTableAdHocColumnsConventions(adHocColumns => { //We want sum of the int columns adHocColumns.AddTypeAggregateFunction( typeof(Int64), new AggregateProvider(AggregateFunction.Sum) { DisplayFormatFormula = obj => obj == null ? string.Empty : string.Format("{0:n0}", obj) }); //We want to dispaly all of the dateTimes as ShamsiDateTime adHocColumns.AddTypeDisplayFormatFormula( typeof(DateTime), data => { return PersianDate.ToPersianDateTime((DateTime)data); } ); adHocColumns.ShowRowNumberColumn(true); adHocColumns.RowNumberColumnCaption("ردیف"); }) .MainTableEvents(events => { events.DataSourceIsEmpty(message: "There is no data available to display."); }) .Export(export => { export.ToExcel(); export.ToXml(); }) .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\AdHocColumnsSampleRpt.pdf")); } } }
SELECT [url] as 'آدرس', [name] as 'نام', [NumberOfPosts] as 'تعداد مطالب', [AddDate] as 'تاریخ ارسال' FROM [tblBlogs] WHERE [NumberOfPosts]>=@p1
adHocColumns.ShowRowNumberColumn(true); adHocColumns.RowNumberColumnCaption("ردیف");
adHocColumns.AddTypeDisplayFormatFormula( typeof(DateTime), data => { return PersianDate.ToPersianDateTime((DateTime)data); } );
adHocColumns.AddTypeAggregateFunction( typeof(Int64), new AggregateProvider(AggregateFunction.Sum) { DisplayFormatFormula = obj => obj == null ? string.Empty : string.Format("{0:n0}", obj) });
Unable to generate an explicit migration because the following explicit migrations are pending: ...
SELECT [MigrationId] ,[ContextKey] ,[Model] ,[ProductVersion] FROM [dbo].[__MigrationHistory]
UPDATE [dbo].[__MigrationHistory] SET [ContextKey] = 'VMT.Data.Migrations.Configuration' WHERE [ContextKey] = 'MyProject.Migrations.Configuration';
Unable to update database to match the current model because there are pending changes and
automatic migration is disabled. Either write the pending model changes to a code-based migration
or enable automatic migration. Set DbMigrationsConfiguration.AutomaticMigrationsEnabled to true
to enable automatic migration.
Add-Migration AddSomeProp2ToProject
namespace EF_Sample02.Migrations
{
using System.Data.Entity.Migrations;
public partial class AddSomeProp2ToProject : DbMigration
{
public override void Up()
{
AddColumn("Projects", "SomeProp", c => c.String());
AddColumn("Projects", "SomeProp2", c => c.String());
}
public override void Down()
{
DropColumn("Projects", "SomeProp2");
DropColumn("Projects", "SomeProp");
}
}
}
Update-Database -Verbose
Using NuGet project 'EF_Sample02'.
Using StartUp project 'EF_Sample02'.
Target database is: 'testdb2012' (DataSource: (local), Provider: System.Data.SqlClient, Origin: Configuration).
Applying explicit migrations: [201205061835024_AddSomeProp2ToProject].
Applying explicit migration: 201205061835024_AddSomeProp2ToProject.
ALTER TABLE [Projects] ADD [SomeProp] [nvarchar](max)
ALTER TABLE [Projects] ADD [SomeProp2] [nvarchar](max)
[Inserting migration history record]
Add-Migration AddSomeProp3ToProject
Update-Database -Verbose
get-help update-database -detailed
Update-Database -TargetMigration:"AddSomeProp2ToProject" -Verbose
Using NuGet project 'EF_Sample02'.
Using StartUp project 'EF_Sample02'.
Target database is: 'testdb2012' (DataSource: (local), Provider: System.Data.SqlClient, Origin: Configuration).
Reverting migrations: [201205061845485_AddSomeProp3ToProject].
Reverting explicit migration: 201205061845485_AddSomeProp3ToProject.
DECLARE @var0 nvarchar(128)
SELECT @var0 = name
FROM sys.default_constraints
WHERE parent_object_id = object_id(N'Projects')
AND col_name(parent_object_id, parent_column_id) = 'SomeProp3';
IF @var0 IS NOT NULL
EXECUTE('ALTER TABLE [Projects] DROP CONSTRAINT ' + @var0)
ALTER TABLE [Projects] DROP COLUMN [SomeProp3]
[Deleting migration history record]
namespace EF_Sample02.Migrations
{
using System.Data.Entity.Migrations;
public partial class AddSomeProp3ToProject : DbMigration
{
public override void Up()
{
AddColumn("Projects", "SomeProp3", c => c.String(defaultValue: "some data"));
Sql("Update Projects set SomeProp3=N'some data'");
}
public override void Down()
{
DropColumn("Projects", "SomeProp3");
}
}
}
public ColumnModel String(bool? nullable = null, int? maxLength = null, bool? fixedLength = null,
bool? isMaxLength = null, bool? unicode = null, string defaultValue = null, string defaultValueSql = null,
string name = null, string storeType = null)
ALTER TABLE [Projects] ADD [SomeProp3] [nvarchar](max) DEFAULT 'some data'
namespace EF_Sample02.Migrations
{
using System;
using System.Data.Entity.Migrations;
internal sealed class Configuration : DbMigrationsConfiguration<EF_Sample02.Sample2Context>
{
public Configuration()
{
this.AutomaticMigrationsEnabled = false;
this.AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(EF_Sample02.Sample2Context context)
{
context.Users.AddOrUpdate(
a => a.Name,
new Models.User { Name = "Vahid", AddDate = DateTime.Now },
new Models.User { Name = "Test", AddDate = DateTime.Now });
}
}
}
Update-Database -Verbose -Script
ALTER TABLE [Projects] ADD [SomeProp5] [nvarchar](max)
INSERT INTO [__MigrationHistory] ([MigrationId], [CreatedOn], [Model], [ProductVersion]) VALUES
('201205060852004_AutomaticMigration', '2012-05-06T08:52:00.937Z', 0x1F8B0800000............ '4.3.1')
Update-Database -Verbose -Script -SourceMigration:"stepName"
Add-Migration AddSomePropToProject
Update-Database -Verbose -Script
[ServiceContract] public interface IService1 { [OperationContract] Order InsertOrder(); [OperationContract] void UpdateOrderWithoutRetrieving(Order order); [OperationContract] void UpdateOrderByRetrieving(Order order); }
public class Service1 : IService1 { public Order InsertOrder() { using (var context = new EFRecipesEntities()) { // remove previous test data context.Database.ExecuteSqlCommand("delete from [orders]"); var order = new Order { Product = "Camping Tent", Quantity = 3, Status = "Received" }; context.Orders.Add(order); context.SaveChanges(); return order; } } public void UpdateOrderWithoutRetrieving(Order order) { using (var context = new EFRecipesEntities()) { try { context.Orders.Attach(order); if (order.Status == "Received") { context.Entry(order).Property(x => x.Quantity).IsModified = true; context.SaveChanges(); } } catch (OptimisticConcurrencyException ex) { // Handle OptimisticConcurrencyException } } } public void UpdateOrderByRetrieving(Order order) { using (var context = new EFRecipesEntities()) { // fetch current entity from database var dbOrder = context.Orders .Single(o => o.OrderId == order.OrderId); if (dbOrder != null && // execute concurrency check StructuralComparisons.StructuralEqualityComparer.Equals(order.TimeStamp, dbOrder.TimeStamp)) { dbOrder.Quantity = order.Quantity; context.SaveChanges(); } else { // Add code to handle concurrency issue } } } }
class Program { static void Main(string[] args) { var service = new Service1Client(); var order = service.InsertOrder(); order.Quantity = 5; service.UpdateOrderWithoutRetrieving(order); order = service.InsertOrder(); order.Quantity = 3; service.UpdateOrderByRetrieving(order); } }
متد ()InsertOrder دادههای پیشین را حذف میکند، سفارش جدیدی میسازد و آن را در دیتابیس ثبت میکند. در آخر موجودیت جدید به کلاینت باز میگردد. موجودیت بازگشتی هر دو مقدار OrderId و TimeStamp را دارا است که توسط دیتابیس تولید شده اند. سپس در کلاینت از دو رویکرد نسبتا متفاوت برای بروز رسانی موجودیت استفاده میکنیم.
در رویکرد نخست، متد ()UpdateOrderWithoutRetrieving موجودیت دریافت شده از کلاینت را Attach میکند و چک میکند که مقدار فیلد Status چیست. اگر مقدار این فیلد "Received" باشد، فیلد Quantity را با EntityState.Modified علامت گذاری میکنیم و متد ()SaveChanges را فراخوانی میکنیم. EF دستورات لازم برای بروز رسانی را تولید میکند، که فیلد quantity را مقدار دهی کرده و یک عبارت where هم دارد که فیلدهای OrderId و TimeStamp را چک میکند. اگر مقدار TimeStamp توسط یک دستور بروز رسانی تغییر کرده باشد، بروز رسانی جاری با خطا مواجه خواهد شد. برای مدیریت این خطا ما بدنه کد را در یک بلاک try/catch قرار میدهیم، و استثنای OptimisticConcurrencyException را مهار میکنیم. این کار باعث میشود اطمینان داشته باشیم که موجودیت Order دریافت شده از متد ()InsertOrder تاکنون تغییری نکرده است. دقت کنید که در مثال جاری تمام خواص موجودیت بروز رسانی میشوند، صرفنظر از اینکه تغییر کرده باشند یا خیر.
رویکرد دوم نشان میدهد که چگونه میتوان وضعیت همزمانی موجودیت را پیش از بروز رسانی مشخصا دریافت و بررسی کرد. در اینجا میتوانید مقدار TimeStamp موجودیت را از دیتابیس بگیرید و آن را با مقدار موجودیت کلاینت مقایسه کنید تا وجود تغییرات مشخص شود. این رویکرد در متد ()UpdateOrderByRetrieving نمایش داده شده است. گرچه این رویکرد برای تشخیص تغییرات خواص موجودیتها و یا روابط شان مفید و کارآمد است، اما بهترین روش هم نیست. مثلا ممکن است از زمانی که موجودیت را از دیتابیس دریافت میکنید، تا زمانی که مقدار TimeStamp آن را مقایسه میکنید و نهایتا متد ()SaveChanges را صدا میزنید، موجودیت شما توسط کلاینت دیگری بروز رسانی شده باشد.
مسلما رویکرد دوم هزینه برتر از رویکرد اولی است، چرا که برای مقایسه مقادیر همزمانی موجودیت ها، یکبار موجودیت را از دیتابیس دریافت میکنید. اما این رویکرد در مواقعی که Object graphهای بزرگ یا پیچیده (complex) دارید بهتر است، چون پیش از ارسال موجودیتها به context در صورت برابر نبودن مقادیر همزمانی پروسس را لغو میکنید.