مطالب
Fluent Linq to Sql

نگارش بعدی یا چهارم entity framework چیزی است شبیه به Fluent NHibernate . یعنی اگر مقاله‌ای را در این زمینه مطالعه کنید و عنوان آن حذف شود، نمی‌توان تشخیص داد که این مقاله مربوط به entity framework است یا Fluent NHibernate. هر چند entity framework حداقل دو نگارش دیگر لازم دارد تا NHibernate را کاملا پشت سر بگذارد.
از آن طرف محبوبیت Linq to SQL هم هنوز پابرجا است و برای مثال سایت پر ترافیکی مثل stack overflow از آن استفاده می‌کند و بسیار هم موفق بوده و کارش را به خوبی انجام می‌دهد.
پروژه مکملی به نام Fluent Linq to Sql با الهام گیری از Fluent NHibernate در سایت codeplex موجود است که این نوع نگاشت‌ها را برای Linq to Sql نیز میسر می‌سازد. به این صورت دیگر نیازی به استفاده از attributes و یا فایل‌های xml نگاشت‌های Linq to Sql نخواهد بود. همچنین مدل کاری اول کد بعد دیتابیس نیز به این صورت محقق می‌شود.



نظرات مطالب
بازنویسی سطح دوم کش برای Entity framework 6
با تشکر.
آیا این کتابخانه با کتابخانه EntityFramework.Extended سازگاری دارد؟
چون قصد  دارم از این دو  کنار هم استفاده کنم. یه کاری شبیه به کار زیر
  public IList<string> GetUserPermissions(int[] roleIds, int userId)
        {
            var permissionsOfRoles = (from p in _permissions
                                      from r in p.ApplicationRoles
                                      where roleIds.Contains(r.Id)
                                      select p.Name).Cacheable().Future();

            var permissionsOfUser = (from p in _permissions
                                     from r in p.AssignedUsers
                                     where userId == r.Id
                                     select p.Name).Cacheable().Future().ToList();
            return permissionsOfUser.Union(permissionsOfRoles).ToList();
        }
