using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Linq; using System.Web; public class GallerySite { int iD; public int ID { get { return iD; } set { iD = value; } } string imagePath; public string ImagePath { get { return imagePath; } set { imagePath = value; } } string imageText; public string ImageText { get { return imageText; } set { imageText = value; } } public List<GallerySite> GetImage() { List<GallerySite> _Gallery = new List<GallerySite>() { }; SqlConnection cnn = new SqlConnection("Data Source=.;Initial Catalog=RoshanZamirDataBase;Integrated Security=True"); cnn.Open(); SqlCommand cmd = new SqlCommand("Select * From Gallery", cnn); SqlDataReader datareadfewr = cmd.ExecuteReader(); if (datareadfewr.HasRows) { while (datareadfewr.Read()) { _Gallery.Add(new GallerySite() { ID = Convert.ToInt32(datareadfewr["ID"]), ImagePath = (string)datareadfewr["ImagePath"], ImageText=(string)datareadfewr["ImageText"] }); } } return _Gallery; } } ................................................................................................................................. <link href="orbit-1.2.3.css" rel="stylesheet" /> <script src="jquery-1.8.3.min.js"></script> <script src="jquery.orbit-1.2.3.min.js"></script> <script type="text/javascript"> $(function () { $.ajax({ url: "Handler.ashx", contentType: "application/json; charset=utf-8", success: function (data) { $.each(data, function (i, b) { var str = '<img src="' + b.ImagePath + '" alt="' + b.ImageText + '"/>'; $("#featured").append(str); }); $('#featured').orbit(); }, dataType: "json" }); }); </script> ........................................................................................................................... Handler : public class Handler : IHttpHandler { public void ProcessRequest (HttpContext context) { var _Gallery = new GallerySite(); var List = _Gallery.GetImage(); string str = JsonConvert.SerializeObject(List); context.Response.Write(str); } public bool IsReusable { get { return false; } } }
ابتدا لازم است Entity framework را توسط Nuget packages manager دانلود نمایید. سپس به پروژهی خود یک فولدر جدید را به نام Models و درون آن ابتدا یک کلاس را به نام User اضافه کرده و بدین صورت خواهیم داشت :
using System; namespace Console1.Models { public enum UserType { Admin, Ordinary } public class User { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public UserType Type { get; set; } } }
UserType نیز کاملا مشخص است؛ هر User نقش Admin یا Ordinary را میتواند داشته باشد.
نوبت به نوشتن اینترفیس IUser میرسد. در همین پوشهای که قرار داریم، آن را پیاده سازی مینماییم:
namespace Console1.Models { public interface IUser { int UserId { get; set; } User User { get; set; } } }
هر entity که با User ارتباط دارد، باید اینترفیس فوق را پیاده سازی نماید. حال یک کلاس دیگر را به نام Post در همین پوشه درست کرده و بدین صورت پیاده سازی مینماییم.
using System.ComponentModel.DataAnnotations.Schema; namespace Console1.Models { public class Post : IUser { public int Id { get; set; } public string Context { get; set; } public int UserId { get; set; } [ForeignKey(nameof(UserId))] public User User { get; set; } } }
واضح است که relation از نوع one to many برقرار است و هر User میتواند n تا Post داشته باشد.
خوب تا اینجا کافیست و میخواهیم مدلهای خود را با استفاده از EF به Context معرفی کنیم. میتوانیم در همین پوشه کلاسی را به نام Context ساخته و بصورت زیر بنویسیم
using System.Data.Entity; namespace Console1.Models { public class Context : DbContext { public Context() : base("Context") { } public DbSet<User> Users { get; set; } public DbSet<Post> Posts { get; set; } } }
در اینجا مشخص کردهایم که دو Dbset از نوع User و Post را داریم. بدین معنا که EF دو table را برای ما تولید خواهد کرد. همچنین نام کلید رشتهی اتصالی به دیتابیس خود را نیز، Context معرفی کردهایم.
خوب تا اینجا قسمت اول پروژهی خود را تکمیل کردهایم. الان میتوانیم با استفاده از Migration دیتابیس خود را ساخته و همچنین رکوردهایی را بدان اضافه کنیم. در Package Manager Console خود دستور زیر را وارد نمایید:
enable-migrations
به صورت خودکار پوشهای به نام Migrations ساخته شده و درون آن Configuration.cs قرار میگیرد که آن را بدین صورت تغییر میدهیم:
namespace Console1.Migrations { using Models; using System.Data.Entity.Migrations; internal sealed class Configuration : DbMigrationsConfiguration<Console1.Models.Context> { public Configuration() { AutomaticMigrationsEnabled = true; } protected override void Seed(Console1.Models.Context context) { context.Users.AddOrUpdate(x => x.Id, new User { Id = 1, Name = "aaa", Age = 30, Type = UserType.Admin }, new User { Id = 2, Name = "bbb", Age = 20, Type = UserType.Ordinary }, new User { Id = 3, Name = "ccc", Age = 25, Type = UserType.Ordinary } ); context.Posts.AddOrUpdate(x => x.Id, new Post { Context = "ccc 1", UserId = 3 }, new Post { Context = "bbb 1", UserId = 2 }, new Post { Context = "bbb 2", UserId = 2 }, new Post { Context = "aaa 1", UserId = 1 }, new Post { Context = "bbb 3", UserId = 2 }, new Post { Context = "ccc 2", UserId = 3 }, new Post { Context = "ccc 3", UserId = 3 } ); context.SaveChanges(); } } }
در متد seed، رکوردهای اولیه را به شکل فوق وارد کرده ایم (رکوردها فقط به منظور تست میباشند*). در کنسول دستور Update-database را ارسال کرده، دیتابیس تولید خواهد شد.
قطعا مراحل بالا کاملا بدیهی بوده و نوشتن آنها بدین دلیل بوده که در Repository که الان میخواهیم شروع به نوشتنش کنیم به مدلهای فوق نیاز داریم تا بصورت کاملا عملی با مراحل کار آشنا شویم.
حال میخواهیم به پیاده سازی بخش اصلی این مقاله یعنی repository که از Row Level Security پشتیبانی میکند بپردازیم. در ریشهی پروژهی خود پوشهای را به نام Repository ساخته و درون آن کلاسی را به نام GenericRepository میسازیم. پروژهی شما هم اکنون باید ساختاری شبیه به این را داشته باشد.
GenericRepository.cs را اینگونه پیاده سازی مینماییم
using Console1.Models; using System; using System.Linq; using System.Linq.Dynamic; using System.Linq.Expressions; namespace Console1.Repository { public interface IGenericRepository<T> { IQueryable<T> CustomizeGet(Expression<Func<T, bool>> predicate); void Add(T entity); IQueryable<T> GetAll(); } public class GenericRepository<TEntity, DbContext> : IGenericRepository<TEntity> where TEntity : class, new() where DbContext : Models.Context, new() { private DbContext _entities = new DbContext(); public IQueryable<TEntity> CustomizeGet(Expression<Func<TEntity, bool>> predicate) { IQueryable<TEntity> query = _entities.Set<TEntity>().Where(predicate); return query; } public void Add(TEntity entity) { int userId = Program.UserId; // یوزد آی دی بصورت فیک ساخته شده // اگر از آیدنتیتی استفاده میکنید میتوان آی دی و هر چیز دیگری که کلیم شده را در اختیار گرفت if (typeof(IUser).IsAssignableFrom(typeof(TEntity))) { ((IUser)entity).UserId = userId; } _entities.Set<TEntity>().Add(entity); } public IQueryable<TEntity> GetAll() { IQueryable<TEntity> result = _entities.Set<TEntity>(); int userId = Program.UserId; // یوزد آی دی بصورت فیک ساخته شده // اگر از آیدنتیتی استفاده میکنید میتوان آی دی و هر چیز دیگری که کلیم شده را در اختیار گرفت if (typeof(IUser).IsAssignableFrom(typeof(TEntity))) { User me = _entities.Users.Single(c => c.Id == userId); if (me.Type == UserType.Admin) { return result; } else if (me.Type == UserType.Ordinary) { string query = $"{nameof(IUser.UserId).ToString()}={userId}"; return result.Where(query); } } return result; } public void Commit() { _entities.SaveChanges(); } } }
1) یک اینترفیس Generic را به نام IGenericRepository داریم که کلاس GenericRepository قرار است آن را پیاده سازی نماید.
2) این اینترفیس شامل متدهای CustomizeGet است که فقط یک predicate را گرفته و خیلی مربوط به این مقاله نیست (صرفا جهت اطلاع). اما متد Add و GetAll بصورت مستقیم قرار است هدف row level security را برای ما انجام دهند.
3) کلاس GenericRepository دو Type عمومی را به نام TEntity و DbContext گرفته و اینترفیس IGenericRepository را پیاده سازی مینماید. همچنین صریحا اعلام کردهایم TEntity از نوع کلاس و DbContext از نوع Context ایی است که قبلا نوشتهایم.
4) پیاده سازی متد CustomizeGet را مشاهده مینمایید که کوئری مربوطه را ساخته و بر میگرداند.
5) پیاده سازی متد Add بدین صورت است که به عنوان پارامتر، TEntity را گرفته (مدلی که قرار است save شود). بعد مشاهد میکنید که من به صورت hard code به UserId مقدار دادهام. قطعا میدانید که برای این کار به فرض اینکه از Asp.net Identity استفاده میکنید، میتوانید Claim آن Id کاربر Authenticate شده را بازگردانید.
با استفاده از IsAssignableFrom مشخص کردهایم که آیا TEntity یک Typeی از IUser را داشته است یا خیر؟ در صورت true بودن شرط، UserId را به TEntity اضافه کرده و بطور مثال در Serviceهای خود نیازی به اضافه کردن متوالی این فیلد نخواهید داشت و در مرحلهی بعد نیز آن را به entity_ اضافه مینماییم.
مشاهده مینمایید که این متد به قدری انعطاف پذیری دارد که حتی مدلهای مختلف به صورت کاملا یکپارچه میتوانند از آن استفاده نمایند.
6) به جالبترین متد که GetAll میباشد میرسیم. ابتدا کوئری را از آن Entity ساخته و در مرحلهی بعد مشخص مینماییم که آیا TEntity یک Typeی از IUser میباشد یا خیر؟ در صورت برقرار بودن شرط، User مورد نظر را یافته در صورتیکه Typeی از نوع Admin داشت، همهی مجموعه را برخواهیم گرداند (Admin میتواند همهی پستها را مشاهده نماید) و در صورتیکه از نوع Ordinary باشد، با استفاده از dynamic linq، کوئری مورد نظر را ساخته و شرط را ایجاد میکنیم که UserId برابر userId مورد نظر باشد. در این صورت بطور مثال همهی پستهایی که فقط مربوط به user خودش میباشد، برگشت داده میشود.
نکته: برای دانلود dynamic linq کافیست از طریق nuget آن را جست و جو نمایید: System.Linq.Dynamic
و اگر هم از نوع IUser نبود، result را بر میگردانیم. بطور مثال فرض کنید مدلی داریم که قرار نیست security روی آن اعمال شود. پس کوئری ساخته شده قابلیت برگرداندن همهی رکوردها را دارا میباشد.
7) متد Commit هم که پرواضح است عملیات save را اعمال میکند.
قبلا در قسمت Seed رکوردهایی را ساخته بودیم. حال میخواهیم کل این فرآیند را اجرا نماییم. Program.cs را از ریشهی پروژهی خود باز کرده و اینگونه تغییر میدهیم:
using System; using Console1.Models; using Console1.Repository; using System.Collections.Generic; using System.Linq; namespace Console1 { public class Program { public static int UserId = 1; //fake userId static void Main() { GenericRepository<Post, Context> repo = new GenericRepository<Post, Context>(); List<Post> posts = repo.GetAll().ToList(); foreach (Post item in posts) Console.WriteLine(item.Context); Console.ReadKey(); } } }
همانطور که ملاحظه میکنید، UserId به صورت fake ساخته شده است. آن چیزی که هم اکنون در دیتابیس رفته، بدین صورت است که UserId = 1 برابر Admin و بقیه Ordinary میباشند. در متد Main برنامه، یک instance از GenericRepository را گرفته و بعد با استفاده از متد GetAll و لیست کردن آن، همهی رکوردهای مورد نظر را برگردانده و سپس چاپ مینماییم. در صورتی که UserId برابر 1 باشد، توقع داریم که همهی رکوردها بازگردانده شود:
حال کافیست مقدار userId را بطور مثال تغییر داده و برابر 2 بگذاریم. برنامه را اجرا کرده و مشاهد میکنیم که با تغییر یافتن userId، عملیات مورد نظر متفاوت میگردد و به صورت زیر خواهد شد:
میبینید که تنها با تغییر userId رفتار عوض شده و فقط Postهای مربوط به آن User خاص برگشت داده میشود.
از همین روش میتوان برای طراحی Repositoryهای بسیار پیچیدهتر نیز استفاده کرد و مقدار زیادی از validationها را به طور مستقیم بدان واگذار نمود!
دانلود کدها در Github
اگر به تصویر دقت کنید، در ستون Model آن، اطلاعات باینری ذخیره شدهاند. شاید در وهلهی اول اینطور به نظر برسد که این ستون حاوی هش نقل و انتقالات صورت گرفتهاست؛ اما ... خیر. اطلاعات این ستون، GZip شدهی یک رشتهی XML ایی یا همان EDMX معادل مدلها و نگاشتهای برنامه است.
در کدهای ذیل، نمونه مثالی را از نحوهی خواندن این اطلاعات، مشاهده میکنید:
using System; using System.Collections.Generic; using System.Data.SqlClient; using System.IO; using System.IO.Compression; using System.Xml.Linq; namespace EF_General { public static class InsideMigrations { public static void PrintFirstMigrationModel() { const string connectionString = "Data Source=(local);Initial Catalog=TestDbIdentity;Integrated Security = true"; const string sqlToExecute = "select top 1 model from __MigrationHistory"; using (var connection = new SqlConnection(connectionString)) { connection.Open(); using (var command = new SqlCommand(sqlToExecute, connection)) { using (var reader = command.ExecuteReader()) { if (!reader.HasRows) { throw new KeyNotFoundException("Nothing to display."); } while (reader.Read()) { var model = (byte[]) reader["model"]; var decompressed = decompressMigrationModel(model); Console.WriteLine(decompressed); } } } } } private static XDocument decompressMigrationModel(byte[] bytes) { using (var memoryStream = new MemoryStream(bytes)) { using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) { return XDocument.Load(gzipStream); } } } } }
بر اساس این اطلاعات، EF کاری به ساختار فعلی بانک اطلاعاتی شما ندارد. زمانیکه Add-Migration را اجرا میکنید، به جدول migrations مراجعه کرده، آخرین رکورد آنرا یافته و سپس اطلاعات آنرا از حالت فشرده خارج و XML نهایی آنرا استخراج میکند. در ادامه اطلاعات این فایل XML را با معادل مدلهای فعلی برنامه مقایسه میکند. اگر این دو یکی نبودند، اسکریپت اعمال تغییرات را تولید خواهد کرد.
مورد دیگری که در این جدول حائز اهمیت است، ستون ContextKey آن است: «رفع مشکل Migration با تغییر NameSpace در EF»
جایگزین کردن یک Subgrid با یک jqGrid کامل
خلاصهی عملیات جایگزینی یک Subgrid را توسط یک jqGrid کامل، در ذیل مشاهده میکنید:
$('#list').jqGrid({ caption: "آزمایش دوازدهم", //..........مانند قبل subGrid: true, subGridRowExpanded: grid1RowExpanded }); function grid1RowExpanded(subGridId, rowId) { var subgridTableId = subGridId + "_t"; var pagerId = "p_" + subgridTableId; var container = 'g_' + subGridId; $("#" + subGridId).html('<div dir="rtl" id="' + container + '" style="width:100%; height: 100%">' + '<table id="' + subgridTableId + '" class="scroll"></table><div id="' + pagerId + '" class="scroll"></div>'); var url = '@Url.Action("GetOrderDetails", "Home", routeValues: new { id = "js-id" })' .replace("js-id", encodeURIComponent(rowId)); // تزریق اطلاعات سمت کاربر به خروجی سمت سرور $("#" + subgridTableId).jqGrid({ caption: "ریز اقلام سفارش " + rowId, autoencode: true, //security - anti-XSS url: url, //..........مانند قبل }); }
امضای متد grid1RowExpanded، شامل id یک div است که گرید جدید در آن قرار خواهد گرفت، به همراه Id ردیفی که اطلاعات زیرگرید آن نیاز است از سرور واکشی شود.
بر مبنای subGridId، مانند قبل، یک جدول و یک div را برای نمایش jgGrid و pager آن به صفحه به صورت پویا اضافه میکنیم.
سپس تعاریف jqGrid آن مانند قبل است و نکتهی خاصی ندارد. بدیهی است گرید جدید نیز میتواند در صورت نیاز یک subgrid دیگر داشته باشد.
در اینجا تنها نکتهی مهم آن نحوهی ارسال اطلاعات rowId به سرور است. اکشن متدی که قرار است اطلاعات زیر گرید را تامین کند، یک چنین امضایی دارد:
public ActionResult GetOrderDetails(int id, JqGridRequest request)
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
jqGrid12.zip
- تمام اکانتهای مدیریتی توکار SQL Server را حذف کردهاید (یا برایتان حذف کردهاند).
- بجز کاربر SA، تمام کاربران را از نقش SYSADMIN حذف کردهاید؛ شامل تمام اکانتهای ویندوزی و همچنین خود SQL Server.
- پسورد SA را هم فراموش کردهاید یا ندارید.
خوب، الان چکار میخواهید بکنید؟!
احتمالا نصب مجدد سرور را پیشنهاد دهید یا بانک اطلاعاتی Master را بازسازی کنید که در هر دو حالت تمام تنظیمات سرور را از دست خواهید داد. روش دیگری هم بدون از دست دادن تنظیمات سرور وجود دارد که در ادامه آنرا بررسی خواهیم کرد.
افزودن یک اکانت مدیریتی جدید به SQL Server
اگر دسترسی کامل مدیریتی خود را به SQL Server از دست دادهاید باید ابتدا به آن سرور لاگین کنید؛ با این فرض که کاربر وارد شده به سیستم، جزو local administrators group است.
C:\>sqlcmd -S . 1> create login [MachineName\TestUser] from windows; 2> go 1> exec sp_addsrvrolemember 'MachineName\TestUser','sysadmin' 2> go 1> select is_srvrolemember('sysadmin', 'MachineName\TestUser') 2> go ----------- 1 (1 rows affected) 1>
الف) اجرای sqlcmd با پارامتر S و مشخص سازی وهلهی مورد نظر
البته حالت کامل آن در صورتیکه از وهلهی پیش فرض استفاده نمیکنید SQLCMD –S Server_Name\Instance_Name است. S نیز در اینجا باید با حروف بزرگ نوشته شود.
ب) create login را بر روی یکی از اکانتهای محلی سیستم اجرا کنید. مثلا MachineName\administrator یا هر اکانت موجود دیگری که لازم است. نام آن نیز باید حتما به شکل server_name\user_name باشد.
در حین استفاده از sqlcmd، هر فرمان زمانی اجرا میشود که در سطر پس از آن، یک go نوشته شده و enter شود.
ج) سپس توسط sp_addsrvrolemember به این اکانت اضافه شده، دسترسی sysadmin بدهید.
برای آزمایش آن فقط کافی است از متد is_srvrolemember برای کوئری گرفتن استفاده کنید و یا سعی کنید توسط اکانت اضافه شده، به SQL Server لاگین کنید. این اکانت اکنون در قسمت Security/logins قابل مشاهده است.
اگر نمیخواهید از اکانتهای ویندوزی استفاده کنید، create login را به نحو ذیل مقدار دهی کنید:
C:\>sqlcmd -S . 1> use master 2> go Changed database context to 'master'. 1> create login test_user with password='123#123' 2> go 1> exec sp_addsrvrolemember 'test_user','sysadmin' 2> go 1>
try { //انتخاب فایل اکسل OpenFileDialog ofd = new OpenFileDialog(); ofd.Title = "انتخاب فایل اکسل حاوی اطلاعات"; ofd.Filter = "فایل اکسل 2003 (*.xls)|*.xls|فایل اکسل 2007 به بعد(*.xlsx)|*.xlsx"; if (ofd.ShowDialog() == DialogResult.OK) { string excelConnectionString = ""; string SourceFilePath = ofd.FileName; //ایجاد کانکشن استرینگ برا خواندن کل اطلاعات از فایل اکسل و ریختن آنها در یک دیتاتیبل به نام dt if (System.IO.Path.GetExtension(ofd.FileName) == ".xlsx") //تشخیص نوع فایل اکسل برای ایجاد کانکشن استرینگ برای نسخههای مختلف اکسل excelConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + SourceFilePath + ";Extended Properties=Excel 12.0"; else if (System.IO.Path.GetExtension(ofd.FileName) == ".xls") excelConnectionString = "Provider=Microsoft.Jet.Oledb.4.0;Data Source=" + SourceFilePath + ";Extended Properties=Excel 8.0"; DataTable dt = new DataTable("tblinfos"); using (System.Data.OleDb.OleDbConnection connection = new System.Data.OleDb.OleDbConnection(excelConnectionString)) { connection.Open(); System.Data.OleDb.OleDbDataAdapter da = new System.Data.OleDb.OleDbDataAdapter("SELECT * FROM [List$]", connection);//List نام شیت در فایل اکسل است da.Fill(dt); connection.Close(); } using (System.Data.OleDb.OleDbConnection connection = new System.Data.OleDb.OleDbConnection(excelConnectionString)) { this.Cursor = Cursors.WaitCursor; connection.Open(); //ایجاد ارتباط با بانک اس کیو ال string sqlConnectionString = @"Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\BankDPR.mdf;Max Pool Size=6000; Connection Timeout=50;Integrated Security=True;User Instance=True"; Dataaccess db = new Dataaccess(); DataTable dtr = db.select("Select Top(1) * From tblinfos");//بدست آورن نام ستونهای جدول مورد نظر برای تطبیق با ستونهای فایل اکسل //******************* Fast Copy using (System.Data.SqlClient.SqlBulkCopy bulkCopy = new System.Data.SqlClient.SqlBulkCopy(sqlConnectionString, System.Data.SqlClient.SqlBulkCopyOptions.KeepIdentity))//تعریف یک شی از کلاس SqlBulkCopy { for (int i = 1; i < dtr.Columns.Count; i++) { bulkCopy.ColumnMappings.Add(dt.Columns[i - 1].Caption, dtr.Columns[i].Caption);//با استفاده از خاصیت ColumnMappings نام ستونهای فایل اکسل با نام ستونهای جدول مورد نظر بانک sql تطبیق داده میشود } bulkCopy.DestinationTableName = "tblinfos";//مشخص نمودن نام جدول که قرار است اطلاعات درون ان کپی گردد bulkCopy.WriteToServer(dt);//انجام عملیات کپی اطلاعات از دیتاتیبل با نام dt به بانک } this.Cursor = Cursors.Arrow; MessageBox.Show("اطلاعات از فایل شما خوانده شد"); } } } catch (Exception ex) { this.Cursor = Cursors.Arrow; MessageBox.Show(ex.Message, "error"); }
<compilation xdt:Transform="RemoveAttributes(debug)" />
<customErrors mode="Off" defaultRedirect="~/GenericErrorPage.aspx"> <error statusCode="404" redirect="~/GenericErrorPage.aspx" /> </customErrors>
<customErrors mode="RemoteOnly" xdt:Transform="Replace" defaultRedirect="~/GenericErrorPage.aspx"> <error statusCode="404" redirect="~/GenericErrorPage.aspx" /> </customErrors>
<connectionStrings> <add name="TestContext" connectionString="Data Source=Server1;Password=****;User ID=sa; Initial Catalog=Test;Integrated Security=True" xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/> </connectionStrings>
- فعال کردن خاصیت Contained Databases
قبل از استفاده از Contained Databases می بایست این امکان را فعال کرد. برای این کار میتوانید از SQL Server Management Studio یا T-SQL commands استفاده نمایید. بر روی نام Instance راست کلیت کنید و گزینه Properties را انتخاب نمایید. از گزینه Advanced که در شکل زیر مشاهده مینمایید خاصیت Enable Contained Databases را بر روی True قرار دهید.
یا میتوایند از sp_configure این کار را انجام دهید.دستورات زیر این موضوع را نشان میدهد.
sp_configure 'show advanced options',1 GO RECONFIGURE WITH OVERRIDE GO sp_configure 'contained database authentication',1 GO RECONFIGURE WITH OVERRIDE GO
- ایجاد یا تغییر یک پایگاه داده از نوع Contained Databases
برای ایجاد یک پایگاه داده با این خاصیت یا تغییر پایگاه داده موجود کافیست مقدار گزینه Containment type را بر روی Partial قرار دهید. برای پایگاه داده موجود از پنجره Properties پایگاه داده صفحه Options را انتخاب کنید.
- ایجاد یک کاربر برای پایگاه داده Contained Databases
برای تعریف یک کاربر در سطح پایگاه داده پوشه Security پایگاه داده خود را باز کنید بر روی پوشه Users راست کیلک و گزینه New User را انتخاب نمایید از گزینه User type که در شکل زیر نشان داده شده است SQL user with password را انتخاب نمایید و نام کاربر و رمز عبور و تکرار آن را وارد نمایید. کاربر ایجاد شده در سطح پایگاه داده میباشد و با انتقال به سرور دیگر نیر قابل دسترسی میباشد.
- اتصال به پایگاه داده Contained Databases
برای اتصال به پایگاه داده کافیست در حالت SQL Server Authentication نام کاربری و رمز عبور جدید را وارد و گزینه Options را انتخاب و از برگه Additional Connection Parameters نام پایگاه داده مورد نظر را مانند شکل زیر وارد نمایید پس از ورود تنها پایگاه داده خود را مشاهده مینمایید. یکی از کاربرهای این قابلیت برای مدیران سرور پایگاه داده میباشد که بدون استفاده از مجوز sysadmin به کاربران اجازه دسترسی را میدهد.
این روزها با وجود ORMs ، کوئری SQL نوشتن شبیه به دورانی شده که با وجود زبانهای سطح بالا، عدهای علاقمند هستند با استفاده از زبان اسمبلی برنامه نویسی کنند! WCF RIA Services به صورت پیش فرض از entity framework استفاده میکند (هر چند میتوان از سایر ORMs هم استفاده کرد)، بنابراین عنوان صحیحتر بحث این خواهد بود: چگونه خروجی SQL تولید شده توسط Entity framework را بررسی کنیم؟
الف) استفاده از SQL Server profiler
اولین برنامهای که از سالها قبل، حتی پیش از ظهور ORMs وجود داشته، برنامهی SQL server profiler است، که عموما در مسیر ذیل قابل دستیابی است:
نکته مهم:
حین کار با SQL Server profiler ، ممکن است انبوهی از کوئریهای دیگر مثلا مرتبط با SQL Server agent یا reporting services و غیره نیز لاگ شوند. اما الان ما تنها به کوئریهای برنامهی خود نیاز داریم. برای این منظور به کانکشن استرینگ خود، گزینهی Application Name=My Application Name را نیز اضافه کنید:
<connectionStrings>
<add name="dmEntities" connectionString="metadata=res://*/Models.dmDataModel.csdl|res://*/Models.dmDataModel.ssdl|res://*/Models.dmDataModel.msl;provider=System.Data.SqlClient;provider connection string="Data Source=(local);Initial Catalog=dm;Integrated Security=True;Application Name=My Application Name;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" />
</connectionStrings>
اکنون اگر برنامه را با پروفایلر مورد بررسی قرار دهید خروجی به صورت زیر خواهد بود:
برای فیلتر کردن Application Name مورد نظر، در ابتدای کار که یک سشن جدید را آغاز میکنید به برگهی events selection مراجعه کرده و بر روی دکمهی column filter کلیک کنید. گزینهی application name را در صفحهی باز شده انتخاب نموده و در قسمت Like آن مطابق تصویر زیر ، نام برنامهی خود را وارد نمائید:
ب) استفاده از IntelliTrace در VS.NET 2010
برنامه را در حالت دیباگ در VS.NET 2010 اجرا کنید. در هر لحظهای میتوان روی گزینهی Break all کلیک کرد و خروجی SQL تولید شده را نیز علاوه بر اطلاعات دیگر مشاهده نمود:
ج) استفاده از برنامهی حرفهای entity framework profiler
این برنامه از هر دو مورد قبل کاملتر بوده و اساسا برای لاگ کردن کوئریها، مدت زمان اجرا، گزارشگیری از وضعیت برنامه، کدامیک از کوئریها سنگینتر هستند، حتی از طریق کدام متد فراخوانی شدهاند، ارائهی گزارشات و راهنماییهایی در مورد چگونگی بهبود کارآیی برنامهی تهیه شده و امثال آن کاربرد دارد.
استفاده از آن هم بسیار ساده است. ابتدا ارجاعی را به اسمبلی HibernatingRhinos.Profiler.Appender.v4.0 به پروژهی ASP.NET خود اضافه کنید (همان پروژهی هوست مربوط به WCF RIA Service ما). سپس به فایل Global.asax.cs برنامه مراجعه کرده و یک سطر ذیل را اضافه کنید:
protected void Application_Start(object sender, EventArgs e)
{
HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();
}
از این پس تنها کافی است برنامهی پروفایلر در حال اجرا بوده و برنامه شما نیز اجرا شود. کلیهی تبادلات با دیتابیس لاگ خواهند شد.
سه مطلب کوتاه
مشکل فایرفاکس با سایتهای msdn و codeplex
هر از چند گاهی در بلاگهای msdn و یا سایت codeplex با خطای زیر از طرف سایت مواجه میشوم:
Bad Request - Request Too Long
HTTP Error 400. The size of the request headers is too long.
اگر با این مشکل مواجه شدید، تمامی کوکیهای مربوط به سایتهای مذکور را یافته و حذف کنید.
به نظر باگی در فایرفاکس در این زمینه سبب میشود که کوکیهای تمام زیر سایتهای فوق با هم ترکیب شده و رشتهای بسیار طولانی بجای کوکی اصلی آن زیر سایت به هاست ارسال شود.
که البته عکس العمل سایتهای مایکروسافت از دیدگاه امنیتی هم جالب توجه است (برای برنامه نویسهای وب).
IE8 و ارائهی fakepath بجای آدرس فایل
کنترل استاندارد آپلود فایل در مرورگرهای جدید، دیگر آدرس محلی فایل را حتی در اختیار اسکریپتهای سمت کاربر نیز قرار نمیدهند. فایرفاکس مدت زیادی است که این مورد را پیاده سازی کرده. اما IE بجای اینکه یک رشتهی خالی را بازگشت دهد مسیر c:\fakepath را ارائه خواهد داد (fakepath جزو استاندارد html 5 است). اگر احیانا با این مورد برخورد داشتید، با استفاده از تنظیم زیر میتوان مانند سابق مسیر کامل را نیز دریافت کرد:
Internet Explorer -> Tools -> Internet Option -> Security -> Custom ->
find the "Include local directory path when uploading files to a server"
-> click on "Enable"
اهمیت این مورد هم برای من این است که IE، یعنی همان مرورگر کاری در اکثر شرکتها (و این مورد فوق را به سادگی از طریق گروپ پالیسی میتوان به تمام کامپیوترها اعمال کرد).
علت تایم آوت و باز نشدن یک سری از سایتها در ایران
مدت زیادی این سؤال برای من وجود داشت که وجه مشترک سایتهایی مانند dotnetkicks.com ، summerofnhibernate.com ، lessthandot.com و امثال آن چیست که از این طرف باز نمیشوند؟
اگر به آدرسهای فوق که به سایت domaintools.com ختم میشوند، مراجعه کنید و سپس برگهی Server Stats آنها را ملاحظه نمائید، همگی توسط Godaddy.com Inc هاست میشوند. این شرکت غیرمحترم، IP های ایرانی را بسته است (مطلب جدیدی هم نیست).