- Basic Spatial Data with SQL Server and Entity Framework 5.0
- Using spatial data types with Entity Framework 5.0
- Using Spatial Data with SQL Server 2012, Entity Framework 5 and ASP.NET MVC
- Spatial Data Type Support in Entity Framework 5
- Using Spatial Data in ASP.Net MVC with SQL Server 2012, Entity Framework 5 and Infragistics jQuery Map
- Spatial Data and the Entity Framework
- Spatial data and Entity Framework - from real world usage
- Entity Framework Spatial Part 1: Loading data
- Spatial Data type support in Entity Framework 5.0
- Creating a Geometry Point in Entity Framework
- Spatial - Code First
- Spatial Types in the Entity Framework
- How To: Using spatial data with Entity Framework and Connector/Net
مدل سازی مفهومی بارزترین ویژگی Entity Framework است و اصل این کتاب را تشکیل میدهد. Entity Framework بر اساس الگوهای دسترسی به داده قبلی ساخته شده است و محیطی را فراهم کرده است که از مدل سازی در سطح domain واقعی پشتیبانی میکند.
Entity Framework 6 تبدیل به تکنولوژی دسترسی به داده بالغ و پخته ای شده است که با امکانات زیاد خود، آمادهی استفاده در برنامههای جدید و قدیمی است.
Entity framework model-first
Entity framework code-first
NHibernate
LLBLGen Pro
Telerik OpenAccess ORM
EntitySpaces
Devart
SubSonic
LINQ to SQL
از Micro ORMs مانند Dapper یا Massive
از یک ORM دست ساز خانگی
از ORM استفاده نمیکنم
الف) دریافت TopShelf
TopShelf یک کتابخانه سورس باز است و علاوه بر آن، آخرین فایلهای باینری آنرا از طریق نیوگت نیز میتوان دریافت کرد:
PM> Install-Package Topshelf
ب) فعال سازی TopShelf
یک برنامه ساده کنسول را ایجاد کنید. سپس با استفاده از نیوگت و اجرای فرمان فوق، ارجاعی را به اسمبلی TopShelf اضافه نمائید.
using Topshelf; namespace MyService { class Program { static void Main(string[] args) { HostFactory.Run(config => { config.Service(settings => new TestService()); config.EnableServiceRecovery(recovery => recovery.RestartService(delayInMinutes: 1)); config.EnableShutdown(); config.EnablePauseAndContinue(); config.SetDescription("MyService Desc."); config.SetDisplayName("MyService"); config.SetServiceName("MyService"); config.RunAsLocalSystem(); }); } } }
using Topshelf; using Topshelf.Logging; namespace MyService { public class TestService : ServiceControl { static readonly LogWriter _log = HostLogger.Get<TestService>(); public bool Start(HostControl hostControl) { _log.Info("TestService Starting..."); return true; } public bool Stop(HostControl hostControl) { _log.Info("TestService Stopped"); return true; } public bool Pause(HostControl hostControl) { _log.Info("TestService Paused"); return true; } public bool Continue(HostControl hostControl) { _log.Info("TestService Continued"); return true; } } }
ج) نصب TopShelf
در همین حالت اگر برنامه را اجرا کنید، سرویس ویندوز ان تی تهیه شده، شروع به کار خواهد کرد (مزیت مهم آن نسبت به قالب توکار تهیه سرویسهای ویندوز در ویژوال استودیو).
برای نصب این سرویس تنها کافی است در خط فرمان با دسترسی مدیریتی، دستور نصب your_exe install و یا عزل your_exe uninstall صادر شوند.
columns: [ { field: "IsAvailable", title: "موجود است", template: '<input type="checkbox" #= IsAvailable ? checked="checked" : "" # disabled="disabled" ></input>' } ]
همانطور که در این مثال نیز مشاهده میکنید، قالبهای Kendo UI از Hash (#) syntax استفاده میکنند. در اینجا قسمتهایی از قالب که با علامت # محصور میشوند، در حین اجرا، با اطلاعات فراهم شده جایگزین خواهند شد.
برای رندر مقادیر ساده میتوان از # =# استفاده کرد. از # :# برای رندر اطلاعات HTML-encoded کمک گرفته میشود و # # برای رندر کدهای جاوا اسکریپتی کاربرد دارد. از حالت HTML-encoded برای نمایش امن اطلاعات دریافتی از کاربران و جلوگیری از حملات XSS استفاده میشود.
اگر در این بین نیاز است # به صورت معمولی رندر شود، در حالت کدهای جاوا اسکریپتی به صورت #\\ و در HTML ساده به صورت #\ باید مشخص گردد.
مثالی از نحوهی تعریف یک قالب Kendo UI
<!--دریافت اطلاعات از منبع محلی--> <script id="javascriptTemplate" type="text/x-kendo-template"> <ul> # for (var i = 0; i < data.length; i++) { # <li>#= data[i] #</li> # } # </ul> </script> <div id="container1"></div> <script type="text/javascript"> $(function () { var data = ['User 1', 'User 2', 'User 3']; var template = kendo.template($("#javascriptTemplate").html()); var result = template(data); //Execute the template $("#container1").html(result); //Append the result }); </script>
در ادامه باید این قالب را رندر کرد. برای این منظور یک div با id مساوی container1 را جهت تعیین محل رندر نهایی اطلاعات مشخص میکنیم. سپس متد kendo.template بر اساس id قالب اسکریپتی تعریف شده، یک شیء قالب را تهیه کرده و سپس با ارسال آرایهای به آن، سبب اجرای آن میشود. خروجی نهایی، یک قطعه کد HTML است که در محل container1 درج خواهد شد.
همانطور که ملاحظه میکنید، متد kendo.template، نهایتا یک رشته را دریافت میکند. بنابراین همینجا و به صورت inline نیز میتوان یک قالب را تعریف کرد.
کار با منابع داده راه دور
فرض کنید مدل برنامه به صورت ذیل تعریف شدهاست:
namespace KendoUI04.Models { public class Product { public int Id { set; get; } public string Name { set; get; } public decimal Price { set; get; } public bool IsAvailable { set; get; } } }
using System.Collections.Generic; using System.Linq; using System.Web.Http; using KendoUI04.Models; namespace KendoUI04.Controllers { public class ProductsController : ApiController { public IEnumerable<Product> Get() { return ProductDataSource.LatestProducts.Take(10); } } }
<!--دریافت اطلاعات از سرور--> <div> <div id="container2"><ul></ul></div> </div> <script id="template1" type="text/x-kendo-template"> <li> #=Id# - #:Name# - #=kendo.toString(Price, "c")#</li> </script> <script type="text/javascript"> $(function () { var producatsTemplate1 = kendo.template($("#template1").html()); var productsDataSource = new kendo.data.DataSource({ transport: { read: { url: "api/products", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' } }, error: function (e) { alert(e.errorThrown); }, change: function () { $("#container2 > ul").html(kendo.render(producatsTemplate1, this.view())); } }); productsDataSource.read(); }); </script>
هرچند خروجی دریافتی از سرور نهایتا یک آرایه از اشیاء Product است، اما در template1 اثری از حلقهی جاوا اسکریپتی مشاهده نمیشود. در اینجا چون از متد kendo.render استفاده میشود، نیازی به ذکر حلقه نیست و به صورت خودکار، به تعداد عناصر آرایه دریافتی از سرور، قطعه HTML قالب را تکرار میکند.
در ادامه برای کار با سرور از یک Kendo UI DataSource استفاده شدهاست. قسمت transport/read آن، کار تعریف محل دریافت اطلاعات را از سرور مشخص میکند. رویدادگران change آن اطلاعات نهایی دریافتی را توسط متد view در اختیار متد kendo.render قرار میدهد. در نهایت، قطعهی HTML رندر شدهی نهایی حاصل از اجرای قالب، در بین تگهای ul مربوط به container2 درج خواهد شد.
رویدادگران change زمانیکه data source، از اطلاعات راه دور و یا یک آرایهی جاوا اسکریپتی پر میشود، فراخوانی خواهد شد. همچنین مباحث مرتب سازی اطلاعات، صفحه بندی و تغییر صفحه، افزودن، ویرایش و یا حذف اطلاعات نیز سبب فراخوانی آن میگردند. متد view ایی که در این مثال فراخوانی شد، صرفا در روال رویدادگردان change دارای اعتبار است و آخرین تغییرات اطلاعات و آیتمهای موجود در data source را باز میگرداند.
یک نکتهی تکمیلی: فعال سازی intellisense کدهای جاوا اسکریپتی Kendo UI
اگر به پوشهی اصلی مجموعهی Kendo UI مراجعه کنید، یکی از آنها vsdoc نام دارد که داخل آن فایلهای min.intellisense.js و vsdoc.js مشهود هستند.
اگر از ویژوال استودیوهای قبل از 2012 استفاده میکنید، نیاز است فایلهای vsdoc.js متناظری را به پروژه اضافه نمائید؛ دقیقا در کنار فایلهای اصلی js موجود. اگر از ویژوال استودیوی 2012 و یا بالاتر استفاده میکنید باید از فایلهای intellisense.js متناظر استفاده کنید. برای مثال اگر از kendo.all.min.js کمک میگیرید، فایل متناظر با آن kendo.all.min.intellisense.js خواهد بود.
بعد از اینکار نیاز است فایلی به نام references.js_ را به پوشهی اسکریپتهای خود با این محتوا اضافه کنید (برای VS 2012 به بعد):
/// <reference path="jquery.min.js" /> /// <reference path="kendo.all.min.js" />
Tools menu –> Options -> Text Editor –> JavaScript –> Intellisense –> References
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید:
KendoUI04.zip
نحوه کار با ftp - بخش اول
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.diagnostics> <trace autoflush="true" /> <sources> <source name="System.Net"> <listeners> <add name="MyTraceFile"/> </listeners> </source> </sources> <sharedListeners> <add name="MyTraceFile" type="System.Diagnostics.TextWriterTraceListener" initializeData="System.Net.trace.log" /> </sharedListeners> <switches> <add name="System.Net" value="Verbose" /> </switches> </system.diagnostics> </configuration>
<Project Sdk="Microsoft.NET.Sdk.Worker"> <ItemGroup> <OpenApiReference Include="OpenAPIs\v1.json" CodeGenerator="NSwagCSharp" Namespace="GettingStarted" ClassName="MyClient"> <SourceUri>https://geo.api.vlaanderen.be/capakey/v2/swagger/docs/v1</SourceUri> </OpenApiReference> </ItemGroup> </Project>
همانطور که عنوان شد به ماژولهای داخلی، فضاهای نام (namespace) گفته میشود. بنابراین از namespaceها نیز جهت مدیریت و ساماندهی پروژههای بزرگ با تعداد فایلهای زیاد استفاده میشود. در واقع یک namespace حالت سادهتری از یک ماژول است؛ یعنی برای بارگذاری آن در مرورگر نیاز به loader خاصی نیست. بنابراین در پروژههایی با مقیاس کوچکتر استفاده از namespaceها، انتخاب بهتری است. همچنین یکی از مزیتهای مهم namespace جلوگیری از شلوغ شدن global scope است.
نحوهی تعریف namespace در TypeScript
برای ایجاد یک namespace کافی است از کلمه کلیدی namespace استفاده کنیم:
namespace Membership { export function AddMember(name: string) { // add a new member } }
در کد فوق یک namespace را با نام Membership تعریف کردهایم. درون این namespace میتوانیم کدهای خود را بنویسیم. به عنوان مثال در کد فوق یک تابع را با نام AddMember درون آن تعریف کردهایم و با استفاده از کلمهی کلیدی export این تابع را در معرض دید مصرف کننده قرار دادهایم. لازم به ذکر است که یک namespace میتواند به صورت nested (تودرتو) نیز استفاده شود. اما باید توجه داشته باشید که برای داشتن چنین قابلیتی باید از export نیز قبل از کلمهی کلیدی namespace استفاده شود:
namespace Membership { export function AddMember(name: string) { // add a new member } export namespace Cards { export function IssueCard(memberNumber: number) { // issue new card } } }
همانطور که مشاهده میکنید، درون یک nested namespace نیز میتوانیم اشیاء را توسط کلمهی کلیدی export در معرض دید مصرف کننده قرار دهیم.
نحوهی استفاده از namespaceها در TypeScript
برای استفاده از کدهای مثال قبل، در یک فایل دیگر میتوانیم به صورت زیر یک ارجاع را به namespace مربوطه داشته باشیم:
/// <reference path="membership.ts" />
اکنون درون ادیتور، به صورت کامل به اشیاء export شدهی توسط فضای نام Membership دسترسی خواهید داشت. لازم به ذکر است به این نوع ارجاع، Triple-Slash References نیز گفته میشود. مزیت دیگر این روش این است که کامپایلر TypeScript ارجاعات را نیز کامپایل میکند.
برای استفاده از اشیاء مربوط به namespace ارجاع داده شده نیز باید از سینتکس نقطه استفاده کرد:
Membership.AddMember('Garrett'); Membership.Cards.IssueCard(500);
نکتهایی که در اینجا وجود دارد این است که کامپایلر TypeScript به صورت پیشفرض فایلهای ارجاع داده شده را به فایلهای جداگانهایی کامپایل خواهد کرد و فرض را بر این خواهد گذاشت که هر کدام از فایلهای کامپایل شده قرار است به ترتیب استفاده از آنها درون صفحه استفاده شوند. برای تولید یک فایل واحد میتوانید از فلگ outFile استفاده کنید. با کمک این فلگ تمامی ارجاعات، بر اساس ترتیب استفاده از آنها، به یک فایل واحد تبدیل خواهند شد. جهت درک بهتر موضوع، یک فایل را با نام utilityFunctions.ts با محتویات زیر ایجاد کنید:
namespace Utility { export namespace Fees { export function CalculateLateFee(daysLate: number): number { return daysLate * .25; } } export function MaxBooksAllowed(age: number): number { if (age < 12) { return 3; } else { return 10; } } function privateFunc(): void { console.log('This is private....'); } }
در کد فوق یک namespace با نام Utility ایجاد کردهایم سپس یک namespace دیگر و همچنین یک تابع را درون آن export کردهایم. اکنون میخواهیم از این فضای نام درون یک فایل دیگر با نام app.ts استفاده کنیم. برای اینکار دورن فایل app.ts با استفاده از Triple-Slash یک ارجاع را به فضای نام عنوان شده اضافه کنید:
/// <reference path="utilityFunctions.ts" />
اکنون میتوانیم از Intellisense مربوط به VS Code جهت دسترسی به اعضای فضای نام Utility استفاده کنیم:
لازم به ذکر است برای namespace مربوطه میتوانیم alias نیز جهت دسترسی سریعتر، تعریف کنیم:
/// <reference path="utilityFunctions.ts" /> import util = Utility.Fees; let fee = util.CalculateLateFee(10); console.log(`Fee: ${fee}`);
توسط فرمان زیر نیز میتوانیم فایل فوق را به راحتی کامپایل کنیم:
tsc --target ES5 app.ts
با فرمان فوق، فایل app.js به همراه تمامی فایلهای ارجاع داده شدهی درون آن نیز کامپایل خواهند شد.
اکنون اگر بخواهیم توسط node فایل app.js را اجرا کنیم، با خطای ReferenceError: Utility is not defined مواجه خواهیم شد. دلیل آن نیز این است که node سعی در لود کردن فایل به صورت یک ماژول دارد و از آنجائیکه ما از namespace استفاده کردهایم، فضای نام Utility توسط node قابل شناسنایی نمیباشد. اما درون یک صفحهی HTML با قرار دادن فایل utilityFunctions.js و سپس app.js مشکلی نخواهیم داشت. برای حل این مورد در node کافی است از نکتهی یکی کردن فایلها استفاده کنیم:
tsc --target ES5 app.ts --outFile out.js
در اینحالت فایلها به ترتیب استفاده، با هم ادغام شده و تبدیل به یک فایل با نام out.js خواهند شد.
public static void CreateUniqueIndex(this DbContext context, string tableName, string fieldName) { context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX [IX_Unique_" + tableName + "_" + fieldName + "] ON [" + tableName + "]([" + fieldName + "] ASC);"); }
public class MyDbMigrationsConfiguration : DbMigrationsConfiguration<MyContext> { public BlogDbMigrationsConfiguration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { CreateUniqueIndex(context, "table1", "field1"); base.Seed(context); } }
برای حل این مشکل و تبدیل کدهای فوق به کدهایی Refactoring friendly، نیاز است نام جدول به صورت خودکار از DbContext دریافت شود. همچنین نام خاصیت یا فیلد نیز به صورت strongly typed قابل تعریف باشد.
کدهای کامل نمونه بهبود یافته را در ذیل مشاهده میکنید:
using System; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Objects; using System.Linq; using System.Linq.Expressions; using System.Text.RegularExpressions; namespace General { public static class ContextExtensions { public static string GetTableName<T>(this DbContext context) where T : class { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; return objectContext.GetTableName<T>(); } public static string GetTableName<T>(this ObjectContext context) where T : class { var sql = context.CreateObjectSet<T>().ToTraceString(); var regex = new Regex("FROM (?<table>.*) AS"); var match = regex.Match(sql); string table = match.Groups["table"].Value; return table .Replace("`", string.Empty) .Replace("[", string.Empty) .Replace("]", string.Empty) .Replace("dbo.", string.Empty) .Trim(); } private static bool hasUniqueIndex(this DbContext context, string tableName, string indexName) { var sql = "SELECT count(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS where table_name = '" + tableName + "' and CONSTRAINT_NAME = '" + indexName + "'"; var result = context.Database.SqlQuery<int>(sql).FirstOrDefault(); return result > 0; } private static void createUniqueIndex(this DbContext context, string tableName, string fieldName) { var indexName = "IX_Unique_" + tableName + "_" + fieldName; if (hasUniqueIndex(context, tableName, indexName)) return; var sql = "ALTER TABLE [" + tableName + "] ADD CONSTRAINT [" + indexName + "] UNIQUE ([" + fieldName + "])"; context.Database.ExecuteSqlCommand(sql); } public static void CreateUniqueIndex<TEntity>(this DbContext context, Expression<Func<TEntity, object>> fieldName) where TEntity : class { var field = ((MemberExpression)fieldName.Body).Member.Name; createUniqueIndex(context, context.GetTableName<TEntity>(), field); } } }
توضیحات
متد GetTableName ، به کمک SQL تولیدی حین تعریف جدول متناظر با کلاس جاری، نام جدول را با استفاده از عبارات باقاعده جدا کرده و باز میگرداند. به این ترتیب به دقیقترین نامی که واقعا جهت تولید جدول مورد استفاده قرار گرفته است خواهیم رسید.
در مرحله بعد آن، همان متد createUniqueIndex ابتدای بحث را ملاحظه میکنید. در اینجا جهت حفظ سازگاری بین SQL Server کامل و SQL CE از UNIQUE CONSTRAINT استفاده شده است که همان کار ایجاد ایندکس منحصربفرد را نیز انجام میدهد. به علاوه مزیت دیگر آن امکان دسترسی به تعاریف قید اضافه شده توسط view ایی به نام INFORMATION_SCHEMA.TABLE_CONSTRAINTS است که در نگارشهای مختلف SQL Server به یک نحو تعریف گردیده و قابل دسترسی است. از این view در متد hasUniqueIndex جهت بررسی تکراری نبودن UNIQUE CONSTRAINT در حال تعریف، استفاده میشود. اگر این قید پیشتر تعریف شده باشد، دیگر سعی در تعریف مجدد آن نخواهد شد.
متد CreateUniqueIndex تعریف شده در انتهای کلاس فوق، امکان دریافت نام خاصیتی از TEntity را به صورت strongly typed میسر میسازد.
اینبار برای تعریف یک قید و یا ایندکس منحصربفرد بر روی خاصیتی مشخص در متد Seed، تنها کافی است بنویسیم:
context.CreateUniqueIndex<User>(x=>x.Name);