آیا در این نسخه Xml Worker از قابلیت RTL و یونیکد پشتیبانی میکند؟
iTextSharp-5.3.2 منتشر شد
آیا در این نسخه Xml Worker از قابلیت RTL و یونیکد پشتیبانی میکند؟
در فضایی که همواره هیچ تضمینی وجود ندارد که درخواست ارسال شدهی به یک API، همواره مسیر خود را همانطور که انتظار میرود طی کرده و پاسخ مورد نظر را در اختیار ما قرار میدهد، بیشک تلاش مجدد برای پردازش درخواست مورد نظر، به دلیل خطاهای گذرا، یکی از راهکارهای مورد استفاده خواهد بود. تصور کنید قصد طراحی یک مجموعه API عمومی را دارید، بهنحوی که مصرف کنندگان بدون نگرانی از ایجاد خرابی یا تغییرات ناخواسته، امکان تلاش مجدد در سناریوهای مختلف مشکل در ارتباط با سرور را داشته باشند. حتما توجه کنید که برخی از متدهای HTTP مانند GET، به اصطلاح Idempotent هستند و در طراحی آنها همواره باید این موضوع مدنظر قرار بگیرد و خروجی مشابهی برای درخواستهای تکراری همانند، مهیا کنید.
در تصویر بالا، حالتی که درخواست، توسط کلاینت ارسال شده و در آن لحظه ارتباط قطع شدهاست یا با یک خطای گذرا در سرور مواجه شدهاست و همچنین سناریویی که درخواست توسط سرور دریافت و پردازش شدهاست ولی کلاینت پاسخی را دریافت نکردهاست، قابل مشاهدهاست.
نکته: Idempotence یکی از ویژگی های پایهای عملیاتی در ریاضیات و علوم کامپیوتر است و فارغ از اینکه چندین بار اجرا شوند، نتیجه یکسانی را برای آرگومانهای همسان، خروجی خواهند داد. این خصوصیت در کانتکستهای مختلفی از جمله سیستمهای پایگاه داده و وب سرویسها قابل توجه میباشد.
طبق HTTP RFC، متدهایی که پاسخ یکسانی را برای درخواستهای همسان مهیا میکنند، به اصطلاح Idempotent هستند. همچنین متدهایی که باعث نشوند تغییری در وضعیت سیستم در سمت سرور ایجاد شود، به اصطلاح Safe در نظر گرفته خواهند شد. برای هر دو خصوصیت عنوان شده، سناریوهای استثناء و قابل بحثی وجود دارند؛ بهعنوان مثال در مورد خصوصیت Safe بودن، درخواست GET ای را تصور کنید که یکسری لاگ آماری هم ثبت میکند یا عملیات بازنشانی کش را نیز انجام میدهد که در خیلی از موارد به عنوان یک قابلیت شناسایی خواهد شد. در این سناریوها و طبق RFC، باتوجه به اینکه هدف مصرف کننده، ایجاد Side-effect نبودهاست، هیچ مسئولیتی در قبال این تغییرات نخواهد داشت. لیست زیر شامل متدهای مختلف HTTP به همراه دو خصوصیت ذکر شده می باشد:
HTTP Method | Safe | Idempotent |
GET | Yes | Yes |
HEAD | Yes | Yes |
OPTIONS | Yes | Yes |
TRACE | Yes | Yes |
PUT | No | Yes |
DELETE | No | Yes |
POST | No | No |
PATCH | No | No |
راهکاری که عموما مورد استفاده قرار میگیرد، استفاده از یک شناسهی یکتا برای درخواست ارسالی و ارسال آن به سرور از طریق هدر HTTP می باشد. تصویر زیر از کتاب API Design Patterns، روش استفاده و مراحل جلوگیری از پردازش درخواست تکراری با شناسهای همسان را نشان میدهد:
در اینجا ابتدا مصرف کننده درخواستی با شناسه «۱» را برای پردازش به سرور ارسال میکند. سپس سرور که لیستی از شناسههای پردازش شدهی قبلی را نگهداری کردهاست، تشخیص میدهد که این درخواست قبلا دریافت شدهاست یا خیر. پس از آن، عملیات درخواستی انجام شده و شناسهی درخواست، به همراه پاسخ ارسالی به کلاینت، در فضایی ذخیره سازی میشود. در ادامه اگر همان درخواست مجددا به سمت سرور ارسال شود، بدون پردازش مجدد، پاسخ پردازش شدهی قبلی، به کلاینت تحویل داده می شود.
ممکن است پیادهسازیهای مختلفی را از این الگوی طراحی در اینترنت مشاهده کنید که به پیاده سازی یک Middleware بسنده کردهاند و صرفا بررسی این مورد که درخواست جاری قبلا دریافت شدهاست یا خیر را جواب می دهند که ناقص است. برای اینکه اطمینان حاصل کنیم درخواست مورد نظر دریافت و پردازش شدهاست، باید در منطق عملیات مورد نظر دست برده و تغییراتی را اعمال کنیم. برای این منظور فرض کنید در بستری هستیم که می توانیم از مزایای خصوصیات ACID دیتابیس رابطهای مانند SQLite استفاده کنیم. ایده به این شکل است که شناسه درخواست دریافتی را در تراکنش مشترک با عملیات اصلی ذخیره کنیم و در صورت بروز هر گونه خطا در اصل عملیات، کل تغییرات برگشت خورده و کلاینت امکان تلاش مجدد با شناسهی مورد نظر را داشته باشد. برای این منظور مدل زیر را در نظر بگیرید:
public class IdempotentId(string id, DateTime time) { public string Id { get; private init; } = id; public DateTime Time { get; private init; } = time; }
هدف از این موجودیت ثبت و نگهداری شناسههای درخواستهای دریافتی میباشد. در ادامه واسط IIdempotencyStorage را برای مدیریت نحوه ذخیره سازی و پاکسازی شناسههای دریافتی خواهیم داشت:
public interface IIdempotencyStorage { Task<bool> TryPersist(string idempotentId, CancellationToken cancellationToken); Task CleanupOutdated(CancellationToken cancellationToken); bool IsKnownException(Exception ex); }
در اینجا متد TryPersist سعی میکند با شناسه دریافتی یک رکورد را ثبت کند و اگر تکراری باشد، خروجی false خواهد داشت. متد CleanupOutdated برای پاکسازی شناسههایی که زمان مشخصی (مثلا ۱۲ ساعت) از دریافت آنها گذشته است، استفاده خواهد شد که توسط یک وظیفهی زمانبندی شده می تواند اجرا شود؛ به این صورت، امکان استفادهی مجدد از آن شناسهها برای کلاینتها مهیا خواهد شد. پیاده سازی واسط تعریف شده، به شکل زیر خواهد بود:
/// <summary> /// To prevent from race-condition, this default implementation relies on primary key constraints. /// </summary> file sealed class IdempotencyStorage( AppDbContext dbContext, TimeProvider dateTime, ILogger<IdempotencyStorage> logger) : IIdempotencyStorage { private const string ConstraintName = "PK_IdempotentId"; public Task CleanupOutdated(CancellationToken cancellationToken) { throw new NotImplementedException(); //TODO: cleanup the outdated ids based on configurable duration } public bool IsKnownException(Exception ex) { return ex is UniqueConstraintException e && e.ConstraintName.Contains(ConstraintName); } // To tackle race-condition issue, the implementation relies on storage capabilities, such as primary constraint for given IdempotentId. public async Task<bool> TryPersist(string idempotentId, CancellationToken cancellationToken) { try { dbContext.Add(new IdempotentId(idempotentId, dateTime.GetUtcNow().UtcDateTime)); await dbContext.SaveChangesAsync(cancellationToken); return true; } catch (UniqueConstraintException e) when (e.ConstraintName.Contains(ConstraintName)) { logger.LogInformation(e, "The given idempotentId [{IdempotentId}] already exists in the storage.", idempotentId); return false; } } }
همانطور که مشخص است در اینجا سعی شدهاست تا با شناسهی دریافتی، یک رکورد جدید ثبت شود که در صورت بروز خطای UniqueConstraint، خروجی با مقدار false را خروجی خواهد داد که می توان از آن نتیجه گرفت که این درخواست قبلا دریافت و پردازش شدهاست (در ادامه نحوهی استفاده از آن را خواهیم دید).
در این پیاده سازی از کتابخانه MediatR استفاده می کنیم؛ در همین راستا برای مدیریت تراکنش ها به صورت زیر می توان TransactionBehavior را پیاده سازی کرد:
internal sealed class TransactionBehavior<TRequest, TResponse>( AppDbContext dbContext, ILogger<TransactionBehavior<TRequest, TResponse>> logger) : IPipelineBehavior<TRequest, TResponse> where TRequest : IBaseCommand where TResponse : IErrorOr { public async Task<TResponse> Handle( TRequest command, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) { string commandName = typeof(TRequest).Name; await using var transaction = await dbContext.Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken); TResponse? result; try { logger.LogInformation("Begin transaction {TransactionId} for handling {CommandName} ({@Command})", transaction.TransactionId, commandName, command); result = await next(); if (result.IsError) { await transaction.RollbackAsync(cancellationToken); logger.LogInformation("Rollback transaction {TransactionId} for handling {CommandName} ({@Command}) due to failure result.", transaction.TransactionId, commandName, command); return result; } await transaction.CommitAsync(cancellationToken); logger.LogInformation("Commit transaction {TransactionId} for handling {CommandName} ({@Command})", transaction.TransactionId, commandName, command); } catch (Exception ex) { await transaction.RollbackAsync(cancellationToken); logger.LogError(ex, "An exception occured within transaction {TransactionId} for handling {CommandName} ({@Command})", transaction.TransactionId, commandName, command); throw; } return result; } }
در اینجا مستقیما AppDbContext تزریق شده و با استفاده از خصوصیت Database آن، کار مدیریت تراکنش انجام شدهاست. همچنین باتوجه به اینکه برای مدیریت خطاها از کتابخانهی ErrorOr استفاده می کنیم و خروجی همهی Command های سیستم، حتما یک وهله از کلاس ErrorOr است که واسط IErrorOr را پیاده سازی کردهاست، یک محدودیت روی تایپ جنریک اعمال کردیم که این رفتار، فقط برروی IBaseCommand ها اجرا شود. تعریف واسط IBaseCommand به شکل زیر میباشد:
/// <summary> /// This is marker interface which is used as a constraint of behaviors. /// </summary> public interface IBaseCommand { } public interface ICommand : IBaseCommand, IRequest<ErrorOr<Unit>> { } public interface ICommand<T> : IBaseCommand, IRequest<ErrorOr<T>> { } public interface ICommandHandler<in TCommand> : IRequestHandler<TCommand, ErrorOr<Unit>> where TCommand : ICommand { Task<ErrorOr<Unit>> IRequestHandler<TCommand, ErrorOr<Unit>>.Handle(TCommand request, CancellationToken cancellationToken) { return Handle(request, cancellationToken); } new Task<ErrorOr<Unit>> Handle(TCommand command, CancellationToken cancellationToken); } public interface ICommandHandler<in TCommand, T> : IRequestHandler<TCommand, ErrorOr<T>> where TCommand : ICommand<T> { Task<ErrorOr<T>> IRequestHandler<TCommand, ErrorOr<T>>.Handle(TCommand request, CancellationToken cancellationToken) { return Handle(request, cancellationToken); } new Task<ErrorOr<T>> Handle(TCommand command, CancellationToken cancellationToken); }
در ادامه برای پیادهسازی IdempotencyBehavior و محدود کردن آن، واسط IIdempotentCommand را به شکل زیر خواهیم داشت:
/// <summary> /// This is marker interface which is used as a constraint of behaviors. /// </summary> public interface IIdempotentCommand { string IdempotentId { get; } } public abstract class IdempotentCommand : ICommand, IIdempotentCommand { public string IdempotentId { get; init; } = string.Empty; } public abstract class IdempotentCommand<T> : ICommand<T>, IIdempotentCommand { public string IdempotentId { get; init; } = string.Empty; }
در اینجا یک پراپرتی، برای نگهداری شناسهی درخواست دریافتی با نام IdempotentId در نظر گرفته شدهاست. این پراپرتی باید از طریق مقداری که از هدر درخواست HTTP دریافت میکنیم مقداردهی شود. به عنوان مثال برای ثبت کاربر جدید، به شکل زیر باید عمل کرد:
[HttpPost] public async Task<ActionResult<long>> Register( [FromBody] RegisterUserCommand command, [FromIdempotencyToken] string idempotentId, CancellationToken cancellationToken) { command.IdempotentId = idempotentId; var result = await sender.Send(command, cancellationToken); return result.ToActionResult(); }
در اینجا از همان Command به عنوان DTO ورودی استفاده شدهاست که وابسته به سطح Backward compatibility مورد نیاز، می توان از DTO مجزایی هم استفاده کرد. سپس از طریق FromIdempotencyToken سفارشی، شناسهی درخواست، دریافت شده و بر روی command مورد نظر، تنظیم شدهاست.
رفتار سفارشی IdempotencyBehavior از ۲ بخش تشکیل شدهاست؛ در قسمت اول سعی می شود، قبل از اجرای هندلر مربوط به command مورد نظر، شناسهی دریافتی را در storage تعبیه شده ثبت کند:
internal sealed class IdempotencyBehavior<TRequest, TResponse>( IIdempotencyStorage storage, ILogger<IdempotencyBehavior<TRequest, TResponse>> logger) : IPipelineBehavior<TRequest, TResponse> where TRequest : IIdempotentCommand where TResponse : IErrorOr { public async Task<TResponse> Handle( TRequest command, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) { string commandName = typeof(TRequest).Name; if (string.IsNullOrWhiteSpace(command.IdempotentId)) { logger.LogWarning( "The given command [{CommandName}] ({@Command}) marked as idempotent but has empty IdempotentId", commandName, command); return await next(); } if (await storage.TryPersist(command.IdempotentId, cancellationToken) == false) { return (dynamic)Error.Conflict( $"The given command [{commandName}] with idempotent-id [{command.IdempotentId}] has already been received and processed."); } return await next(); } }
در اینجا IIdempotencyStorage تزریق شده و در صورتی که امکان ذخیره سازی وجود نداشته باشد، خطای Confilict که بهخطای 409 ترجمه خواهد شد، برگشت داده میشود. در غیر این صورت ادامهی عملیات اصلی باید اجرا شود. پس از آن اگر به هر دلیلی در زمان پردازش عملیات اصلی، درخواست همزمانی با همان شناسه، توسط سرور دریافت شده و پردازش شود، عملیات جاری با خطای UniqueConstaint برروی PK_IdempotentId در زمان نهایی سازی تراکنش جاری، مواجه خواهد شد. برای این منظور بخش دوم این رفتار به شکل زیر خواهد بود:
internal sealed class IdempotencyExceptionBehavior<TRequest, TResponse>(IIdempotencyStorage storage) : IPipelineBehavior<TRequest, TResponse> where TRequest : IIdempotentCommand where TResponse : IErrorOr { public async Task<TResponse> Handle( TRequest command, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(command.IdempotentId)) return await next(); string commandName = typeof(TRequest).Name; try { return await next(); } catch (Exception ex) when (storage.IsKnownException(ex)) { return (dynamic)Error.Conflict( $"The given command [{commandName}] with idempotent-id [{command.IdempotentId}] has already been received and processed."); } } }
در اینجا عملیات اصلی در بدنه try اجرا شده و در صورت بروز خطایی مرتبط با Idempotency، خروجی Confilict برگشت داده خواهد شد. باید توجه داشت که نحوه ثبت رفتارهای تعریف شده تا اینجا باید به ترتیب زیر انجام شود:
services.AddMediatR(config => { config.RegisterServicesFromAssemblyContaining(typeof(DependencyInjection)); // maintaining the order of below behaviors is crucial. config.AddOpenBehavior(typeof(LoggingBehavior<,>)); config.AddOpenBehavior(typeof(IdempotencyExceptionBehavior<,>)); config.AddOpenBehavior(typeof(TransactionBehavior<,>)); config.AddOpenBehavior(typeof(IdempotencyBehavior<,>)); });
به این ترتیب بدنه اصلی هندلرهای موجود در سیستم هیچ تغییری نخواهند داشت و به صورت ضمنی و انتخابی، امکان تعیین command هایی که نیاز است به صورت Idempotent اجرا شوند را خواهیم داشت.
https://www.mscharhag.com/p/rest-api-design
public class BaseEntity { public int Id { set; get; } public DateTime? DateAdded { set; get; } public DateTime? DateUpdated { set; get; } }
public class Person : BaseEntity { public string FirstName { get; set; } public string LastName { get; set; } }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Ignore<BaseEntity>();
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Ignore<BaseEntity>(); foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { var dateAddedProperty = entityType.FindProperty("DateAdded"); dateAddedProperty.ValueGenerated = ValueGenerated.OnAdd; dateAddedProperty.SqlServer().DefaultValueSql = "getdate()"; var dateUpdatedProperty = entityType.FindProperty("DateUpdated"); dateUpdatedProperty.ValueGenerated = ValueGenerated.OnAddOrUpdate; dateUpdatedProperty.SqlServer().ComputedColumnSql = "getdate()"; }
public class Blog { public int BlogId { get; set; } public string Url { get; set; } } public class RssBlog : Blog { public string RssUrl { get; set; } } class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasDiscriminator<string>("blog_type") .HasValue<Blog>("blog_base") .HasValue<RssBlog>("blog_rss"); } }
var blog1 = db.Blogs.OfType<RssBlog>().FirstOrDefault(x => x.RssUrl == "………");
public class Person { public int ID { get; set; } public string Firstname { get; set; } public string Lastname { get; set; } public string Email { get; set; } public string PhoneNumber { get; set; } public override string ToString() { return $"{ID}: {Firstname} {Lastname} - {Email} - {PhoneNumber}"; } }
Install-Package GenFu
var person = A.New<Person>(); Console.WriteLine(person);
18: Diedra Morgan - Zachary.Garcia@telus.net - (531) 273-9001
var people = A.ListOf<Person>(5); people.ForEach(Console.WriteLine);
97: Maria MacKenzie - Alexandra.Johnson@rogers.ca - (670) 787-3053 34: Alexander Scott - Isaiah.Price@gmail.com - (730) 645-4946 66: Kevin Perez - Gabrielle.Alexander@hotmail.com - (230) 758-8233 81: Maria Evans - Vanessa.Bell@rogers.ca - (508) 572-4343 79: Tyler Parker - Alyssa.Taylor@telus.net - (297) 357-7617
A.Configure<Person>().Fill(x => x.ID, 0); var people = A.ListOf<Person>(5); people.ForEach(Console.WriteLine);
0: Darron Gonzalez - Benjamin.Daeninck@hotmail.com - (405) 418-7783 0: Melanie Garcia - Jennifer.Griffin@microsoft.com - (711) 277-8826 0: James Hughes - Tristan.Ward@live.com - (734) 400-8322 0: Miranda Torres - Ross.Davis@rogers.ca - (495) 479-8147 0: David Hughes - Jillian.Alexander@live.com - (361) 617-6642
var i = 1; A.Configure<Person>() .Fill(c => c.ID, () => i++); var people = A.ListOf<Person>(5); people.ForEach(Console.WriteLine);
1: Paul Long - Carlos.Kelly@telus.net - (202) 573-6278 2: Jesse Iginla - Liberty.Moore@gmail.com - (589) 791-3606 3: Raymundo Price - Ang.Taylor@live.com - (336) 400-1601 4: Elizabeth Getzlaff - Leslie.Campbell@att.com - (662) 582-9010 5: Abigail Bailey - Tristan.Ross@live.com - (225) 661-7023
A.Configure<Person>() .Fill(c => c.ID, 0) .Fill(c => c.Email, c => $"{c.Firstname}.{c.Lastname}@gmail.com"); var people = A.ListOf<Person>(5); people.ForEach(Console.WriteLine);
0: Patrick Perry - Patrick.Perry@gmail.com - (796) 460-6576 0: Rebecca Main - Rebecca.Main@gmail.com - (757) 472-3332 0: Kimberly Carter - Kimberly.Carter@gmail.com - (436) 484-8273 0: Sara Lewis - Sara.Lewis@gmail.com - (424) 717-7682 0: Lauren Ross - Lauren.Ross@gmail.com - (277) 294-5776
A.Configure<Person>() .Fill(x => x.Firstname).AsPersonTitle(); var people = A.ListOf<Person>(5); people.ForEach(Console.WriteLine);
64: Miss. Ratzlaff - Bryce.Simmons@att.com - (386) 309-2414 7: Air Marshall Yarobi - Ariana.Russell@att.com - (459) 238-0717 96: Air Marshall Taylor - Luke.Olsen@gmail.com - (775) 401-5281 28: Doctor Cox - Leah.Diaz@att.com - (569) 464-7961 99: Master Phillips - Chloe.Scott@hotmail.com - (578) 221-9021
string, int?, byte[]
int, decimal, bool, DateTime
[Required] public string Url { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) .IsRequired(); }
public string FirstName { get; set; }
[StringLength(450)] public string FirstName { get; set; } [MaxLength(450)] public string LastName { get; set; } [MaxLength] public string Address { get; set; }
modelBuilder.Entity<Person>() .Property(x => x.Address) .HasMaxLength(450);
modelBuilder.Entity<Person>() .Property(x => x.Address) .HasColumnType("nvarchar(max)");
[Column(TypeName = "varchar")] [MaxLength] public string Address { get; set; }
Data type 'varchar' is not supported in this form. Either specify the length explicitly in the type name, for example as 'varchar(16)', or remove the data type and use APIs such as HasMaxLength to allow EF choose the data type.
[Column(TypeName = "varchar(max)")]
public class Person { public int Id { set; get; } public DateTime? DateAdded { set; get; } public DateTime? DateUpdated { set; get; } [StringLength(450)] public string FirstName { get; set; } [MaxLength(450)] public string LastName { get; set; } //[Column(TypeName = "varchar")] [MaxLength] public string Address { get; set; } //bit public bool IsActive { get; set; } //tiny Int public byte Age { get; set; } //small Int public short Short { get; set; } //int public int Int32 { get; set; } //Big int public long Long { get; set; } }
CREATE TABLE [dbo].[Persons]( [DateAdded] [datetime2](7) NULL, [DateUpdated] [datetime2](7) NULL,
[ConcurrencyCheck] public string Name { set; get; }
modelBuilder.Entity<Person>() .Property(p => p.Name) .IsConcurrencyToken();
[Timestamp] public byte[] Timestamp { get; set; }
modelBuilder.Entity<Blog>() .Property(p => p.Timestamp) .ValueGeneratedOnAddOrUpdate() .IsConcurrencyToken();
در مطالب بعدی در موردی مشخصههای مایکرو سرویسها صحبت خواهیم کرد.
HTTP Error 500.21 - Internal Server Error Handler "aspNetCore" has a bad module "AspNetCoreModule" in its module list
%PROGRAMFILES(x86)%\IIS Express\config\templates\PersonalWebServer\applicationhost.config
%PROGRAMFILES(x86)%\IIS Express\Asp.Net Core Module\V2
<add name="AspNetCoreModule" image="C:\Program Files\IIS Express\aspnetcore.dll" /> <add name="AspNetCoreModuleV2" image="C:\Program Files\IIS Express\Asp.Net Core Module\V2\aspnetcorev2.dll" />
<add name="AspNetCoreModule" image="C:\Program Files (x86)\IIS Express\aspnetcore.dll" /> <add name="AspNetCoreModuleV2" image="C:\Program Files (x86)\IIS Express\Asp.Net Core Module\V2\aspnetcorev2.dll" />
<section name="aspNetCore" overrideModeDefault="Allow" />
<add name="AspNetCoreModule" lockItem="true" /> <add name="AspNetCoreModuleV2" lockItem="true" />
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified"/> </handlers> <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/> </system.webServer> </configuration>
در طول این سری آموزشهای MDX (البته هنوز نمیدانم چند قسمت خواهد بود) تلاش خواهم کرد تمامی موارد موجود در MDXها را به طور کامل با شرح و توضیح مناسب پوشش دهم.
امیدوارم شما دوستان عزیز پس از مطالعهی این مجموعه مقالات به دانش کافی در خصوص MDX Queryها دست پیدا کنید.
در قسمت اول این آموزشها در نظر دارم در ابتدا مفاهیم اولیه OLAP و همچنین مفاهیم مورد نیاز در Multi Dimentional Data Base ها برای شما عزیزان توضیح دهم و در قسمتهای بعدی این مجموعه در خصوص MDX Queryها صحبت خواهم کرد.
انباره داده (Data Warehouse)
عملا یک یا چند پایگاه داده میباشد که اطلاعات تجمیع شده از دیگر پایگاههای داده را درخود نگه داری میکند. برای ارایه گزارشاتی که از پایگاه دادههای OLTP نمیتوانیم به راحتی بگیریم.
(OLTP (Online transaction processing
سیستم پردازش تراکنش برخط میباشند . که عملا همان سیستم هایی میباشند که در طول روز دارای تغییرات بسیار زیادی میباشند (مانند سیستمهای حسابداری، انبار داری و ... که در طول روز دایما دارای تغییرات در سطح داده میباشند.)
(OLAP (OnLine Analysis Processing
این سیستمها خدماتی در نقش تحلیلگر داده و تصمیم گیرنده ارائه میکند. چنین سیستمهایی میتوانند، داده را در قالبهای مختلف برای هماهنگ کردن نیازهای مختلف کاربران مختلف، سازماندهی کنند.
تفاوت انبار داده (Data Warehouse) و پایگاه داده(Data Base)
وظیفه اصلی سیستمهای پایگاهداده کاربردی OnLine ،پشتیبانی از تراکنشهای برخط و پردازش کوئری است. این سیستمها، سیستم پردازش تراکنش برخط(OLTP) نامیده میشوند و بیشتر عملیات روزمره یک سازمان را پوشش میدهند. از سوی دیگر انبارداده، خدماتی در نقش تحلیلگر داده و تصمیم گیرنده ارائه میکند. چنین سیستمهایی میتوانند داده را در قالبهای مختلف برای هماهنگ کردن نیازهای مختلف کاربران مختلف، سازماندهی و ارائه میکند. این سیستمها با نام سیستمهای پردازش تحلیلی برخط (OLAP) شناختهمیشوند.
موارد تفاوت انبار داده (Data Warehouse) و پایگاه داده(Data Base)
• از لحاظ مدلهای داده: پایگاههای داده برای مدل OLTP بهینه سازی شدهاست. که بر اساس مدل داده رابطهای امکان پردازش تعداد زیادی تراکنش همروند، که اغلب حاوی رکوردهای اندکی هستند را دارد. اما در انبارهای داده که برای پردازش تحلیلی بر خط، طراحی شدهاند امکان پردازش تعداد کمی کوئری پیچیده بر روی تعداد بسیار زیادی رکورد داده فراهم میشود. سرورهای OLAP میتوانند از دو نوع رابطهای (ROLAP) یا چندبعدی باشند (MOLAP).
• از لحاظ کاربران: کاربران پایگاهداده کارمندان دفتری و مسؤولان هستند در حالیکه کاربران انبارداده مدیران و تصمیمگیرندهها هستند.
• از لحاظ عملیات قابل اجرا بر روی آنها: عملیات انجام شده برروی پایگاههای داده عمدتا عملیات (Select/Insert/Update/Delete) میباشد ، در حالی که عملیات روی انبار داده عمدتا Select ها میباشند.
• از لحاظ مقدار دادهها: مقدار دادههای یک پایگاهداده در حدود چند مگابایت تا چند گیگابایت است در حالی که این مقدار در انبار داده در حدود چند گیگابایت تا چند ترابایت است.
• از لحاظ زمان پرس و جو : به طور کلی سرعت پرس و جو ها روی انبارهی داده بسیار بالاتر از کوئری مشابه آن روی پایگاه داده میباشد.
• پاکسازی داده (Data Cleansing)
پاکسازی دادهها عبارت است از شناسایی و حذف خطاها و ناسازگاریهای داده ای به منظور دستیابی به دادههایی با کیفیت بالاتر.
اگر دادهها از منابع یکسان مثل فایلها یا پایگاههای داده ای گرفته شوند خطاهایی از قبیل اشتباهات تایپی، دادههای نادرست و فیلدهای بدون مقدار را خواهیم داشت و چنانچه دادهها از منابع مختلف مثل پایگاه دادههای مختلف یا سیستم اطلاعاتی مبتنی بر وب گرفته شوند .با توجه به نمایشهای دادهای مختلف خطاها بیشتر بوده و پاکسازی دادهها اهمیت بیشتری پیدا خواهد کرد. برای دستیابی به دادههای دقیق و سازگار، بایستی دادهها را یکپارچه نموده و تکرارهای آنها را حذف نمود.
وجود خطاهای نویزی، ناسازگاری در دادههای انبار داده و ناقص بودن دادهها امری طبیعی است. فیلدهای یک جدول ممکن است خالی باشند و یا دارای دادههای خطا دار و ناسازگار باشند. برای هر کدام از این حالتها روشهایی جهت پاکسازی و اصلاح دادهها ارایه میشود.
در این بخش عملیات مختلفی برای پاکسازی دادهها قابل انجام است:
• نادیده گرفتن تاپلهای نادرست
• پرکردن فیلدهای نادرست به صورت دستی
• پرکردن فیلدهای نادرست با یک مقدار مشخص
• پرکردن فیلدها با توجه به نوع فیلد و دادهها ی موجود
• پرکردن فیلدها با نزدیکترین مقدار ممکن (مثلا میانگین فیلد تاپلهای دیگر میتواند به عنوان یک مقدار مناسب در نظر گرفته شود)
• یکپارچهسازی (Integration)
• تبدیل دادهها(Data Transformation)• شناسایی فیلدهای یکسان: فیلدهای یکسان که در جدولها ی مختلف دارای نامهای مختلف میباشند.
• شناسایی افزونگیها ی موجود در دادهها ی ورودی: دادههای ورودی گاهی دارای افزونگی است. مثلا بخشی از رکورد در جداول مختلف وجود دارد.
• مشخص کردن برخوردهای داده ای: مثالی از برخوردهای داده ای یکسان نبودن واحدهای نمایش داده ای است. مثلا فیلد وزن در یک جدول بر حسب کیلوگرم و در جدولی دیگر بر حسب گرم ذخیره شده است.
در این فاز، دادههای ورودی طی مراحل زیر به شکلی که مناسب عمل داده کاوی باشند، در میآیند:
• از بین بردن نویز داده¬ها(Smoothing)
• تجمیع داده¬ها(Aggregation)
• کلی¬سازی(Generalization)
• نرمال¬سازی(Normalization)
• افزودن فیلدهای جدید
• استفاده از مقادیر مجاور برای تعیین یک مقدار مناسب برای فیلدهای دارای نویز
• دسته بندی دادههای موجود و مقداردهی فیلد دارای داده نویزی با استفاده از دسته نزدیکتر
• ترکیب روشهای فوق با ملاحظات انسانی، در این روش، اصلاح مقادیر نویزی با استفاده از یکی از روشهای فوق انجام میگیرد اما افرادی برای بررسی و اصلاح نیز وجود دارند
5. افزودن فیلدهای جدید: گاهی اوقات برای سهولت عمل داده کاوی میتوان فیلدهایی به مجموعه فیلدهای موجود اضافه کرد. مثلا میتوان فیلد میانگین حقوق کارمندان یک شعبه را به مجموعه فیلدهای موجود اضافه نمود.
در این مرحله، عملیات کاهش دادهها انجام میگیرد که شامل تکنیکهایی برای نمایش کمینه اطلاعات موجود است
. این فاز از سه بخش تشکیل میشود:
• کاهش دامنه و بعد: فیلدهای نامربوط، نامناسب و تکراری حذف میشوند. برای تشخیص فیلدهای اضافی، روشهای آماری و تجربی وجود دارند ؛ یعنی با اعمال الگوریتمهای آماری و یا تجربی بر روی دادههای موجود در یک بازه زمانی مشخص، به این نتیجه میرسیم که فیلد یا فیلدهای خاصی کاربردی در انباره داده ای و داده کاوی نداشته و آنها را حذف میکنیم.
• فشرده سازی داده ها: از تکنیکهای فشرده سازی برای کاهش اندازه دادهها استفاده میشود.
• کدکردن داده ها: دادهها در صورت امکان با پارامترها و اطلاعات کوچکتر جایگزین میشوند.
مدل دادهای رابطهای (Relational) وچند بعدی (Multidimensional) :
1. مدل داده رابطهای (Relational data modeling) بر اساس دو مفهوم اساسی موجودیت (entity) و رابطه (relation) بنا نهاده شده است. از این رو آن را با نام مدل ER نیز میشناسند.
• موجودیت (entity) : نمایانگر همه چیزهایی که در پایگاه داده وجود خارجی دارند یا به تصور در میآیند. پدیدهها دارای مشخصاتی هستندکه به آنها صفت (attribute) گفته میشود.
• رابطه (relation) : پدیدهها را به هم میپیوندد و چگونگی در ارتباط قرار گرفتن آنها با یکدیگر را مشخص میکند.
2. مدل داده چندبعدی ( Multidimensional modeling ) یا MD بر پایه دو ساختار جدولی اصلی بنا نهاده شده است:
این ساختار امکان داشتن یک نگرش مدیریتی و تصمیمگیری به دادههای موجود در پایگاه داده را تسهیل میکند.
• جدول حقایق (Fact Table)
• جداول ابعاد (Dimension Table)
جدول حقایق : قلب حجم دادهای ما را تشکیل میدهد و شامل دو سری فیلد است : کلیدهای خارجی به ابعاد و شاخصها (Measure).
شاخصها (Measure) : معیارهایی هستند که بر روی آنها تحلیل انجام میگیرد و درون جدول حقایق قرار دارند. شاخصها قبل از شکلگیری انبار داده توسط مدیران و تحلیلگران به دقت مشخص میشوند. چون در مرحله کار با انبار اطلاعات اساسی هر تحلیل بر اساس همین شاخصها شکل میگیرد. شاخصها تقریباً همیشه مقادیر عددی را شامل میشوند. مثلا برای یک فروشگاه زنجیرهای این شاخصها میتوانند واحدهای فروختهشده کالاها و مبلغ فروش به تومان باشند.
بعد (Dimension) : هر موجودیت در این مدل میتواند با یک بعد تعریف شود. ولی بعدها با موجودیتهای مدل ER متفاوتند زیرا آنها سازمان شاخصها را تعیین میکنند. علاوه بر این دارای یک ساختار سلسله مراتبی هستند و به طور کلی برای حمایت از سیستمهای تصمیم گیری سازماندهی شدهاند.
اجزای بعدها member نام دارند و تقریباٌ همه بعدها، memberهای خود را در یک یا چند سطح سلسله مراتبی (hierarchies) سازماندهی مینمایند، که این سلسله مراتب نمایانگر مسیر تجمیع (integration) و ارتباط بین سطوح پایینتر (مثل روز) و سطوح بالاتر (مثل ماه و سال) است. وقتی یک دسته از memberهای خاص با هم مفهوم جدیدی را ایجاد میکنند، به آنها یک سطح (Level) میگوییم. ( مثلاٌ هر سی روز را ماه میگوییم. در این حالت ماه یک سطح است. )
حجمهای دادهای (Data Cube)
حجمهای دادهای یا Cube از ارتباط تعدادی بعد با تعدادی شاخص تعریف میشود. ترکیب memberهای هر بعد از حجم دادهای فضای منطقی را تعریف میکند که در آن مقادیر شاخصها ظاهر میشوند. هر بخش مجزا که شامل یکی از memberهای بعد در حجم دادهای است ، سلول (cell) نامیدهمیشود. سلولها شاخصهای مربوط به تجمیعهای مختلف را در خود نگهداری مینمایند. در واقع مقادیر مربوط به حقایق (Fact) که در جدول حقایق (Fact) تعریف میشوند در حجم دادهای (Data Cube) در سلولها (Cell) نمایان میگردند.
شماهای دادهای (Data Schema) : سه نوع Schema در طراحی Data Warehouse وجود دارد
1. Stare
2. Snowflake
3. Galaxy1. شمای ستارهای (Star Schema) : متداولترین شما، همین شمایستارهای است. که در آن انبارداده با استفاده از اجزای زیر تعریف میشود:
• مجموعهای از جدولهای کمکی کوچکتر به نام جدول بعد ، که به ازای هر بعد یکی از این جداول موجود خواهد بود.
• شکل این شما به صورت یک ستاره است که جدول حقایق در مرکز آن قرار گرفته و هر یک از جداول بعد به وسیله شعاعهایی به آن مربوط هستند.
مشکل این مدل احتمال پیشامد افزونگی در آن است.
2. شمای دانهبرفی ( Snowflake Schema ) : در واقع شمای دانهبرفی، نوعی از شمای ستارهای است که در آن بعضی از جداول بعد نرمال شدهاند. و به همین خاطر دارای تقسیمات بیشتری به شکل جداول اضافی میباشد که از جداول بعد جدا شدهاند.
تفاوت این دو شما در این است که جداول شمای دانه برف نرمال هستند و افزونگی در آنها کاهش یافته است. که این برای کار کردن با دادهها و از لحاظ فضای ذخیرهسازی مفید است. ولی در عوض کارایی را پایین میآورد، زیرا در محاسبه کوئریها به joinهای بیشتری نیاز داریم.
3. شمای کهکشانی (galaxy schema) : در کاربردهای پیچیده برای به اشتراک گذاشتن ابعاد نیاز به جداول حقایق چندگانه احساس میشود که یک یا چند جدول بعد را در بین خود به اشتراک میگذارند. این نوع شما به صورت مجموعهای از شماهای ستارهای است و به همین دلیل شمای کهکشان یا شمای منظومهای نامیدهمیشود. این شما به ما این امکان را میدهد که جداول بعد بین جداول حقایق مختلف به اشتراک گذاشته شوند.
عملیات بر روی حجمهای دادهای :
• Roll Up (یا Drill-up) : با بالا رفتن در ساختار سلسله مراتبی مفهومی یک حجم دادهای، یا با کاهش دادن بعد، یک مجموعه با جزئیات کمتر (خلاصه شده) ایجاد مینماید. بالا رفتن در ساختار سلسله مراتبی به معنای حذف قسمتی از جزئیات است. برای مثال اگر قبلاٌ بعد مکان بر حسب شهر بوده آن را با بالا رفتن در ساختار سلسله مراتبی بر حسب کشور درمیآوریم. ولی وقتی با کاهش دادن بعد سروکار داریم منظور حذف یکی از ابعاد و جایگزین کردن مقادیر کل است. در واقع همان عمل تجمیع (aggregation) است.
• Drill Down : بر عکس عملRoll-up است و از موقعیتی با جزئیات دادهای کم به جزئیات زیاد میرود. این کار با پایین آمدن در ساختار سلسله مراتبی( به سمت جزئیات بیشتر) یا با ایجاد ابعاد اضافی انجام میگیرد.
نمونهای از عملیات Drill Down و Roll Up
• Slice : با انتخاب و اعمال شرط بر روی یکی از ابعاد یک subcube به شکل یک برش دو بعدی ایجاد میکند. در واقع همان عمل انتخاب (select) است.
• Dice : با انتخاب قسمتی از ساختار سلسله مراتبی بر روی دو یا چند بعد یک subcube ایجاد مینماید.
نمونهای از عملیات Dice و Slice
• Pivot (یا Rotate) : این عملیات بردارهای بعد را در ظاهر میچرخاند.
نمونهای از عملیات pivot
• Drill-across : نتیجه اجرای کوئریهایی که نتیجه اجرای آنها حجمهای دادهایهای مرکب با بیش از یک fact-table است.
• Ranking : سلولهایی را باز میگرداند که در بالا یا پایین شرط خاصی واقع هستند. مثلاٌ ده محصولی که بهترین فروش را داشتهاند.
سرورهای OLAP :
در تکنولوژیOALP دادهها به دو صورت چندبعدی (Multidimensional OLAP) (MOLAP) و رابطهای (Relational OLAP) (ROLAP) ذخیره میشوند. OLAP پیوندی(HOLAP) تکنولوژیی است که دو نوع قبل را با هم ترکیب میکند.
MOLAP : روشی است که معمولاٌ برای تحلیلهای OLAP در تجارت مورد استفاده قرار میگیرد. در MOLAP، دادهها با ساختار یک حجم دادهای ( Data Cube ) چند بعدی ذخیره میشوند. ذخیرهسازی در پایگاهدادههای رابطهای انجام نمیگیرد، بلکه با یک فرمت خاص انجام میشود. اغلب محصولات موفق MOLAP از یک روش چندبعدی استفاده مینمایند که در آن یک سری حجمهای دادهای کوچک، انبوه و از پیش محاسبهشده، یک حجم دادهای بزرگ (hypercube ) را میسازند.
علاوه براین MOLAP به شما امکان میدهد دادههای دیدهای (View) تحلیلگران را دسته بندی کنید، که این در حذف اشتباهات و برخورد با ترجمههای پرغلط کمک بزرگی است.
گذشته از همه اینها از آنجا که دادهها به طور فیزیکی در حجمهای دادهای بزرگ چندبعدی ذخیره میشوند، سرعت انجام فعالیتها بسیار زیاد خواهد بود.
از آنجا که یک کپی از دادههای منبع در کامپیوتر Analysis server ذخیرهمیشود، کوئریها میتوانند بدون مراجعه به منابع مجدداً محاسبه شوند. کامپیوتر Analysis server ممکن است کامپیوترسرور که تقسیم بندیها در آن انجام شده یا کامپیوتر دیگری باشد. این امر بستگی به این دارد که تقسیمبندیها در کجا تعریف شدهاند. حتی اگر پاسخ کوئریها از روی تقسیمات تجمیع (integration) شده قابل دستیابی نباشند، MOLAP سریعترین پاسخ را فراهم میکند. سرعت انجام این کار به طراحی و درصد تجمیع تقسیمبندیها بستگی دارد.
مزایا : کارایی عالی- حجمهای دادهای MOLAP برای بازیابی سریع دادهها ساخته شدهاند و در فعالیتهای slice و dice به صورت بهینه پاسخ میدهند. ترکیب سادگی و سرعت مزیت اصلی MOLAP است.
در ضمنMOLAP قابلیت محاسبه محاسبات پیچیده را فراهم میکند. همه محاسبات از پیش وقتی که حجمهای دادهای ساخته میشود، ایجاد میشوند. بنابراین نه تنها محاسبات پیچیده انجام شدنی هستند بلکه بسیار سریع هم پاسخ میدهند.
معایب : عیب این روش این است که تنها برای دادههایی با مقدار محدود کارکرد خوبی دارد. از آنجا که همه محاسبات زمانی که حجمهای دادهای ساخته میشود، محاسبه میگردند، امکان این که حجمهای دادهای مقدار زیادی از دادهها را در خود جای دهد، وجود ندارد. ولی این به این معنا نیست که دادههای حجمهای دادهای نمیتوانند از مقدار زیادی داده مشتق شده باشند. دادهها میتوانند از مقدار زیادی داده مشتق شدهباشند. اما در این صورت، فقط اطلاعات level خلاصه (level ای که دارای کمترین جزئیات است یعنی سطوح بالاتر) میتوانند در حجمهای دادهای موجود باشند.
ROLAP : محدودیت MOLAP در حجم دادههای قابل پرسوجو و نیاز به روشی که از دادههای ذخیرهشده به روش رابطهای حمایت کند، موجب پیشرفت ROLAP شد.
مبنای این روش کارکردن با دادههایی که در پایگاهدادههای رابطهای ذخیرهشدهاند، برای انجام اعمال slicing و dicing معمولی است. با استفاده از این مدل ذخیرهسازی میتوان دادهها را بدون ایجاد واقعی تجمیع در پایگاهدادههای رابطهای به هم مربوط کرد.
مزایا : با این روش میتوان به حجم زیادی از دادهها را رسیدگی کرد. محدودیت حجم داده در تکنولوژی ROLAP مربوط به محدودیت حجم دادههای قابل ذخیرهسازی در پایگاهدادههای رابطهای است. به بیان دیگر، خود ROLAP هیچ محدودیتی بر روی حجم دادهها اعمال نمیکند.
معایب : ممکن است کارایی پایین بیاید. زیرا هر گزارش ROLAP در واقع یک کواِری SQL (یا چند کواِری SQL )در پایگاه دادههای رابطهای است و اگر حجم دادهها زیاد باشد ممکن است زمان پاسخ کواِری طولانی شود. در مجموع ROLAP سنگین است، نگهداری آن سخت است و کند هم هست. بخصوص زمانی که نیاز به آدرس دهی جدولهای ذخیره شده در سیستم چند بعدی داریم.
این محدودیت ناشی از عملکرد SQL است. زیرا تکنولوژی ROLAP بر پایه عبارات مولد SQL برای پرسش و پاسخ بر روی پایگاه داده رابطهای است و عبارات SQL به همه نیازها پاسخ نمیدهند (مثلاٌ محاسبه حسابهای پیچیده در SQL مشکل است)، بنابراین فعالیتهای ROLAP به آن چه SQL قادر به انجام آن است محدود میگردد.
تفاوت ROALP و MOLAP : تفاوت اصلی این دو در معماری آنها است. محصولات MOLAP دادههای مورد نیاز را در یک حافظه نهان (cache) مخصوص میگذارد. ولی ROLAP تحلیلهای خود را بدون استفاده از یک حافظه میانی انجام میدهد، بدون آن که از یک مرحله میانی برای گذاشتن دادهها در یک سرور خاص استفاده کند.
با توجه به کند بودن ROLAP در مقایسه باMOLAP ، باید توجه داشت که کاربرد این روش بیشتر در پایگاه دادههای بسیار بزرگی است که گاهگاهی پرس و جویی بر روی آنها شکل میگیرد، مثل دادههای تاریخی و کمتر جدید سالهای گذشته.
نکته: اگر از Analysis Services که به وسیله Microsoft OLE DB Provider مهیا شده استفاده میکنید، تجمیعها نمیتوانند برای تقسیمبندی از روش ROLAP استفاده نمایند.
HOLAP : با توجه به نیاز رو به رشدی که برای کارکردن با دادههای بلادرنگ (real time) در بخشهای مختلف در صنعت و تجارت احساس میشود، مدیران تجاری انتظار دارند بتوانند با دامنه وسیعی از اطلاعات که فوراً و بدون حتی لحظهای تأخیر در دسترس باشند، کار کنند. در حال حاضر شبکه اینترنت و سایر کاربردها یی که به دادههایی از منابع مختلف مراجعه دارند و نیاز به فعالیت با یک سیستم بلادرنگ هم دارند، همگی از سیستم HOLAP بهره میگیرند.
named set :
Named Set مجموعهای از memberهای بعد یا مجموعهای از عبارات است که برای استفاده مجدد ایجاد میشود.
Calculated member
Calculated Memberها memberهایی هستند که بر اساس دادهها نیستند بلکه بر اساس عبارات ارزیابی MDX هستند. آنها دقیقاَ به سبک سایر memberهای معمولی هستند. MDX یک مجموعه قوی از عملیاتی را تامین میکند که میتوانند برای ساختCalculated Memberها مورد استفاده قرار گیرند به طوری که به شما امکان داشتن انعطاف زیاد در کار کردن با دادههای چند بعدی را بدهد.
امیدوارم در این قسمت با مفاهیم نخستین OLAP آشنا شده باشید.
تلاش خواهم کرد در قسمت بعدی در خصوص نصب SQL Server Analysis Services و نصب پایگاه دادهی Adventure Work DW 2008 شرح کاملی را ارایه کنم.