ولی با خطای 
The source query must be of type ObjectQuery or DbQuery.
Parameter name: source
[ArgumentException: The source query must be of type ObjectQuery or DbQuery.
Parameter name: source]
   EntityFramework.Extensions.FutureExtensions.Future(IQueryable`1 source) +249
مواجه شدم. که مشخص است برای اعمال متد Future باید مبدا از نوع IQueryable باشد.آیا اعمال متد AsQueryable در روند کار کتابخانه EFSecondLevelCache مشکلی ایجاد نخواهد شد؟
اشتراک‌ها
سری ساخت یک برنامه‌ی #C از آغاز تا پایان

C# Application From Start to Finish: Tournament Tracker Course - YouTube
28 videos, 192,192 views, Last updated on Jan 13, 2019
Follow along in this free course as I show you how to create an application in C# from idea through the finished product. Everything is shown on screen and in great detail. Learn how to use SQL databases, CSV text files, custom events, Linq, Lambda expressions, emailing, and more. Everything you learn will be in context of a real application.

سری ساخت یک برنامه‌ی #C از آغاز تا پایان
مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت هشتم - ساده سازی معرفی سرویس‌ها توسط Scrutor
قابلیت‌های قرار گرفته‌ی در اسمبلی Microsoft.Extensions.DependencyInjection که پایه‌ی تزریق وابستگی‌های برنامه‌های مبتنی بر NET Core. را ارائه می‌دهد، برای پیاده سازی اکثر پروژه‌ها کافی است. اما اگر از نگارش‌های پیشین ASP.NET MVC به ASP.NET Core مهاجرت کرده باشید، حتما با قابلیت‌های ویژه‌ی اسکن اسمبلی‌های موجود در IoC Containers ثالث، جهت ساده سازی معرفی سرویس‌های برنامه به سیستم تزریق وابستگی‌ها، آشنایی دارید. برای مثال StructureMap قابلیت اسکن اسمبلی‌های موجود در برنامه و معرفی اینترفیس‌ها و سرویس‌های موجود در آن‌را به Container خود دارد:
var container = new Container(x =>
            {
                x.Scan(scanner =>
                {
                    scanner.AssemblyContainingType<IOrderHandler>();
                    // connects `IAccounting` to `Accounting` and `ISales` to `Sales` automatically.
                    scanner.WithDefaultConventions();
                });
            });
و یا AutoFac نیز به همین صورت:
builder.RegisterAssemblyTypes(myAssembly)
    .Where(t => t.IsAssignableTo<IMyInterface>())
    .AsImplementedInterfaces();
البته می‌توان مجددا به تمام این قابلیت‌ها رسید؛ به شرطی‌که سیستم تزریق وابستگی‌های پایه‌ی NET Core. را با یکی از IoC Containers ثالث به طور کامل تعویض کنیم. اگر قصد چنین تعویض پایه‌ای را ندارید و هنوز قصد دارید از همان Microsoft.Extensions.DependencyInjection استفاده کنید، اما تعدادی متد الحاقی جدید تعریف شده‌ی بر فراز آن، کار اسکن کردن اسمبلی‌ها را مانند قبل انجام دهند، می‌توان از کتابخانه‌ی کمکی Scrutor استفاده کرد. این کتابخانه، جایگزین سیستم تزریق وابستگی‌های توکار برنامه‌های NET Core. نیست؛ بلکه صرفا مکمل آن است.


دریافت و نصب کتابخانه‌ی کمکی Scrutor

کتابخانه‌ی کمکی Scrutor سورس باز بوده و بسته‌ی NuGet آن توسط یکی از دستورات زیر به پروژه افزوده می‌شود:
> Install-Package Scrutor
> dotnet add package Scrutor
و یا به صورت مدخلی جدید در فایل csproj:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <PackageReference Include="Scrutor" Version="3.0.2" />
  </ItemGroup>
</Project>


ثبت و معرفی ساده‌تر سرویس‌ها بر اساس قواعد نامگذاری آن‌ها توسط Scrutor

فرض کنید تعدادی سرویس را به صورت زیر تعریف کرده‌اید:
namespace CoreIocServices
{
    public interface IFoo
    {
        void Run();
    }

    public class Foo : IFoo
    {
        public void Run()
        {
            throw new System.NotImplementedException();
        }
    }

    public interface IBar
    {
        void Add();
    }

    public class Bar : IBar
    {
        public void Add()
        {
            throw new System.NotImplementedException();
        }
    }


    public interface IBaz
    {
        void Stop();
    }

    public class Baz : IBaz
    {
        public void Stop()
        {
            throw new System.NotImplementedException();
        }
    }
}
روش متداول معرفی آن‌ها به IoC Container برنامه به صورت زیر است:
services.AddScoped<IFoo, Foo>();
services.AddScoped<IBar, Bar>();
services.AddScoped<IBaz, Baz>();
و هرچقدر تعداد سرویس‌های برنامه بیشتر شود، سطرهای فوق نیز بیشتر خواهند شد.
در اینجا در حین تعریف سرویس‌های فوق این روش نامگذاری رعایت شده‌است: هر اینترفیس، نامش یک I بیشتر از نام کلاس مشتق شده‌ی از آن دارد؛ مانند اینترفیس IFoo و کلاس Foo. کتابخانه‌ی StructureMap که در ابتدای بحث معرفی شد، کار اسکن و اتصال یک چنین سرویس‌هایی را با تعریف scanner.WithDefaultConventions انجام می‌دهد. معادل آن با Scrutor به صورت زیر است:
namespace CoreIocSample02
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.Scan(scan =>
                //scan.FromCallingAssembly()
                scan.FromAssemblyOf<IFoo>()
                    .AddClasses()
                    .AsMatchingInterface()
                    .WithScopedLifetime());
تعریف فوق به این معنا است:
- scan.FromAssemblyOf کار اسکن اسمبلی را انجام می‌دهد که نوع IFoo در آن قرار دارد. اگر از scan.FromCallingAssembly استفاده کنیم، به این معنا است که کار اسکن را دقیقا از همین اسمبلی فراخوان کدهای جاری، شروع کن. اما چون IFoo تعریف شده، در یک پروژه و اسمبلی دیگر قرار دارد، به همین جهت نیاز به ذکر صریح اسمبلی آن نیز هست.
- AddClasses یعنی تمام کلاس‌های public, non-abstract را به لیست services اضافه کن.
- AsMatchingInterface یعنی بر اساس قرارداد نامگذاری IClassName و ClassName، اتصالات سرویس‌ها را انجام بده.
بجای آن می‌توان از AsImplementedInterfaces نیز استفاده کرد. این حالت برای زمانی مناسب است که یک کلاس، چندین اینترفیس را پیاده سازی کند (مثلا کلاس TestService اینترفیس‌های ITestService و IService را پیاده سازی کرده باشد) و علاقمند باشید به ازای هر اینترفیس، یکبار سرویس آن نیز ثبت شود؛ کاری مانند تنظیمات زیر:
services.AddScoped<ITestService, TestService>();
services.AddScoped<IService, TestService>();
یا حتی می‌توان از متد ()<As<T نیز استفاده کرد. در اینجا به Scrutor گفته می‌شود که تمام کلاس‌های یافت شده را بر اساس نوع سرویس T ثبت و معرفی کن. البته اگر کلاسی نتواند نوع اینترفیس T را پیاده سازی کند، در زمان اجرا با استثناء مواجه خواهید شد.
- WithScopedLifetime نیز طول عمر این سرویس‌های اضافه شده را مشخص می‌کند. در اینجا می‌توان WithTransientLifetime و WithSingletonLifetime را نیز ذکر کرد.

بنابراین همانطور که ملاحظه می‌کنید، هنوز هم همان سیستم Microsoft.Extensions.DependencyInjection برقرار است؛ اما با وجود متد الحاقی جدید Scan، کار تعاریف سرویس‌های برنامه به شدت ساده می‌شود.


کار با وهله‌های کلاس‌های سرویس‌ها بجای اینترفیس‌های آن توسط Scrutor

می‌خواهیم مثال سوم قسمت ششم «چگونه بجای اینترفیس‌ها، یک وهله از کلاسی مشخص را از سیستم تزریق وابستگی‌ها درخواست کنیم؟» را توسط Scrutor پیاده سازی کنیم:
namespace CoreIocServices
{
    public interface IService { }
    public class Service1 : IService { }
    public class Service2 : IService { }
    public class Service : IService { }
}
در حالت متداول آن می‌توان از روش زیر نیز استفاده کرد:
services.AddTransient<Service1>();
services.AddTransient<Service2>();
services.AddTransient<Service>();
که با افزایش تعداد کلاس‌های سرویس برنامه به همین نحو نیز افزایش خواهند یافت. معادل این تنظیمات با Scrutor به صورت زیر است:
namespace CoreIocSample02
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.Scan(scan =>
              //scan.FromCallingAssembly()
              scan.FromAssemblyOf<IService>()
                  .AddClasses()
                  .AsSelf()
                  .WithTransientLifetime());
در اینجا اسمبلی حاوی IService اسکن خواهد شد و سپس تمام کلاس‌های public, non-abstract آن AsSelf (ثبت پیاده سازی خود کلاس به عنوان سرویس) با طول عمر Transient به لیست services اضافه می‌شوند و یا اگر صرفا تعدادی سرویس مشخص مد نظر بود می‌توان به صورت زیر عمل کرد:
services.Scan(scan =>
               scan.AddTypes(new[] { typeof(Service1), typeof(Service2) })
                   .AsSelf()
                   .WithTransientLifetime());
متدهایی که در Scrutor، یک پیاده سازی را به عنوان سرویس معرفی می‌کنند، شامل این موارد هستند:
AsSelf: معادل ()<services.AddTransient<TestService است. در این حالت کلاس‌هایی که اینترفیسی را پیاده سازی نمی‌کنند و یا در کل مایل هستید که از طریق تزریق وابستگی‌ها در دسترس باشند، می‌توان توسط متد AsSelf به سیستم معرفی کرد.
AsSelfWithInterfaces: معادل تنظیمات زیر است:
services.AddSingleton<TestService>();
services.AddSingleton<ITestService>(x => x.GetRequiredService<TestService>());
services.AddSingleton<IService>(x => x.GetRequiredService<TestService>());
فرض کنید کلاس TestService اینترفیس‌های ITestService و IService را پیاده سازی کرده باشد. با استفاده از AsSelfWithInterfaces، یکبار پیاده سازی خود سرویس به سیستم معرفی می‌شود، سپس به ازای هر اینترفیس، از همان وهله‌ی TestService برای وهله سازی سرویس‌های ITestService و IService نیز استفاده می‌شود.


روش‌های متفاوت اسکن اسمبلی‌ها در Scrutor

Scrutor به همراه روش‌های متعددی برای تعریف اسمبلی یا اسمبلی‌هایی است که باید اسکن شوند و نمونه‌ای از آن‌را با FromAssemblyOf بررسی کردیم:
services.Scan(scan =>
              //scan.FromCallingAssembly()
              scan.FromAssemblyOf<IService>()
سایر موارد آن به شرح زیر هستند:
الف) FromAssemblyOf<>, FromAssembliesOf : اسمبلی یا اسمبلی‌هایی که نوع یا نوع‌های تعیین شده را به همراه دارند، اسکن می‌کند.
ب) FromCallingAssembly, FromExecutingAssembly, FromEntryAssembly کار اسکن اسمبلی‌های فراخوان، اسمبلی که هم اکنون در حال اجرا است و اسمبلی آغازین برنامه را انجام می‌دهند.
ج) FromAssemblyDependencies: تمام اسمبلی‌هایی را که وابسته‌ی به اسمبلی معرفی شده‌ی به آن هستند، اسکن می‌کند.
د) FromApplicationDependencies, FromDependencyContext: تمام اسمبلی‌هایی را که توسط برنامه، ارجاعی به آن‌ها وجود دارند، اسکن می‌کند.


انتخاب دقیق‌تر کلاس‌ها و سرویس‌های مدنظر توسط Scrutor

شاید عملکرد کلی متد AddClasses مدنظر شما نباشد و نیاز به انتخاب دقیق‌تری از سرویس‌های اسکن شده را داشته باشید؛ برای این مورد نیز Scrutor روش‌های زیر را ارائه می‌دهد. برای مثال خود کلاس AddClasses دارای overloadهای زیر نیز هست:
    public interface IImplementationTypeSelector : IAssemblySelector, IFluentInterface
    {
        IServiceTypeSelector AddClasses();
        IServiceTypeSelector AddClasses(bool publicOnly);
        IServiceTypeSelector AddClasses(Action<IImplementationTypeFilter> action);
        IServiceTypeSelector AddClasses(Action<IImplementationTypeFilter> action, bool publicOnly);
    }
حالت پیش‌فرض آن انتخاب تمام کلاس‌های public, non-abstract است. اگر پارامتر publicOnly را با false مقدار دهی کنید، internal/private nested classes را نیز انتخاب می‌کند. پارامتر action ای که در اینجا درنظر گرفته شده، جهت فیلتر کردن سرویس‌های انتخابی است که تعدادی از مثال‌های آن‌را در زیر بررسی می‌کنیم:
services.Scan(scan => scan
              .FromAssemblyOf<IService>()
                .AddClasses(classes => classes.AssignableTo<IService>())
// .AddClasses(classes => classes.InNamespaces("MyApp")) 
// .AddClasses(classes => classes.Where(type => type.Name.EndsWith("Repository")) 
                    .AsImplementedInterfaces()
                    .WithTransientLifetime());
در اینجا در حالت اول، کلاس‌هایی که صرفا اینترفیس IService را پیاده سازی کرده باشند، انتخاب می‌شوند. حالت دوم آن، انتخاب‌ها را به یک فضای نام محدود می‌کند و حالت سوم اگر نام کلاسی به Repository ختم شود، آن‌را به عنوان سرویس انتخاب خواهد کرد.


مدیریت جایگزینی سرویس‌ها توسط Scrutor

یکی از مزیت‌های طراحی یک برنامه با درنظر گرفتن الگوی تزریق وابستگی‌ها، امکان جایگزین کردن سرویس‌های پیش‌فرض آن با سرویس‌های دیگری است. فرض کنید کتابخانه‌ای ارائه شده و از الگوریتم هش کردن X استفاده کرده‌است؛ اما شما علاقمندید تا از الگوریتم Y بجای آن استفاده کنید. اگر این کتابخانه وهله‌ی الگوریتم هش کردن را از طریق تزریق وابستگی‌ها تامین کرده باشد، فقط کافی است در ابتدای معرفی تنظیمات تزریق وابستگی‌های آن، سرویس الگوریتم هش کردن موجود را با نمونه‌ی خاص خودتان جایگزین کنید.
اکنون فرض کنید پیش از استفاده‌ی از Scrutor، تعدادی سرویس را به روش متداولی ثبت و معرفی کرده‌اید:
services.AddTransient<ITransientService, TransientService>();
services.AddScoped<IScopedService, ScopedService>();
حال که قرار است متد Scan آن، سرویس‌های یک اسمبلی را به لیست موجود اضافه کند، به سرویس‌های زیر می‌رسد:
public class TransientService : IFooService {}
public class AnotherService : IScopedService {}
 رفتار آن با سرویس‌های معادلی که از پیش ثبت شده‌اند چگونه باید باشد؟ برای مدیریت این مساله، متد UsingRegistrationStrategy پیش بینی شده‌است:
services.Scan(scan =>
                scan.FromAssemblyOf<IFoo>()
                    .AddClasses()
                    .UsingRegistrationStrategy(RegistrationStrategy.Skip)
                    .AsMatchingInterface()
                    .WithScopedLifetime());
و پارامتر دریافتی آن یک چنین امضایی را دارد:
namespace Scrutor
{
    public abstract class RegistrationStrategy
    {
        public static readonly RegistrationStrategy Skip;
        public static readonly RegistrationStrategy Append;
        protected RegistrationStrategy();
        public static RegistrationStrategy Replace();
        public static RegistrationStrategy Replace(ReplacementBehavior behavior);
        public abstract void Apply(IServiceCollection services, ServiceDescriptor descriptor);
    }
}
- حالت Append آن که حالت پیش‌فرض نیز هست، تمام سرویس‌های یافت شده را به لیست IServiceCollection اضافه می‌کند؛ صرفنظر از اینکه پیشتر ثبت شده‌است یا خیر.
- حالت Skip آن، سرویسی را تکراری ثبت نمی‌کند. یعنی اگر سرویسی پیشتر در مجموعه‌ی IServiceCollection موجود بود، مجددا آن‌را ثبت نمی‌کند.

سپس نوبت به متدهای Replace می‌رسد که یک چنین پارامتری را قبول می‌کنند:
namespace Scrutor
{
    [Flags]
    public enum ReplacementBehavior
    {
        Default = 0,
        ServiceType = 1,
        ImplementationType = 2,
        All = 3
    }
}
- در حالت استفاده‌ی از Replace(​ReplacementBehavior.​ServiceType)، اگر سرویسی پیشتر در لیست IServiceCollection ثبت شده باشد، آن‌را حذف کرده و سپس نمونه‌ی جدید را ثبت می‌کند (ثبت سرویس بر اساس اینترفیس و پیاده سازی آن).
- در حالت استفاده‌ی از Replace(​ReplacementBehavior.​ImplementationType)، اگر پیاده سازی کلاسی پیشتر در لیست IServiceCollection ثبت شده باشد، آن‌را حذف کرده و سپس نمونه‌ی جدید را ثبت می‌کند (ثبت سرویس صرفا بر اساس نام کلاس آن).
- حالت Replace(​ReplacementBehavior.All) هر دو حالت قبل را با هم شامل می‌شود.


امکان ترکیب چندین استراتژی جستجو با هم توسط Scrutor

در یک برنامه‌ی واقعی غیرممکن است که بخواهید تمام کلاس‌ها را با یک طول عمر، اسکن و ثبت کنید. برای این منظور می‌توان از قابلیت فیلتر کردن کلاس‌ها که در مورد آن بحث شد و همچنین امکان ترکیب زنجیر وار حالت‌های مختلف اسکن، استفاده کرد:
services.Scan(scan => scan 
  .FromAssemblyOf<CombinedService>() 
    .AddClasses(classes => classes.AssignableTo<ICombinedService>()) // Filter classes 
      .AsSelfWithInterfaces() 
      .WithSingletonLifetime() 
 
    .AddClasses(x=> x.AssignableTo(typeof(IOpenGeneric<>))) // Can close generic types 
      .AsMatchingInterface() 
 
    .AddClasses(x=> x.InNamespaceOf<MyClass>()) 
      .UsingRegistrationStrategy(RegistrationStrategy.Replace()) // Defaults to ReplacementBehavior.ServiceType 
      .AsMatchingInterface() 
      .WithScopedLifetime() 
 
  .FromAssemblyOf<DatabaseContext>()   // Can load from multiple assemblies within one Scan() 
    .AddClasses()  
      .AsImplementedInterfaces() 
);
مطالب
بررسی مقدار دهی اولیه متغیرها در T-SQL

یکی از موارد مشکل ساز حین استفاده از T-SQL ، مقدار دهی اولیه متغیرها به نال است و اگر اسکریپت تهیه شده کمی طولانی باشد، خطایابی مشکلات مرتبط با آن بسیار مشکل می‌شود. برای مثال:
Declare
@x int,
@y int

Set @x = 1
If (@x + @y = 1)
BEGIN
print 'yes!'
End

Set @y = (select sum(id) from Account)
If @x + @y = 1
BEGIN
print 'yes!'
End

کد فوق بدون هیچگونه خطایی اجرا می‌شود و هیچ وقت هم yes را چاپ نمی‌کند. مشکل هم همینجا است. خطایابی قسمت دوم این اسکریپت کمی مشکل‌تر از حالت قبل است. چون در اینجا به نظر متغیر y صریحا مقدار دهی شده است؛ اما در عمل ممکن است برای مثال به دلیل عدم وجود رکوردی در جدول Account، باز هم null به آن نسبت داده شود.

بنابراین سؤال این است که چگونه این نوع مشکلات را در یک پروژه با تعداد زیادی رویه ذخیره شده، تابع و غیره می‌توان تشخیص داد؟
پاسخ:
در این مورد قبلا مطلبی در این سایت منتشر شده [+] (البته اگر از نگارش کامل VS 2010 استفاده می‌کنید نیازی به نصب چیزی نخواهید داشت) و نکته‌ی آن بررسی SR0007 است.



مطالب
نحوه تعریف Linked Server و دریافت اطلاعات از سروری دیگر

برخی مواقع شما نیاز دارید تا یک Query را بر روی یک سرور اجرا نمایید و این Query برخی اطلاعات خود را از سرور دیگری دریافت می‌نماید. در این صورت باید یک پل ارتباطی بین سرور جاری و سرور دیگر وجود داشته باشد تا بتوانید در یک Query به سرور دیگری متصل شوید و اطلاعاتی را دریافت نمایید. در حالت عادی یک Query فقط می‌تواند بر روی سرور جاری اجرا شده و اطلاعاتی را بازیابی نماید. اما اگر همین Query بخواهد به سرور دیگری متصل شود، آن سرور باید در سرور جاری بصورت Linked Server تعریف شده باشد.

به عنوان مثال:

من سروری با آدرس 192.168.0.1 دارم که دارای پایگاه داده‌ای با نام Salary می باشد. نام این سرور را A می‌گذارم.

همچنین من سرور دیگری با آدرس 192.168.1.100 دارم که دارای پایگاه داده ای با نام Accounting است. نام این سرور را B می‌گذارم.

حالا می‌خواهم در سرور A یک Query بنویسم که جدول Payment را با اتصال به سرور B به جدول Document متصل نموده و نتیجه ی JOIN این دو جدول را نمایش دهد. به عنوان مثال:

SELECT * FROM Payment AS pay JOIN Document AS doc
ON pay.DocumentId = doc.Id
این Query به هیچ عنوان اجرا نخواهد شد. زیرا نمی‌تواند جدول Document را پیدا کند. برای این منظور باید سرور B را به سرور A معرفی کنیم که این کار از طریق Linked Server انجام خواهد شد.


نحوه‌ی ایجاد یک  Linked Server

بر روی سیستم من دو نسخه از SQL نصب شده است. یکی Standard Edition و دیگری Express Edition. من می‌خواهم در نسخه Standard یک Linked Server به نسخه‌ی Express ایجاد کنم. بنابراین با اتصال به نسخه Standard مراحل زیر را طی می‌کنم:

1.  یک New query ایجاد می‌کنم.

2.  دستورات زیر را در Query ایجاد شده می‌نویسم:

sp_addlinkedserver 'MyServer', '', 'SQLNCLI', '.\sqlexpress'
توضیحات:

sp_addlinkedserver نام رویه ای است که یک Linked Server را ایجاد می‌نماید.
پارامتر اول نام Linked Server را مشخص می‌نماید که جهت دسترسی به سرور دیگر مورد استفاده قرار می‌گیرد.
پارامتر دوم Product Name می‌باشد که من خالی گذاشتم.
پارامتر سوم Provider Name یا نام فراهم کننده داده‌ای است. چون من میخواهم به یک سرور SQL متصل شوم SQLNCLI (SQL Native Client) را انتخاب کردم. اگر به منبع داده‌ای دیگری مثل Access،Oracle، MySql و ... متصل می‌شوید باید Provider Name دیگری را نتخاب کنید.
پارامتر چهارم نام یا IP سروری است که می‌خواهیم به آن لینک شویم.

3.  با فشردن F5 یا منوی Execute این Query را اجرا کنید.

با اجرای موفقیت آمیز مراحل فوق باید عنوان MyServer را در مسیر Server Objects > Linked Server مشاهده کنید. در نسخه Express پایگاه داده‌ای با نام test دارم که شامل جدولی به نام tbl می باشد. با نوشتن Query زیر می‌توانم محتویات این جدول را مشاهده کنم:

SELECT * FROM MyServer.test.dbo.tbl
ممکن است جهت اتصال به سرور لینک شده نیاز به نام کاربری و رمز عبور داشته باشید. جهت تعریف نام کاربری و رمز عبور برای سرور لینک شده از دستورات زیر استفاده کنید:
sp_addlinkedsrvlogin 'MyServer',@rmtuser='user1', @rmtpassword='abc123'
توضیحات:

sp_addlinkedsrvlogin نام رویه ای است که نام کاربری و رمز عبور را به یک Linked Server اضافه می‌کند.
پارامتر اول نام Linked Server می باشد.
پارامتر دوم نام کاربری جهت اتصال به سرور لینک شده می‌باشد.
پارامتر سوم رمز عبور جهت اتصال به سرور لینک شده می‌باشد. 

مطالب
تزریق وابستگی‌ها فراتر از کلاس‌ها در برنامه‌های Angular
عموما تزریق وابستگی‌های کلاس‌ها، در برنامه‌های Angular صورت می‌گیرند. برای مثال در یک NgModule در قسمت providers آن نام کلاسی را معرفی می‌کنیم و سپس می‌توان این کلاس را به سازنده‌ی کامپوننت‌ها تزریق کرد و از امکانات آن استفاده کرد. اما سیستم تزریق وابستگی‌های Angular محدود به تزریق وهله‌های کلاس‌ها نیست و می‌توان قسمت providers را با یک سری شیء تعریف شده‌ی با {} نیز مقدار دهی کرد. در اینجا می‌توان یک token را به یک وابستگی انتساب داد.


انواع providers در Angular

سیستم تزریق وابستگی‌های Angular، تامین کننده‌های ذیل را نیز به همراه دارد:
 - تامین کننده‌ی مقادیر که با useValue مشخص می‌شود.
 - تامین کننده‌ی Factory‌ها که با useFactory تعریف خواهد شد.
 - تامین کننده‌ی کلاس‌ها که با useClass تعریف می‌شود.
 - تامین کننده‌ی کلاس‌هایی با نام‌های مستعار که توسط useExisting مشخص می‌شود.

یک تامین کننده مشخص می‌کند که سیستم تزریق کننده‌ی وابستگی‌ها، با درخواست توکن/کلیدی مشخص، چه وابستگی را باید وهله سازی کند.


تزریق وابستگی‌هایی از نوع ثوابت در برنامه‌های Angular

فرض کنید برنامه‌ی Angular شما در مسیر دیگری نسبت به Web API سمت سرور آن قرار دارد. به همین جهت در تمام سرویس‌های برنامه نیاز به تعریف مسیر پایه‌ی Web API مانند API_BASE_HREF را خواهید داشت. یک روش حل این مساله، تعریف این ثابت به صورت یک وابستگی و سپس تزریق آن به کلاس‌های سرویس‌ها و یا کامپوننت‌های برنامه است:
@NgModule({
  imports: [
    CommonModule,
    InjectionBeyondClassesRoutingModule
  ],
  declarations: [TestProvidersComponent],
  providers: [
    { provide: "API_BASE_HREF", useValue: "http://localhost:5000" },
    { provide: "APP_BASE_HREF", useValue: document.location.pathname },
    { provide: "IS_PROD", useValue: true },
    { provide: "APIKey", useValue: "XYZ1234ABC" },
    { provide: "Random", useValue: Math.random() },
    {
      provide: "emailApiConfig", useValue: Object.freeze({
        apiKey: "email-key",
        context: "registration"
      })
    },
    { provide: "languages", useValue: "en", multi: true },
    { provide: "languages", useValue: "fa", multi: true }
  ]
})
export class InjectionBeyondClassesModule { }
- در اینجا چندین مثال از تکمیل قسمت providers یک ماژول را با شیء‌های token دار provide مشاهده می‌کنید. هر provide یک token را مشخص می‌کند که از آن جهت دریافت مقدار وابستگی منتسب به آن استفاده خواهد شد.
- در این مثال، حالت‌های مختلفی از تامین کننده‌ی useValue را نیز مشاهده می‌کنید. انتساب یک رشته، یک مقدار boolean و یا یک مقدار که در زمان انتساب محاسبه خواهد شد مانند Math.random.
- همچنین در اینجا می‌توان در قسمت useValue مانند emailApiConfig، یک شیء را نیز تعریف کرد. علت استفاده‌ی از Object.freeze، تعریف این شیء به صورت read only است.
- در حین تعریف provideها اگر کلید توکن بکار رفته یکی باشد، آخرین مقدار، مابقی را بازنویسی می‌کند؛ مانند حالت languages که در اینجا دوبار تعریف شده‌است. اما با ذکر خاصیت multi، می‌توان به کلید languages به صورت یک آرایه دسترسی یافت و در این حالت مقادیر آن بازنویسی نمی‌شوند.

اکنون برای استفاده‌ی از این توکن‌های تعریف شده توسط سیستم تزریق وابستگی‌ها، می‌توان به صورت ذیل عمل کرد:
import { Component, OnInit, Inject } from "@angular/core";
import { inject } from "@angular/core/testing";

@Component({
  selector: "app-test-providers",
  templateUrl: "./test-providers.component.html",
  styleUrls: ["./test-providers.component.css"]
})
export class TestProvidersComponent implements OnInit {

  constructor(
    @Inject("API_BASE_HREF") public apiBaseHref: string,
    @Inject("APP_BASE_HREF") public appBaseHref: string,
    @Inject("IS_PROD") public isProd: boolean,
    @Inject("APIKey") public apiKey: string,
    @Inject("Random") public random: string,
    @Inject("emailApiConfig") public emailApiConfig: any,
    @Inject("languages") public languages: string[]
  ) { }

  ngOnInit() {
  }
}
در اینجا هر توکن توسط ویژگی Inject به سازنده‌ی کلاس تزریق شده‌است. از این جهت آن‌ها را public تعریف کرده‌ایم که بتوان در قالب این کامپوننت، به مقادیر تزریق شده، دسترسی یافت:
<h1>
  Injection Beyond Classes
</h1>
<div class="alert alert-info">
  <ul>
    <li>API_BASE_HREF: {{apiBaseHref}}</li>
    <li>APP_BASE_HREF: {{appBaseHref}}</li>
    <li>IS_PROD: {{isProd}}</li>
    <li>APIKey: {{apiKey}}</li>
    <li>Random-1: {{random}}</li>
    <li>Random-2: {{random}}</li>
    <li>emailApiConfig {{emailApiConfig | json}}</li>
    <li>languages: {{languages | json}}</li>
  </ul>
</div>
با این خروجی:


در اینجا همانطور که مشاهده می‌کنید، languages از نوع multi: true به یک آرایه تبدیل شده‌است و یا emailApiConfig نیز یک شیء است که توسط کلیدهای آن می‌توان به مقادیر متناظر آن دسترسی یافت. Random نیز تنها یکبار دریافت شده‌است و مهم نیست که چندبار صدا زده شود؛ همواره مقدار آن مساوی اولین مقداری است که در زمان انتساب دریافت می‌کند.


تزریق تنظیمات برنامه توسط تامین کننده‌ی مقادیر

یک نمونه از تزریق شیء emailApiConfig: any را در مثال فوق ملاحظه کردید. روش بهتر و نوع دار آن به صورت ذیل است. ابتدا یک فایل جدید thismodule.config.ts یا app.config.ts را ایجاد می‌کنیم:
import { InjectionToken } from "@angular/core";

export let APP_CONFIG = new InjectionToken<string>("this.module.config");

export interface IThisModuleConfig {
  apiEndpoint: string;
}

export const ThisModuleConfig: IThisModuleConfig = {
  apiEndpoint: "http://localhost:45043/api/"
};
تاکنون توکن‌های تعریف شده را توسط یک رشته‌ی ثابت مانند "API_BASE_HREF" تعریف کردیم. مشکل این روش، امکان تداخل آن‌ها در یک برنامه‌ی بزرگ است. به همین جهت روش توصیه شده، قرار دادن این کلید داخل یک InjectionToken است تا همواره بتوان به یک توکن منحصربفرد در طول عمر برنامه دست یافت که نمونه‌ی آن‌را در تعریف APP_CONFIG مشاهده می‌کنید. در برنامه اگر دو new InjectionToken، با یک سازنده‌ی یکسان تعریف شوند، با هم مساوی نخواهند بود و توکن نهایی آن منحصربفرد است:
import { InjectionToken } from '@angular/core';
export const EmailService1 = new InjectionToken<string>("EmailService");
export const EmailService2 = new InjectionToken<string>("EmailService");
console.log(EmailService1 === EmailService2); // false

سپس نوع تنظیمات را توسط اینترفیس IThisModuleConfig تعریف کرده‌ایم (که نسبت به استفاده‌ی از any یک پیشرفت محسوب می‌شود). در آخر وهله‌ای از این اینترفیس را به نحوی که مشاهده می‌کنید export کرده‌ایم.

اکنون نحوه‌ی تعریف تزریق وابستگی از نوع IThisModuleConfig در یک NgModule به صورت ذیل است:
import { ThisModuleConfig, APP_CONFIG } from "./thismodule.config";

@NgModule({
  providers: [
    { provide: APP_CONFIG, useValue: ThisModuleConfig }
  ]
})
export class InjectionBeyondClassesModule { }
اینبار توکن تعریف شده توسط InjectionToken مشخص شده‌است و مقدار آن توسط ThisModuleConfig تامین خواهد شد.

در آخر، تزریق آن به سازنده‌ی یک کامپوننت بر اساس توکن APP_CONFIG و از نوع مشخص اینترفیس آن خواهد بود:
import { IThisModuleConfig, APP_CONFIG } from "./../thismodule.config";
@Component()
export class TestProvidersComponent implements OnInit {

  constructor(
    @Inject(APP_CONFIG) public config: IThisModuleConfig
  ) { }

  ngOnInit() {
  }

}


تزریق وابستگی‌ها توسط تامین کننده‌ی Factory ها

تا اینجا useValue را بررسی کردیم. نوع دیگر تامین کننده‌های قابل تعریف، useFactory هستند:
@NgModule({
  providers: [
    // ------ useFactory
    { provide: "BASE_URL", useFactory: getBaseUrl },
    { provide: "RandomFactory", useFactory: randomFactory }
  ]
})
export class InjectionBeyondClassesModule { }

export function getBaseUrl() {
  return document.getElementsByTagName("base")[0].href;
}

export function randomFactory() {
  return Math.random();
}
در اینجا روش استفاده‌ی از useFactory را مشاهده می‌کنید. کار کرد آن با useValue دقیقا یکی است؛ یک توکن را مشخص می‌کنیم و سپس مقداری به آن نسبت داده می‌شود. اما در اینجا می‌توان یک متد را که بیانگر نحوه‌ی تامین این مقدار است نیز مشخص کرد و نسبت به حالت useValue که تنها یک مقدار ثابت و مشخص را دریافت می‌کند، انعطاف پذیری بیشتر دارد و می‌توان منطق سفارشی خاصی را نیز در اینجا پیاده سازی کرد.

روش استفاده‌ی از آن نیز همانند توکن‌های useValue است که توسط ویژگی Inject مشخص می‌شوند:
export class TestProvidersComponent implements OnInit {

  constructor(
    @Inject("BASE_URL") public baseUrl: string,
    @Inject("RandomFactory") public randomFactory: string
  ) { }

حالت useFactory علاوه بر امکان دریافت یک منطق سفارشی توسط یک function، امکان دریافت یک سری وابستگی را نیز دارد. فرض کنید کلاس سرویس خودرو به صورت زیر تعریف شده‌است که دارای وابستگی از نوع HttpClient تزریق شده‌ی در سازنده‌ی آن است:
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";

@Injectable()
export class CarService {

  constructor(private http: HttpClient) { }

}
در این حالت useFactory آن جهت تامین پارامتر سازنده‌ی  new CarService، به همراه متدی خواهد بود که پارامتری از نوع HttpClient را دریافت می‌کند:
import { CarService } from "./car.service";
import { HttpClient } from "@angular/common/http";

@NgModule({
  providers: [
    // ------ useFactory
    { provide: "Car_Service", useFactory: carServiceFactory, deps: [HttpClient] }
  ]
})
export class InjectionBeyondClassesModule { }

export function carServiceFactory(http: HttpClient) {
  return new CarService(http);
}
در اینجا برای تامین این پارامتر سازنده، خاصیت دیگری به نام deps قابل تعریف است که می‌تواند یک یا چند سرویس و وابستگی را تزریق و تامین کند. برای مثال سرویس HttpClient در اینجا توسط deps: [HttpClient] تزریق شده‌است.


تزریق وابستگی‌ها توسط تامین کننده‌ی کلاس‌ها

تا اینجا useValue و useFactory را بررسی کردیم. نوع دیگر تامین کننده‌های قابل تعریف، useClass هستند. در حالت استفاده‌ی useClass، نام یک نوع مشخص می‌شود و سپس Angular وهله‌ای از آن‌را تامین خواهد کرد. در این حالت اگر این وابستگی دارای پارامترهای تزریق شده‌ای در سازنده‌ی آن باشد، آن‌ها نیز به صورت خودکار وهله سازی می‌شوند.
import { CarService } from "./car.service";

@NgModule({
  providers: [
    // ------ useClass
    { provide: "Car_Service_Name1", useClass: CarService },
  ]
})
export class InjectionBeyondClassesModule { }
این حالت دقیقا معادل تعریف متداول سرویس ذیل است؛ با این تفاوت که توکن آن مساوی مقدار سفارشی Car_Service_Name1 است:
import { CarService } from "./car.service";

@NgModule({
  providers: [
        CarService
  ]
})
export class InjectionBeyondClassesModule { }


تزریق وابستگی‌ها توسط تامین کننده‌ی کلاس‌هایی با نام‌های مستعار

چگونه می‌توان دو تامین کننده را برای کلاسی مشابه، با توکن‌هایی متفاوت ایجاد کرد؟ در این حالت از useExisting استفاده می‌شود:
import { CarService } from "./car.service";

@NgModule({
  providers: [
    // ------ useClass
    { provide: "Car_Service_Name1", useClass: CarService },
    // ------ useExisting
    { provide: "Car_Service_Token2", useExisting: "Car_Service_Name1" },
  ]
})
export class InjectionBeyondClassesModule { }
در اینجا CarService توسط دو توکن مختلف در معرض دید قرار گرفته‌است. باید دقت داشت که درخواست "Car_Service_Token2" دقیقا همان وهله‌ی ایجاد شده‌ی توسط توکن "Car_Service_Name1" را بازگشت می‌دهد و وهله‌ی جدیدی در این حالت ایجاد نخواهد شد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
مطالب
جزئیات برنامه نویسی افزونه فارسی به پارسی

این افزونه با استفاده از ابزار Visual Studio Tools for Office که به VSTO مشهور شده است، تهیه شد. در بسته به روز رسانی سیستم که در ذیل (معرفی افزونه) نیز معرفی شد نگارش sp1 vsto3.0 آن به صورت خودکار نصب خواهد شد.
برای ایجاد این پروژه در VS.Net 2008 ، تنها کافی است یک پروژه جدید Word add-in را آغاز نمائیم. (شکل زیر)





قبل از ادامه بحث، بهتر است در مورد بانک اطلاعاتی مورد استفاده نیز توضیح داده شود. در اینجا از SQLite استفاده شد. (بسیار سبک، کم حجم و سریع است و اساسا یک کاربر نهایی برای تنظیمات آن نیازی نیست اطلاعاتی داشته باشد). بسته به روز رسانی سیستم (در مطلب قبلی)، این مورد را نیز به صورت خودکار نصب خواهد کرد (در GAC باید نصب شود وگرنه افزونه قادر به یافتن آن نخواهد شد).
برای ایجاد این بانک اطلاعاتی، از افزونه SQLite manager برای فایرفاکس استفاده شد. (این افزونه رایگان شما را از هر ابزار جانبی برای مدیریت یک بانک اطلاعاتی SQLite بی‌نیاز می‌کند)
برای مثال فایل ErrorsBank.sqlite برنامه افزونه فارسی به پارسی را توسط افزونه SQLite manager فایرفاکس باز کنید (این فایل را در محل نصب افزونه می‌توانید پیدا کنید). در اینجا می‌توان جداول جدید را ایجاد کرد، کوئری‌های دلخواه را اجرا نمود و یا اطلاعات را مرور کرده، حذف یا ویرایش کرد (شکل زیر).




و خوشبختانه این بانک اطلاعاتی و محصور کننده‌های آن با اطلاعات یونیکد فارسی هیچ مشکلی ندارند و برای کارهایی با وسعت کم و تعداد رکورد پائین یکی از بهترین انتخاب‌ها به‌شمار می‌روند.
نحوه استفاده از SQLite نیز در دات نت بسیار ساده است. اگر با ADO.Net کار کرده باشید، پس از افزودن ارجاعی از اسمبلی System.Data.SQLite.DLL به پروژه و معرفی فضای نام آن به پروژه، تنها کافی است در کدهای قبلی خود برای مثال SqlConnection را به SQLiteConnectionتغییر دهید و امثال آن. یعنی دانش ADO.Net شما در اینجا نیز کاملا قابل استفاده خواهد بود و نیازی نیست مدتی را صرف آشنا شدن با کلاس‌ها و مفاهیم جدید نمائید (البته این تنها زمانی معنا خواهد داشت که به ویزاردها عادت نکرده باشید و کارهای خود را با کد نویسی انجام داده باشید).
تنها یک نکته را باید به‌خاطر داشت و آن هم مربوط است به ساز و کار درونی SQLite . هنگام انجام عملیات update یا insert حتما از transaction استفاده کنید تا سرعت کوئری‌های شما در SQLite به نحو شگفت انگیزی افزایش یابد. مثالی در این مورد را در فایل chm راهنمای SQLite.NET می‌توانید پیدا کنید.

مطلب دیگری که پیش از پرداختن به کد نویسی افزونه باید با آن آشنا شویم، مفهوم smart tags در مجموعه آفیس است که در این پروژه از آن استفاده گردید.
smart tags در مجموعه آفیس برچسب‌هایی هستند که به صورت خودکار توسط یکی از محصولات آفیس مثلا ورد یا اکسل و امثال آن، پس از تشخیص یک کلمه خاص ایجاد می‌شوند و می‌توان اعمالی را به این برچسب ایجاد شده انتساب داد. برای مثال در اینجا امکان جایگزین کردن کلمه فارسی با معادل پارسی در نظر گرفته شد.
ویدیویی در مورد نحوه ایجاد اسمارت تگ‌ها در VS.Net و یا مثالی پیشرفته‌تر در مورد تشخیص دمای فارنهایت در یک متن و ایجاد smart tag مخصوص به آن برای تبدیل به سلسیوس. (از regular expressions جهت یافتن یک الگو در متن استفاده شده است)

در این پروژه، حدود 3800 واژه فارسی به‌ یک smart tag انتساب داده می‌شود (در روال استاندارد ThisAddIn_Startup). سپس در هنگام نمایش آن، معادل پارسی کلمه نیز به منوی باز شده افزوده گشته و در روال رخداد کلیک آن، تعویض کلمه تشخیص داده شده با واژه پیدا شده صورت خواهد گرفت.

در ادامه فرض بر این است که یک پروژه جدید word add-in را در VS.Net ایجاد کرده‌اید و همچنین ارجاعی را به فایل System.Data.SQLite.DLL افزوده‌اید.

using System;
using System.Diagnostics;
using Microsoft.Office.Tools.Word;
using Action = Microsoft.Office.Tools.Word.Action;

private SmartTag _st;
private void init()
{
try
{
//Enable Smart Tags in Word
if (!Application.Options.LabelSmartTags)
{
//ممکن است اسمارت تگ‌ها در ورد غیرفعال باشند. به این صورت می‌شود آنها را فعال کرد
Application.Options.LabelSmartTags = true;
}

_st = new SmartTag(@"www.microsoft.com/Demo#FarsiSmartTag", @"فارسی به پارسی");

//دریافت واژه‌های فارسی از دیتابیس و افزودن خودکار آنها به اسمارت تگ‌ها
if (!DBhelper.AddSmartTagItems(_st, "select distinct farsi from tblFarsiToParsi")) return;

Action stActions = new Action("تبدیل");//تعریف یک اکشن جدید
stActions.Click += stActions_Click;//انتساب روال‌های رخداد گردان
stActions.BeforeCaptionShow += stActions_BeforeCaptionShow;
_st.Actions = new[] { stActions };
VstoSmartTags.Add(_st);//افزودن اسمارت تگ به مجموعه
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
}
}

private void ThisAddIn_Startup(object sender, EventArgs e)
{
init();
}

دو روال رخداد گردان زیر نیز جهت تغییر عنوان پیش فرض به واژه یافته شده در لحظه نمایش منو و روال کلیک نیز ایجاد خواهد شد:

static void stActions_BeforeCaptionShow(object sender, ActionEventArgs e)
{
try
{
Action clickedAction = sender as Action;
if (clickedAction != null)
{
string parsi = DBhelper.FindParsi(e.Text);//معادل پارسی از دیتابیس دریافت می‌شود
clickedAction.Caption = (parsi == string.Empty ? e.Text : parsi);
}
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
}
}

static void stActions_Click(object sender, ActionEventArgs e)
{
try
{
Action clickedAction = sender as Action;
if (clickedAction != null)
{
e.Range.Text = clickedAction.Caption;//جایگزینی متن موجود با عنوانی که پیشتر پارسی شده است
}
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex.ToString(), EventLogEntryType.Error, 7);
}
}

نکته‌ای را که در اینجا باید حتما رعایت کرد بحث exception handling‌ است. خصوصا در روال استاندارد ThisAddIn_Startup . اگر در این روال خطایی مدیریت نشده رخ دهد، word افزودنی شما را به صورت غیرفعال به مجموعه اضافه خواهد کرد و فعال سازی بعدی آن پس از اصلاح کد واقعا مشکل خواهد بود. همانطور که ملاحظه می‌کنید تمامی خطاها در event log‌ ویندوز نوشته می‌شوند.
همچنین باید دقت داشت که اگر متغیری در سطح کلاس تعریف نشود به احتمال زیاد تا دقایقی بعد توسط garbage collector به دیار باقی خواهد شتافت (تعریف st_ در اینجا). اینجاست که شاید ساعت‌ها وقت صرف کنید که چرا روال‌های رخ‌داد گردان دیگر اجرا نمی‌شوند. چرا افزونه دیگر کار نمی‌کند.

همین! کل سورس این add-in منهای بحث دریافت اطلاعات از دیتابیس همین بود! وظیفه‌ی تشخیص کلمات معرفی شده به ms-word به‌عهده‌ی خود آن است و این‌کار را نیز به‌خوبی انجام می‌دهد. در گذشته‌های نچندان دور ایجاد یک افزونه برای word واقعا مشکل بود که با این روش بسیاری از موانع برطرف شده است.

کلاس DBHelper که کار دریافت اطلاعات واژه‌ها را از دیتابیس SQLite انجام می‌دهد به شرح زیر است:

using System;
using System.Data.SQLite;
using System.Diagnostics;
using System.Reflection;
using Microsoft.Office.Tools.Word;

namespace Farsi2Parsi
{
class DBhelper
{
#region Methods (2)

// Public Methods (2)

public static bool AddSmartTagItems(SmartTag st, string strSQL)
{
SQLiteDataReader myReader = null;
SQLiteCommand sqlCmd = null;
bool ret = false;
try
{
SQLiteConnection sqlCon = new SQLiteConnection
{
ConnectionString = "Data Source=" + ConStr.ConnectionString
};
sqlCon.Open();
sqlCmd = new SQLiteCommand(strSQL, sqlCon);
myReader = sqlCmd.ExecuteReader();

if (myReader != null)
while (myReader.Read())
{
if (myReader.GetValue(0) != DBNull.Value)
st.Terms.Add(myReader.GetValue(0).ToString());
}

ret = true;
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex + "\n" + Environment.CurrentDirectory + "\n" +
Assembly.GetExecutingAssembly().Location, EventLogEntryType.Error, 7);
}
finally
{
if (myReader != null)
myReader.Close();

if (sqlCmd != null)
sqlCmd.Connection.Close();
}
return ret;
}

public static string FindParsi(string farsi)
{
SQLiteDataReader myReader = null;
SQLiteCommand sqlCmd = null;
string ret = string.Empty;
string strSQL = "select parsi from tblFarsiToParsi where farsi='" + farsi.Replace("'", "''") + "'";
try
{
SQLiteConnection sqlCon = new SQLiteConnection
{
ConnectionString = "Data Source=" + ConStr.ConnectionString
};
sqlCon.Open();
sqlCmd = new SQLiteCommand(strSQL, sqlCon);
myReader = sqlCmd.ExecuteReader();

if (myReader != null)
{
myReader.Read(); //اولین مورد کافی است
if (myReader.GetValue(0) != DBNull.Value)
ret = myReader.GetValue(0).ToString();
}
}
catch (Exception ex)
{
EventLog.WriteEntry("FarsiToParsi", ex + "\n" + Environment.CurrentDirectory + "\n" +
Assembly.GetExecutingAssembly().Location, EventLogEntryType.Error, 8);
}
finally
{
if (myReader != null)
myReader.Close();

if (sqlCmd != null)
sqlCmd.Connection.Close();
}
return ret;
}
#endregion Methods
}
}

همانطور که پیشتر نیز عنوان شد اگر با ADO.net آشنایی داشته باشید، هیچ نکته‌ی خاص جدیدی را در اینجا مشاهده نخواهید کرد و تنها یک سری امور روزمره کاری با ADO.net مطرح شده است، باز کردن کانکشن، اجرای کوئری، دریافت اطلاعات و پاکسازی نهایی. (قسمت finally را با استفاده از عبارت using می‌شود حذف کرد)

هنگام نصب برنامه، مسیر پوشه نصب در رجیستری ویندوز توسط نصاب نوشته خواهد شد. از همین مورد برای ایجاد رشته اتصالی به دیتابیس استفاده گردید.

class ConStr
{
public static string ConnectionString
{
get
{
return Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SOFTWARE\\FarsiToParsi").GetValue("folder") + "\\ErrorsBank.sqlite";
}
}
}

سورس کامل این افزونه را به صورت یک پروژه VS.Net 2008 SP1 از اینجا می‌توانید دریافت کنید.
نصاب برنامه با استفاده از NSIS ایجاد شده که در روزی دیگر درباره‌ی آن توضیح خواهم داد.
اگر قصد داشته باشید از روش‌های متداول استفاده کنید، مشاهده ویدیوی زیر توصیه می‌شود:
http://msdn.microsoft.com/en-us/office/bb851702.aspx

برای توزیع این نوع افزونه‌ها علاوه بر دات نت فریم ورک، به چهار به روز رسانی دیگر نیز نیاز خواهد بود:
به روز رسانی نصاب ویندوز (که احتمالا نصب هست)
WindowsInstaller-KB893803-v2-x86.exe
Microsoft Office System Update: Redistributable Primary Interop Assemblies :
o2007pia.msi
نصب vsto و همچنین sp1 آن
vstor30.exe
vstor30sp1-KB949258-x86.exe

این موارد را من در بسته به روز رسانی سیستم قرار داده‌ام که به صورت خودکار و یکی پس از دیگری اجرا و نصب خواهند شد.
پس از آن با کلیک بر روی فایلی با پسوند vsto که در پوشه build برنامه موجود است، می‌توان افزونه را نصب کرد (click once installation).




سایر اطلاعات در مورد پروژه‌های VSTO را می‌توان از طریق وبلاگ رسمی آنها دنبال کرد:
http://blogs.msdn.com/vsto/

ایده‌های دیگری را هم در همین رابطه می‌توان پیاده سازی کرد. برای مثال درست کردن یک افزونه برای بررسی آئین نگارش فارسی در متون word. دقیقا با همین روش قابل پیاده سازی است و یا ایجاد غلط یاب بهتری نسبت به آن‌چه که هم اکنون برای آفیس 2003 توسط مایکروسافت ارائه شده است (این غلط یاب با صفحه کلید استاندارد تایپ ایران همخوانی ندارد، به همین جهت با استقبال نیز مواجه نشد).