ابتدا بسته زیر را از طریق nuget نصب نمایید:
dotnet add package MongoDB.Driver
سپس مدلهای زیر را ایجاد نمایید:
public class BaseModel { public BaseModel() { CreationDate=DateTime.Now; } public string Id { get; set; } public DateTime CreationDate { get; set; } public bool IsRemoved { get; set; } public DateTime? ModificationDate { get; set; } }
این مدل شامل یک کلاس پایه برای id,CreationDate,ModificationDate,IsRemoved میباشد که بسیار شبیه مدلهایی است که عموما در EntityFramework تعریف میکنیم.
برای اینکه فیلد Id به صورت objectId ایجاد شود ولی به صورت رشتهای استفاده شود ابتدا ویژگی BsonId را در بالای آن تعریف کرده تا به عنوان شناسه یکتا سند شناخته شود و سپس با استفاده از ویژگی BsonRepresentation اعلام میکنیم که کار تبدیل به رشته و بلعکس آن به صورت خودکار در پشت صحنه صورت بگیرد:
public class BaseModel { [BsonId] [BsonRepresentation((BsonType.ObjectId))] public string Id { get; set; } }
البته این حالت برای زمانی مناسب است که ما
در استفاده از ویژگیها محدودیتی نداشته باشیم؛ ولی در بسیاری از نرم افزارها که از
معماریهای چند لایه مانند لایه پیازی استفاده میشود استفاده از این خصوصیتها یعنی اعمال کارکرد کتابخانه بالاتر بر روی لایههای زیرین که هسته نرم افزار
شناخته میشوند که صحیح نبوده و باید توسط لایههای بالاتر این تغییرات اعمال شوند که
میتواند از طریق کلاس این کار را انجام دهید. به ازای هر مدل که نیاز به تغییرات
دارد، یک حالت جدید تعریف شده و در ابتدای برنامه در فایل Program.cs یا قبل از دات نت 6 در Startup.cs صدا زده میشوند.
BsonClassMap.RegisterClassMap<BaseModel>(map => { map.SetIdMember(map.GetMemberMap(x=>x.Id)); map.GetMemberMap(x => x.Id) .SetSerializer(new StringSerializer(BsonType.ObjectId)); });
یک نکته بسیار مهم: کلاس و متد BsonClassMap . RegisterClassMap قادر به اعمال تغییرات بر روی خصوصیتهای کلاس والد نیستند و آن خصوصیات حتما باید در آن کلاسی که آن را کانفیگ میکنید، تعریف شده باشند؛ یعنی چنین چیزی که در کد زیر میبینید در زمان اجرا با یک خطا مواجه خواهد شد:
public class Employee : BaseModel { public string FirstName { get; set; } public string LastName { get; set; } } //================= BsonClassMap.RegisterClassMap<Employee >(map => { map.SetIdMember(map.GetMemberMap(x=>x.Id)); map.GetMemberMap(x => x.Id) .SetSerializer(new StringSerializer(BsonType.ObjectId)); });
روش استفاده از مونگو در asp.net core به صورت زیر بسیار متداول میباشد که در قسمتهای پیشین هم در این مورد نوشته بودیم:
MongoDbContext
public interface IMongoDbContext { IMongoCollection<TEntity> GetCollection<TEntity>(); } public class MongoDbContext : IMongoDbContext { private readonly IMongoClient _client; private readonly IMongoDatabase _database; public MongoDbContext(string databaseName,string connectionString) { var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString)); _client = new MongoClient(settings); _database = _client.GetDatabase(databaseName); } public IMongoCollection<TEntity> GetCollection<TEntity>() { return _database.GetCollection<TEntity>(typeof(TEntity).Name.ToLower() + "s"); } }
سپس از طریق کد زیر IMongoDbContext را به سیستم تزریق وابستگیها معرفی میکنیم. الگوی استفاده شدهی در اینجا بر خلاف نسخههای sql که عموما به صورت AddScoped تعریف میشدند، در اینجا به صورت AddSingleton تعریف کردیم و نحوه پیاده سازی آن را نیز در طرف سمت راست به صورت صریح اعلام کردیم:
public static class MongoDbContextService { public static void AddMongoDbContext(this IServiceCollection services,string databaseName,string connectionString) { services.AddSingleton<IMongoDbContext>(serviceProvider => new MongoDbContext(databaseName, connectionString)); } } //=============== Program.cs builder.Services.AddMongoDbContext("bookstore", "mongodb://localhost:27017");
پیاده سازی SoftDelete در مونگو
در مونگو چیزی تحت عنوان Global Query Filter نداریم که تمام کوئری هایی که به سمت دیتابیس ارسال میشوند، توسط کانتکس اطلاح شوند؛ بدین جهت برای پیاده سازی این خصوصیت میتوان اینترفیسی با نام <IRepository<T را به شکل زیر طراحی نماییم:
public interface IRepository<T> where T : BaseModel { IMongoCollection<T> GetCollection(); IMongoQueryable<T> GetFilteredCollection(); } public class Repository<T> : IRepository<T> where T:BaseModel { private IMongoDbContext _mongoDbContext; public Repository(IMongoDbContext mongoDbContext) { _mongoDbContext = mongoDbContext; } public IMongoCollection<T> GetCollection() { return _mongoDbContext.GetCollection<T>(); } public IMongoQueryable<T> GetFilteredCollection() { var query= _mongoDbContext.GetCollection<T>().AsQueryable(); //================= Global Query Filters ==================== //Filter 1 query=query.Where(x => x.RemovedAt.HasValue == false); //============================================================== return query; } }
این کلاس یا اینترفیس شامل دو متد هستند که کلاس جنریک آنها باید از BaseModel ارث بری کرده باشد و اولین متد، تنها یک کالکشن بدون هیچگونه فیلتری است که میتواند نقش متد IgnoreQueryFilters را بازی کند و دیگری GetFilteredCollection است که در این متد ابتدا کالکشنی دریافت شده و سپس آن را به حالت کوئری تغییر داده و فیلترهای مورد نظر، مانند حذف منطقی را پیاده سازی میکنیم:
public interface IRepository<T> where T : BaseModel { IMongoCollection<T> GetCollection(); IMongoQueryable<T> GetFilteredCollection(); } public class Repository<T> : IRepository<T> where T:BaseModel { private IMongoDbContext _mongoDbContext; public Repository(IMongoDbContext mongoDbContext) { _mongoDbContext = mongoDbContext; } public IMongoCollection<T> GetCollection() { return _mongoDbContext.GetCollection<T>(); } public IMongoQueryable<T> GetFilteredCollection() { var query= _mongoDbContext.GetCollection<T>().AsQueryable(); //================= Global Query Filters ==================== //Filter 1 query=query.Where(x => x.RemovedAt.HasValue == false); //============================================================== return query; } }
اصلاح تاریخ ویرایش در مدل
در EF به لطف dbset و همچنین ChangeTracking امکان شناسایی حالتها وجود دارد و میتوانید در متدی مانند saveChanges مقدار تاریخ ویرایش را تنظیم نمود. برای مدلهای منگو چنین چیزی وجود ندارد و به همین دلیل چند روش زیر پیشنهاد میگردد:
یک. استفاده از اینترفیس INotifyPropertyChanged یا جهت حذف کدهای تکراری نیز از الگوی AOP بهره بگیرید.
دو. استفاده از یک <Repository<T همانند بالا که شامل متدهای داخلی Update و Delete هستند که در آنجا میتوانید این مقادیر را به صورت مستقیم تغییر دهید.
موشکافی ASP.NET 5
ASP.NET 5 is an entirely new web stack, rebuilt from the ground up to run fast, cross-platform, and completely configurable. Join David Fowler, one of the architects of ASP.NET 5, as he goes deep into the core of ASP.NET 5. What's the hosting model? How does the HTTP stack work? How do middleware and NuGet packages get pulled in to compose your application? How do the different servers (IIS, Kestrel, WebListener, etc.) actually work? How can you take advantage of these things to build lean, fast, optimized web apps on the new ASP.NET 5 platform? Come find out!
LINQ یک DLS بر مبنای .NET می باشد که برای پرس و جو در منابع داده ای مانند پایگاههای داده ، فایلهای XML و یا لیستی از اشیاء درون حافظه کاربرد دارد.
یکی از بزرگترین مزیتهای آن Syntax آسان و خوانا آن میباشد.
LINQ از 2 نوع نمادگذاری پشتیبانی میکند:
- Inline LINQ یا query expressions :
var result = from product in dbContext.Products where product.Category.Name == "Toys" where product.Price >= 2.50 select product.Name;
- Fluent Syntax :
var result = dbContext.Products .Where(p => p.Category.Name == "Toys" && p.Price >= 250) .Select(p => p.Name);
در پرس و چوهای بالا فیلدهای مورد نیاز در قسمت Select در زمان Compile شناخته شده هستند . اما گاهی ممکن است فیلدهای مورد نیاز در زمان اجرا مشخص شوند.
به عنوان مثال یک گزارش ساز پویا که کاربر مشخص میکند چه ستون هایی در خروجی نمایش داده شوند یا یک جستجوی پیشرفته که ستونهای خروجی به اختیار کاربر در زمان اجرا مشخص میشوند.
این مدل را در نظر داشته باشید :
public class Student { public int Id { get; set; } public string Name { get; set; } public string Field1 { get; set; } public string Field2 { get; set; } public string Field3 { get; set; } public static IEnumerable<Student> GetStudentSource() { for (int i = 0; i < 10; i++) { yield return new Student { Id = i, Name = "Name " + i, Field1 = "Field1 " + i, Field2 = "Field2 " + i, Field3 = "Field3 " + i }; } } }
ستونهای کلاس Student را در رابط کاربری برنامه جهت انتخاب به کاربر نمایش میدهیم. سپس کاربر یک یا چند ستون را انتخاب میکند که قسمت Select کوئری برنامه باید بر اساس فیلدهای مورد نظر کاربر مشخص شود.
یکی از روش هایی که میتوان از آن بهره برد استفاده از کتاب خانه Dynamic LINQ معرفی شده در اینجا می باشد.
این کتابخانه جهت سهولت در نصب به کمک NuGet در این آدرس قرار دارد.
فرض بر این است که فیلدهای انتخاب شده توسط کاربر با "," از یکدیگر جدا شده اند.
public class Program { private static void Main(string[] args) { System.Console.WriteLine("Specify the desired fields : "); string fields = System.Console.ReadLine(); IEnumerable<Student> students = Student.GetStudentSource(); IQueryable output = students.AsQueryable().Select(string.Format("new({0})", fields)); foreach (object item in output) { System.Console.WriteLine(item); } System.Console.ReadKey(); } }
همانطور که در عکس ذیل مشاهده میکنید پس از اجرای برنامه ، فیلدهای انتخاب شده توسط کاربر از منبع دادهی دریافت شده و در خروجی نمایش داده شده اند.
این روش مزایا و معایب خودش را دارد ، به عنوان مثال خروجی یک لیست از شیء Student نیست یا این Select فقط برای روی یک شیء IQueryable قابل انجام است.
روش دیگری که میتوان از آن بهره جست استفاده از یک متد کمکی جهت تولید پویای عبارت Lambda ورودی Select می باشد :
public class SelectBuilder <T> { public static Func<T, T> CreateNewStatement(string fields) { // input parameter "o" var xParameter = Expression.Parameter(typeof(T), "o"); // new statement "new T()" var xNew = Expression.New(typeof(T)); // create initializers var bindings = fields.Split(',').Select(o => o.Trim()) .Select(o => { // property "Field1" var property = typeof(T).GetProperty(o); // original value "o.Field1" var xOriginal = Expression.Property(xParameter, property); // set value "Field1 = o.Field1" return Expression.Bind(property, xOriginal); } ).ToList(); // initialization "new T { Field1 = o.Field1, Field2 = o.Field2 }" var xInit = Expression.MemberInit(xNew, bindings); // expression "o => new T { Field1 = o.Field1, Field2 = o.Field2 }" var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter); // compile to Func<T, T> return lambda.Compile(); } }
IEnumerable<Student> result = students.Select(SelectBuilder<Student>.CreateNewStatement("Field1, Field2")).ToList(); foreach (Student student in result) { System.Console.WriteLine(student.Field1); }
ابتدا فیلدهای انتخابی کاربر که با "," جدا شده اند به ورودی پاس داده میشود سپس یک statement خالی ایجاد میشود :
o=>new Student()
var property = typeof(T).GetProperty(o);
EF Code First #2
در بخشی گفتید که
[Column("MyTableKey")] public int Id { set; get; }
بازنویسی سطح دوم کش برای Entity framework 6
متدهای توکار استفاده از نوع دادهای XML - قسمت دوم
ابتدا جدول xmlTest را به همراه چند رکورد ثبت شده در آن، درنظر بگیرید:
CREATE TABLE xmlTest ( id INT IDENTITY PRIMARY KEY, doc XML ) GO INSERT xmlTest VALUES('<Person name="Vahid" />') INSERT xmlTest VALUES('<Person name="Farid" />') INSERT xmlTest VALUES('<Person name="Mehdi" /><Person name="Hamid" />') GO
استفاده از متد sql:column
در ادامه میخواهیم مقدار ویژگی name رکوردی را که نام آن Vahid است، به همراه id آن ردیف، توسط یک XQuery بازگشت دهیم:
SELECT doc.query(' for $p in //Person where $p/@name="Vahid" return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li> ') FROM xmlTest
در مورد متد data در قسمت قبل بیشتر بحث شد و از آن برای استخراج دادهی یک ویژگی در اینجا استفاده شدهاست. عبارات داخل {} نیز پویا بوده و به همراه سایر قسمتهای ثابت return، ابتدا محاسبه و سپس بازگشت داده میشود.
اگر این کوئری را اجرا کنید، ردیف اول آن مساوی عبارت زیر خواهد بود
<li>Vahid has id = 1</li>
یک روش برای حذف این ردیفهای خالی استفاده از متد exist است به شکل زیر:
SELECT doc.query(' for $p in //Person where $p/@name="Vahid" return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li> ') FROM xmlTest WHERE doc.exist(' for $p in //Person where $p/@name="Vahid" return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li> ')=1
روش دوم استفاده از یک derived table و بازگشت ردیفهای غیرخالی است:
SELECT * FROM ( (SELECT doc.query(' for $p in //Person where $p/@name="Vahid" return <li>{data($p/@name)} has id = {sql:column("xmlTest.id")}</li> ') AS col1 FROM xmlTest) ) A WHERE CONVERT(VARCHAR(8000), col1)<>''
استفاده از متد sql:variable
DECLARE @number INT = 1 SELECT doc.query(' for $p in //Person where $p/@name="Vahid" return <li>{data($p/@name)} has number = {sql:variable("@number")}</li> ') FROM xmlTest
استفاده از For XML برای دریافت یکبارهی تمام ردیفهای XML
اگر کوئری معمولی ذیل را اجرا کنیم:
SELECT doc.query('/Person') FROM xmlTest
اما اگر بخواهیم این سه ردیف را با هم ترکیب کرده و تبدیل به یک نتیجهی واحد کنیم، میتوان از For XML به نحو ذیل استفاده کرد:
DECLARE @doc XML SET @doc = (SELECT * FROM xmlTest FOR XML AUTO, ELEMENTS) SELECT @doc.query('/xmlTest/doc/Person')
بررسی متد xml.nodes
متد xml.nodes اندکی متفاوت است نسبت به تمام متدهایی که تاکنون بررسی کردیم. کار آن تجزیهی محتوای XML ایی به ستونها و سطرها میباشد. بسیار شبیه است به متد OpenXML اما کارآیی بهتری دارد.
DECLARE @doc XML =' <people> <person><name>Vahid</name></person> <person><name id="2">Farid</name></person> <person><name>Mehdi</name></person> <person><name>Hooshang</name><name id="1">Hooshi</name></person> <person></person> </people> '
در ادامه قصد داریم این اطلاعات را تبدیل به ردیفهایی کنیم که هر ردیف حاوی یک نام است. اولین سعی احتمالا استفاده از متد value خواهد بود:
SELECT @doc.value('/people/person/name', 'varchar(50)')
SELECT @doc.value('(/people/person/name)[1]', 'varchar(50)')
سعی بعدی استفاده از متد query است:
SELECT @doc.query('/people/person/name')
<name>Vahid</name> <name id="2">Farid</name> <name>Mehdi</name> <name>Hooshang</name> <name id="1">Hooshi</name>
الف) خروجی آن XML است.
ب) تمام اینها در طی یک ردیف و یک ستون بازگشت داده میشوند.
و این خروجی نیز چیزی نیست که برای ما مفید باشد. ما به ازای هر شخص نیاز به یک ردیف جداگانه داریم. اینجا است که متد xml.nodes مفید واقع میشود:
SELECT tab.col.value('text()[1]', 'varchar(50)') AS name, tab.col.query('.'), tab.col.query('..') from @doc.nodes('/people/person/name') AS tab(col)
هر ردیف حاصل از این جدول بازگشت داده شده، یک اشارهگر است. به همین جهت نمیتوان آنها را مستقیما نمایش داد. هر سطر آن، به نودی که با آن مطابق XQuery وارد شده تطابق داشته است، اشاره میکند. در اینجا مطابق کوئری نوشته شده، هر ردیف به یک نود name اشاره میکند. در ادامه برای استخراج اطلاعات آن میتوان از متد text استفاده کرد.
اگر قصد داشتید، اطلاعات کامل نود ردیف جاری را مشاهده کنید میتوان از
tab.col.query('.'),
روش دیگر بدست آوردن مقدار یک نود را در کوئری ذیل مشاهده میکنید؛ value دات و data دات. خروجی value مقدار آن نود است و خروجی data مقدار آن نود با فرمت XML.
SELECT tab.col.value('.', 'varchar(50)') AS name, tab.col.query('data(.)'), tab.col.query('.'), tab.col.query('..') from @doc.nodes('/people/person/name') AS tab(col)
همچنین اگر بخواهیم اطلاعات تنها یک نود خاص را بدست بیاوریم، میتوان مانند کوئری ذیل عمل کرد:
SELECT tab.col.value('name[.="Farid"][1]', 'varchar(50)') AS name, tab.col.value('name[.="Farid"][1]/@id', 'varchar(50)') AS id, tab.col.query('.') from @doc.nodes('/people/person[name="Farid"]') AS tab(col)
در مورد کار با جداول، بجای متغیرهای T-SQL نیز روال کار به همین نحو است:
DECLARE @tblXML TABLE ( id INT IDENTITY PRIMARY KEY, doc XML ) INSERT @tblXML VALUES('<person name="Vahid" />') INSERT @tblXML VALUES('<person name="Farid" />') INSERT @tblXML VALUES('<person />') INSERT @tblXML VALUES(NULL) SELECT id, doc.value('(/person/@name)[1]', 'varchar(50)') AS name FROM @tblXML
نکته : استفادهی وسیع SQL Server از XML برای پردازش کارهای درونی آن
بسیاری از ابزارهایی که در نگارشهای جدید SQL Server اضافه شدهاند و یا مورد استفاده قرار میگیرند، استفادهی وسیعی از امکانات توکار XML آن دارند. مانند:
Showplan، گرافهای dead lock، گزارش پروسههای بلاک شده، اطلاعات رخدادها، SSIS Jobs، رخدادهای Trace و ...
مثال اول: کدام کوئریها در Plan cache، کارآیی پایینی داشته و table scan را انجام میدهند؟
CREATE PROCEDURE LookForPhysicalOps (@op VARCHAR(30)) AS SELECT sql.text, qs.EXECUTION_COUNT, qs.*, p.* FROM sys.dm_exec_query_stats AS qs CROSS APPLY sys.dm_exec_sql_text(sql_handle) sql CROSS APPLY sys.dm_exec_query_plan(plan_handle) p WHERE query_plan.exist(' declare default element namespace "http://schemas.microsoft.com/sqlserver/2004/07/showplan"; /ShowPlanXML/BatchSequence/Batch/Statements//RelOp/@PhysicalOp[. = sql:variable("@op")] ') = 1 GO EXECUTE LookForPhysicalOps 'Table Scan' EXECUTE LookForPhysicalOps 'Clustered Index Scan' EXECUTE LookForPhysicalOps 'Hash Match'
اگر علاقمند هستید که اصل این اطلاعات را با فرمت XML مشاهده کنید، کوئری نوشته شده را تا پیش از where آن یکبار مستقلا اجرا کنید. ستون آخر آن query_plan نام دارد و حاوی اطلاعات XML ایی است.
مثال دوم: استخراج اپراتورهای رابطهای (RelOp) از یک Query Plan ذخیره شده
WITH XMLNAMESPACES(DEFAULT N'http://schemas.microsoft.com/sqlserver/2004/07/showplan') SELECT RelOp.op.value(N'../../@NodeId', N'int') AS ParentOperationID, RelOp.op.value(N'@NodeId', N'int') AS OperationID, RelOp.op.value(N'@PhysicalOp', N'varchar(50)') AS PhysicalOperator, RelOp.op.value(N'@LogicalOp', N'varchar(50)') AS LogicalOperator, RelOp.op.value(N'@EstimatedTotalSubtreeCost ', N'float') AS EstimatedCost, RelOp.op.value(N'@EstimateIO', N'float') AS EstimatedIO, RelOp.op.value(N'@EstimateCPU', N'float') AS EstimatedCPU, RelOp.op.value(N'@EstimateRows', N'float') AS EstimatedRows, cp.plan_handle AS PlanHandle, st.TEXT AS QueryText, qp.query_plan AS QueryPlan, cp.cacheobjtype AS CacheObjectType, cp.objtype AS ObjectType FROM sys.dm_exec_cached_plans cp CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) qp CROSS APPLY qp.query_plan.nodes(N'//RelOp') RelOp(op)
بررسی متد xml.modify
تا اینجا تمام کارهایی که صورت گرفت و نکاتی که بررسی شدند، به مباحث select اختصاص داشتند. اما insert، delete و یا update قسمتی از یک سند XML بررسی نشدند. برای این منظور باید از متد xml.modify استفاده کرد. از آن در عبارات update و یا set کمک گرفته شده و ورودی آن نباید نال باشد. در ادامه در طی مثالهایی این موارد را بررسی خواهیم کرد.
ابتدا فرض کنید که سند XML ما چنین شکلی را دارا است:
DECLARE @doc XML = ' <Invoice> <InvoiceId>100</InvoiceId> <CustomerName>Vahid</CustomerName> <LineItems> <LineItem> <Sku>134</Sku> <Quantity>10</Quantity> <Description>Item 1</Description> <UnitPrice>9.5</UnitPrice> </LineItem> <LineItem> <Sku>150</Sku> <Quantity>5</Quantity> <Description>Item 2</Description> <UnitPrice>1.5</UnitPrice> </LineItem> </LineItems> </Invoice> '
SET @doc.modify(' insert <InvoiceInfo><InvoiceDate>2014-02-10</InvoiceDate></InvoiceInfo> after /Invoice[1]/CustomerName[1] ') SELECT @doc
<Invoice> <InvoiceId>100</InvoiceId> <CustomerName>Vahid</CustomerName> <InvoiceInfo> <InvoiceDate>2014-02-10</InvoiceDate> </InvoiceInfo> <LineItems> ...
در SQL Server 2008 به بعد، امکان استفاده از متغیرهای T-SQL نیز در اینجا مجاز شدهاست:
SET @x.modify('insert sql:variable("@x") into /doc[1]')
The argument 1 of the XML data type method "modify" must be a string literal.
افزودن ویژگیهای جدید به یک سند XML توسط متد xml.modify
اگر بخواهیم یک ویژگی (attribute) جدید را به نود خاصی اضافه کنیم میتوان به نحو ذیل عمل کرد:
SET @doc.modify(' insert attribute status{"backorder"} into /Invoice[1] ') SELECT @doc
<Invoice status="backorder"> <InvoiceId>100</InvoiceId> ....
حذف نودهای یک سند XML توسط متد xml.modify
اگر بخواهیم تمام LineItemها را حذف کنیم میتوان نوشت:
SET @doc.modify('delete /Invoice/LineItems/LineItem') SELECT @doc
<Invoice status="backorder"> <InvoiceId>100</InvoiceId> <CustomerName>Vahid</CustomerName> <InvoiceInfo> <InvoiceDate>2014-02-10</InvoiceDate> </InvoiceInfo> <LineItems /> </Invoice>
به روز رسانی نودهای یک سند XML توسط متد xml.modify
اگر نیاز باشد تا مقدار یک نود را تغییر دهیم میتوان از replace value of استفاده کرد:
SET @doc.modify('replace value of /Invoice[1]/CustomerName[1]/text()[1] with "Farid" ') SELECT @doc
<Invoice status="backorder"> <InvoiceId>100</InvoiceId> <CustomerName>Farid</CustomerName> <InvoiceInfo> <InvoiceDate>2014-02-10</InvoiceDate> </InvoiceInfo> <LineItems /> </Invoice>
The target of 'replace value of' must be a non-metadata attribute or an element with simple typed content.
به روز رسانی نودهای خالی توسط متد xml.modify
باید دقت داشت، نودهای خالی (بدون مقدار)، مانند LineItems پس از delete کلیه اعضای آن در مثال قبل، قابل replace نیستند و باید مقادیر جدید را در آنها insert کرد. یک مثال:
DECLARE @tblTest AS TABLE (xmlField XML) INSERT INTO @tblTest(xmlField) VALUES ( '<Sample> <Node1>Value1</Node1> <Node2>Value2</Node2> <Node3/> </Sample>' ) DECLARE @newValue VARCHAR(50) = 'NewValue' UPDATE @tblTest SET xmlField.modify( 'insert text{sql:variable("@newValue")} into (/Sample/Node3)[1] [not(text())]' ) SELECT xmlField.value('(/Sample/Node3)[1]','varchar(50)') FROM @tblTest
UPDATE @tblTest SET xmlField.modify( 'replace value of (/Sample/Node3/text())[1] with sql:variable("@newValue")' )
برای آغاز به کار با این دیتابیس ابتدا باید آن را از سایت اصلی دریافت و بر روی سیستم نصب نمایید. متاسفانه سایت مونگو برای کشور ایران محدودیتی قرار داده است و باید از روشهای دیگری آن را دریافت نمایید و بر روی سیستم خود نصب نمایید. نحوه نصب این دیتابیس را میتوانید در مقاله MongoDb#3 مشاهده نمایید.
شاید نیاز باشد بجای کار کردن با محیط کنسول این دیتابیس، با یک محیط گرافیکی شبیه آن چیزی که Raven دارد کار کنید وتغییرات را مشاهده نمایید؛ برای همین به این آدرس رفته و محیط دلخواه خود را انتخاب نمایید.
یک پروژه از نوع کنسول را در ویژوال استادیو ایجاد کنید و سپس درایور رسمی مونگو را از این آدرس یا از طریق nuget نصب نمایید:
Install-Package mongocsharpdriver
ابتدا سه مدل را به شکل زیر ایجاد میکنیم:
public class Author { public ObjectId Id { get; set; } public string Name { get; set; } }
public class Language { public ObjectId Id { get; set; } public string Name { get; set; } }
public class Book { public ObjectId Id { get; set; } public string Title { get; set; } public string ISBN { get; set; } public int Price { get; set; } public List<Author> Authors { get; set; } public Language Language { get; set; } }
نوع ObjectId، نوعی است که توسط مونگو برای مشخص کردن کلید یکتای سند معرفی میشود.
در خطوط اولیه کد زیر، یک شیء از مدل بالا را ساخته و آن را مقداردهی میکنیم:
var book =new Book() { Title = "Gone With Wind", ISBN = "43442424", Price = 50000, Language = new Language() { Name = "Persian" }, Authors = new List<Author>() { new Author() { Name = "Margaret Mitchell" }, new Author() { Name = "Ali Mahboobi (Translator)" }, } };
بعد از آن یک شیء کلاینت از نوع mongoClient میسازیم که نوع خروجی آن یک اینترفیس میباشد که توسط کلاسی از جنس آن مقداردهی شده است. بیشتر خروجیهای مونگو در این کتابخانه از نوع اینترفیس هستند. شیء کلاینت وظیفه دارد تا ارتباط شما را با سرور مونگو برقرار کند:
var client = new MongoClient();
string connectionString = "mongodb://localhost:27017"; MongoClientSettings settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString)); var client = new MongoClient(settings);
در قسمت بعد لازم است که از سرور جاری، دیتابیس خود را دریافت کنیم. در صورتیکه دیتابیس درخواستی وجود نداشته باشد، یک دیتابیس جدید با آن نام ساخته خواهد شد:
var db = client.GetDatabase("publisher");
در مونگو اصطلاحی به نام collection وجود دارد که اسناد در آن قرار گرفته و ارتباط با اسناد از طریق آنها انجام میپذیرد. پس در اینجا قبل از هر کاری باید یک collection را ایجاد کرد و در صورتیکه کالکشن درخواستی وجود نداشته باشد، آن را تولید و ارتباط با آن را برخواهد گرداند.
var collection = db.GetCollection<Book>("books");
در اینجا کالکشنی با نام books با تبدیلاتی بر اساس مدل Book ایجاد میشود. در مرحله بعد لازم است که شیء ایجاد شده بر اساس کلاس مدل را با استفاده از متدهای insert شیء کالکشن، در دیتابیس ارسال کنیم.
شیءهای درج یک سند جدید به دیتابیس حالات مختلفی را دارد: افزودن تک سند، افزودن چند سند و دو مورد قبلی به صورت غیر همزمان میباشند:
collection.InsertOneAsync(book);
فعلا موجودیتهای مؤلفان و زبان به دلیل اینکه سند اختصاصی برای خود ندارند، با صفر پر شدهاند؛ ولی شناسه یکتای سند، مقدار خود را گرفته است.
عملیات خواندن
قبل از هر چیزی برای اینکه در مانور دادن بر روی دادهها راحت باشیم و اطلاعات را با فیلترهای متفاوتی واکشی کنیم، 7 عدد کتاب را با مشخصات زیر اضافه میکنیم. دو فیلد سال و تاریخ آخرین موجودی انبار را هم اضافه میکنیم.
var client = new MongoClient(); var db = client.GetDatabase("publisher"); db.DropCollection("books"); var collection = db.GetCollection<Book>("books"); var book =new Book() { Title = "Gone With Wind", ISBN = "43442424", Price = 50000, Year = 1936, LastStock = DateTime.Now.AddDays(-13), Language = new Language() { Name = "Persian" }, Authors = new List<Author>() { new Author() { Name = "Margaret Mitchell" }, new Author() { Name = "Ali Mahboobi (Translator)" }, } }; var book2 = new Book() { Title = "Jane Eyre", ISBN = "87897897", Price = 60000, Year = 1847, LastStock = DateTime.Now.AddDays(-5), Language = new Language() { Name = "English" }, Authors = new List<Author>() { new Author() { Name = "Charlotte Brontë" }, } }; var book3 = new Book() { Title = "White Fang", ISBN = "43442424", Price = 50000, Year = 1936, LastStock = DateTime.Now.AddDays(-13), Language = new Language() { Name = "English" }, Authors = new List<Author>() { new Author() { Name = "Jack London" }, new Author() { Name = "Philippe Mignon" }, } }; var book4 = new Book() { Title = "The Lost Symbol", ISBN = "43442424", Price = 3500000, Year = 2009, LastStock = DateTime.Now.AddDays(-17), Language = new Language() { Name = "Persian" }, Authors = new List<Author>() { new Author() { Name = "Dan Brown" }, new Author() { Name = "Mehrdad" }, } }; var book7 = new Book() { Title = "The Lost Symbol", ISBN = "43442424", Price = 47000000, Year = 2009, LastStock = DateTime.Now.AddDays(-56), Language = new Language() { Name = "Persian" }, Authors = new List<Author>() { new Author() { Name = "Dan Brown" }, new Author() { Name = "Mehrdad" }, } }; var book5= new Book() { Title = "The Help", ISBN = "45345e3er3", Price = 9000000, Year = 2009, LastStock = DateTime.Now.AddDays(-2), Language = new Language() { Name = "Enlish" }, Authors = new List<Author>() { new Author() { Name = "Kathryn Stockett" }, } }; var book6 = new Book() { Title = "City of Glass", ISBN = "454534545", Price = 500000, Year = 2009, LastStock = DateTime.Now, Language = new Language() { Name = "Persian" }, Authors = new List<Author>() { new Author() { Name = "Cassandra Clare" }, new Author() { Name = "Ali" }, } }; var books = new List<Book> {book, book2, book3, book4, book5, book6,book7}; collection.InsertManyAsync(books);
برای واکشی دیتاها کالکشنی از آن نوع را همانند قبل درخواست میکنیم. بعد از آن نیاز است که فیلتری برای واکشی اطلاعات تعریف کنیم که این فیلتر در قالب یک کلاس به نام BsonDocument ایجاد میشود که ما در اینجا، به دلیل اینکه میخواهیم همه اسناد را واکشی کنیم ، این سند Bson را مقداردهی نمیکنیم و توسط متد Find آن را در واکشی دیتاها شرکت میدهیم و سپس با صدا زدن متد ToList، عملیات واکشی را انجام میدهیم، برای اینکار میتوانیم از عملیات غیرهمزمان هم استفاده کنیم.
var client = new MongoClient(); var db = client.GetDatabase("publisher"); var collection = db.GetCollection<Book>("books"); var filter=new BsonDocument(); var docs = collection.Find(filter).ToList(); foreach (var book in docs) { Console.WriteLine(book.Title + " By "+ book.Authors[0].Name); }
با اجرای کد بالا به نتایج زیر میرسیم:
Gone With Wind By Margaret Mitchell Jane Eyre By Charlotte Brontë White Fang By Jack London The Lost Symbol By Dan Brown The Help By Kathryn Stockett City of Glass By Cassandra Clare The Lost Symbol By Dan Brown
اگر بخواهید فیلتری را بر روی این واکشی قرار دهید و مثلا بخواهید کتابهای منتشر شده در سال 2009 را واکشی نمایید، باید این سند Bson را مقداردهی نمایید. ولی برای راحتی اینکار، این کتابخانه شامل یک بیلدر Builder بوده که میتوان از طریق آن فیلترهای متنوعی را به صورت سادهتر طراحی کنید:
در خطوط بالا ابتدا یک بیلدر را برای کلاس مورد نظر ایجاد کرده و از خصوصیت Filter آن استفاده میکنیم و این خصوصیت شامل متدهای فراوانی است که میتوانید برای ایجاد شرط یا فیلتر استفاده کنید. تعدادی از متدهای پر استفاده آن همانند eq (برابری) ، gt (برزگتر از ...) ، gte (بزرگتر مساوی ...) و طبیعتا خانواده lt و ... موجود هستند.
var filter = Builders<Book>.Filter.Eq("Year", 2009); var docs = collection.Find(filter).ToList(); foreach (var book in docs) { Console.WriteLine(book.Title + " By "+ book.Authors[0].Name); }
The Lost Symbol By Dan Brown The Help By Kathryn Stockett City of Glass By Cassandra Clare The Lost Symbol By Dan Brown
// var filter=new BsonDocument(); var filterBuilder = Builders<Book>.Filter; var filter= filterBuilder.Eq("Year", 2009) | filterBuilder.Gte("Price",700000); var docs = collection.Find(filter).ToList(); foreach (var book in docs) { Console.WriteLine(book.Title + " By "+ book.Authors[0].Name); }
Gone With Wind By Margaret Mitchell White Fang By Jack London The Lost Symbol By Dan Brown The Help By Kathryn Stockett City of Glass By Cassandra Clare The Lost Symbol By Dan Brown
برای اینکه بتوانید از linq به جای queryBuilder استفاده کنید، میتوانید از خصوصیت AsQueryable استفاده کنید. خط زیر همان شرط یا فیلتر بالا را توسط Linq اعمال میکند
var docs = collection.AsQueryable().Where(x => x.Year == 2009 || x.Price <= 50000).ToList();
Sort کردن دادهها
برای مرتب سازی اطلاعات به شیوه کوئری بیلدر، همانند فیلتر که از کلاس Builder استفاده میکردیم، از همین شیء استفاده میکنیم؛ با این تفاوت که بجای استفاده از خصوصیت Filter، از Sort استفاده میکنیم و شیء ایجاد شده را به متد Sort میدهیم:
var sort = Builders<Book>.Sort.Ascending("Title").Descending("Price"); var docs = collection.Find(filter).Sort(sort).ToList(); foreach (var book in docs) { Console.WriteLine(book.Title + " By "+ book.Authors[0].Name); }
City of Glass By Cassandra Clare Gone With Wind By Margaret Mitchell The Help By Kathryn Stockett The Lost Symbol By Dan Brown The Lost Symbol By Dan Brown White Fang By Jack London
توجه داشته باشید که متد sort بعد از فیلتر گذاری، یعنی عمل Find در دسترس میباشد.
در قسمت بعدی به روزرسانی، حذف و ایندکس گذاری را مورد بررسی قرار میدهیم.
SELECT id, TestDate.ToString() FROM TestTable;
روشهای مختلف اطلاع رسانی به سیستم ردیابی تغییرات
متد DbSet.Add کار اطلاع رسانی تبدیل وهلههای ثبت شده را به کوئریهای Insert رکوردهای جدید، انجام میدهد:
using (var db = new BloggingContext()) { var blog = new Blog { Url = "http://sample.com" }; db.Blogs.Add(blog); db.SaveChanges(); }
سیستم ردیابی اطلاعات، اگر تغییراتی را در خواص اشیاء تحت نظر خود مشاهده کند، سبب تولید کوئریهای Update میگردد. یک چنین اشیایی تحت نظر Context هستند:
الف) اشیایی که در طول عمر Context از دیتابیس کوئری گرفته شدهاند.
ب) اشیایی که در طول عمر Context به آن اضافه شدهاند (حالت قبل).
using (var db = new BloggingContext()) { var blog = db.Blogs.First(); blog.Url = "http://sample.com/blog"; db.SaveChanges(); }
و متد DbSet.Remove کار اطلاع رسانی تبدیل وهلههای حذف شده را به کوئریهای Delete معادل، انجام میدهد:
using (var db = new BloggingContext()) { var blog = db.Blogs.First(); db.Blogs.Remove(blog); db.SaveChanges(); }
به علاوه امکان ترکیب متدهای Add، Remove و همچنین به روز رسانی اشیاء در طی یک Context و با فراخوانی یک SaveChanges در انتهای کار نیز وجود دارد. از این جهت که یک Context، الگوی واحد کار را پیاده سازی میکند و بیانگر یک تراکنش است. در این حالت ترکیبی، یا کل تراکنش با موفقیت به پایان میرسد و یا در صورت بروز مشکلی، هیچکدام از تغییرات درخواستی، اعمال نخواهند شد.
عملیات ردیابی، بر روی هر نوع Projections صورت نمیگیرد
اگر توسط LINQ Projections، نتیجهی نهایی کوئری را تغییر دادید، فقط در زمانی سیستم ردیابی بر روی آن فعال خواهد بود که projection نهایی حاوی اصل موجودیت مدنظر باشد. برای مثال در کوئری ذیل چون در Projection صورت گرفتهی در متد Select، هنوز در خاصیت Blog، به اصل موجودیت Blog اشاره میشود، نتیجهی این کوئری نیز تحت نظر سیستم ردیابی خواهد بود:
using (var context = new BloggingContext()) { var blog = context.Blogs .Select(b => new { Blog = b, Posts = b.Posts.Count() }); }
using (var context = new BloggingContext()) { var blog = context.Blogs .Select(b => new { Id = b.BlogId, Url = b.Url }); }
لغو سیستم ردیابی تغییرات، در زمانیکه به آن نیازی نیست
سیستم ردیابی تغییرات بر اساس مفاهیم AOP و تولید پروکسیهای آن کار میکند. این پروکسیها، اشیایی شفاف هستند که اشیاء شما را احاطه میکنند و هر تغییری را که اعمال میکنید، ابتدا از این غشاء رد شده و در سیستم ردیابی EF ثبت میشوند. سپس به وهلهی اصلی شیء موجود اعمال خواهند شد.
بدیهی است تولید این پروکسیها، دارای سربار است و اگر هدف شما صرفا کوئری گرفتن از اطلاعات، جهت نمایش آنها است، نیازی به تولید خودکار این پروکسیها را ندارید و این مساله سبب کاهش مصرف حافظهی برنامه و بالا رفتن سرعت آن میشود.
در قسمت قبل عنوان شد که «یک چنین اشیایی تحت نظر Context هستند: الف) اشیایی که در طول عمر Context از دیتابیس کوئری گرفته شدهاند.»
اگر میخواهید این حالت پیش فرض را لغو کنید، از متد AsNoTracking استفاده نمائید:
using (var context = new BloggingContext()) { var blogs = context.Blogs.AsNoTracking().ToList(); }
اگر میخواهید متد AsNoTracking را به صورت خودکار به تمام کوئریهای یک context خاص اعمال کنید، روش کار و تنظیم آن به صورت زیر است:
using (var context = new BloggingContext()) { context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
نکات به روز رسانی ارجاعات موجودیتها
دو حالت زیر را درنظر بگیرید که در اولی، blog از بانک اطلاعاتی واکشی شدهاست و post به صورت مستقیم وهله سازی شدهاست:
using (var context = new BloggingContext()) { var blog = context.Blogs.First(); var post = new Post { Title = "Intro to EF Core" }; blog.Posts.Add(post); context.SaveChanges(); }
using (var context = new BloggingContext()) { var blog = new Blog { Url = "http://blogs.msdn.com/visualstudio" }; var post = context.Posts.First(); blog.Posts.Add(post); context.SaveChanges(); }
در حالت دوم، ابتدا blog در بانک اطلاعاتی ثبت میشود (چون برخلاف حالت اول، تحت نظر context نیست) و سپس این post (که تحت نظر context است) به مجموعه مطالب آن اضافه میشود (بلاگ جدیدی اضافه شده و ارجاع مطلب موجودی به آن اضافه میشود).
وارد کردن یک موجودیت به سیستم ردیابی اطلاعات
در مثال قبل مشاهده کردیم که اگر موجودیتی تحت نظر context نباشد (برای مثال توسط یک کوئری به context وارد نشده باشد)، در حین ذخیره سازی ارجاعات، با آن به صورت یک وهلهی جدید رفتار شده و حتما در بانک اطلاعاتی به صورت یک رکورد جدید ذخیره میشود؛ حتی اگر Id آنرا دستی تنظیم کرده باشید که ندید گرفته خواهد شد.
اگر Id و سایر اطلاعات شیءایی را دارید، نیازی نیست تا حتما توسط یک کوئری ابتدا آنرا از بانک اطلاعاتی دریافت و سپس به صورت خودکار وارد سیستم ردیابی کنید؛ متد Attach نیز یک چنین کاری را انجام میدهد:
var blog = new Blog { Id = 2, Url = "https://www.dntips.ir" }; context.Blog.Attach(blog); context.SaveChanges();
علاوه بر متد Attach، متد AttachRange نیز برای افزودن لیستی از موجودیتها در حالت EntityState.Unchanged، پیش بینی شدهاست.
روش دیگر انجام اینکار به صورت ذیل است:
در اینجا ابتدا یک وهلهی جدید از Blog ایجاد شدهاست و سپس توسط متد Entry به Context وارد شده و همچنین حالت آن به صورت صریح، به تغییر یافته، مشخص گردیدهاست:
var blog = new Blog { Id = 2, Url = "https://www.dntips.ir" }; context.Entry(blog).State = EntityState.Modified ; context.SaveChanges();
var blog = new Blog { Id = 2, Url = "https://www.dntips.ir" }; context.Update(blog); context.SaveChanges();
به علاوه متد UpdateRange نیز برای افزودن لیستی از موجودیتها در حالت EntityState.Modified، پیش بینی شدهاست.
یک نکته: متدهای Attach و Update، هم بر روی یک DbSet و هم بر روی Context، قابل اجرا هستند. اگر بر روی Context اجرا شدند، نوع موجودیت دریافتی به نوع DbSet متناظر به صورت خودکار نگاشت شده و استفاده میشود (context.Set<T>().Attach(entity)). یعنی در حقیقت بین این دو حالت تفاوتی نیست و امکان فراخوانی این متدها بر روی Context، صرفا جهت سهولت کار درنظر گرفته شدهاست.
تفاوت رفتار context.Entry در EF Core با EF 6.x
متد context.Entry در EF 6.x هم وجود دارد. اما در EF core سبب تغییر وضعیت گراف متصل به یک شیء نمیشود و ضعیت روابط آنرا به روز رسانی نمیکند (برخلاف EF 6.x). اگر در EF Core نیاز به یک چنین به روز رسانی گراف مانندی را داشتید، باید از متد جدید context.ChangeTracker.TrackGraph به نحو ذیل استفاده نمائید:
context.ChangeTracker.TrackGraph(blog, e => e.Entry.State = EntityState.Added);
کوئری گرفتن از سیستم ردیابی اطلاعات
این سناریوها را درنظر بگیرید:
- میخواهم سیستمی شبیه به تریگرهای اس کیوال سرور را با EF داشته باشم.
- میخواهم اطلاعات تمام رکوردهای ثبت شده، حذف شده و به روز رسانی شده را لاگ کنم.
- میخواهم پس از ثبت رکوردی در هر جای برنامه، شبیه به مباحث SQL Server Service Broker و SqlDependency بلافاصله مطلع شده و توسط SignalR اطلاع رسانی کنم.
و در حالت کلی میخواهم پیش و یا پس از ثبت اطلاعات، بتوانم به تغییرات صورت گرفته دسترسی داشته باشم و عملیاتی را بر روی آنها انجام دهم. تمام این موارد و سناریوها را با کوئری گرفتن از سیستم ردیابی اطلاعات EF میتوان پیاده سازی کرد.
برای نمونه در مطلب قبل و قسمت «طراحی یک کلاس پایه، بدون تنظیمات ارث بری روابط»، یک کلاس پایه را که مقادیر پیش فرض خود را از SQL Server دریافت میکند، طراحی کردیم. در اینجا میخواهیم با استفاده از سیستم ردیابی EF، طراحی این کلاس پایه را عمومی کرده و سازگار با تمام بانکهای اطلاعاتی موجود کنیم.
جهت یادآوری، کلاس پایه موجودیتها، یک چنین شکلی را داشته:
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; } }
public class ApplicationDbContext : DbContext { // same as before public override int SaveChanges() { this.ChangeTracker.DetectChanges(); var modifiedEntries = this.ChangeTracker .Entries<BaseEntity>() .Where(x => x.State == EntityState.Modified); foreach (var modifiedEntry in modifiedEntries) { modifiedEntry.Entity.DateUpdated = DateTime.UtcNow; } var addedEntries = this.ChangeTracker .Entries<BaseEntity>() .Where(x => x.State == EntityState.Added); foreach (var addedEntry in addedEntries) { addedEntry.Entity.DateAdded = DateTime.UtcNow; } return base.SaveChanges(); } }
در اینجا کار با کوئری گرفتن از خاصیت ChangeTracker شروع میشود. سپس باید مشخص کنیم چه نوع موجودیتهایی را مدنظر داریم. چون تمام موجودیتهای ما از کلاس پایهی BaseEntity مشتق میشوند، بنابراین کوئری گرفتن بر روی این نوع، به معنای دسترسی به تمام موجودیتهای برنامه نیز هست. سپس در اینجا اگر حالتی EntityState.Modified بود، فقط مقدار خاصیت DateUpdated را به صورت خودکار مقدار دهی میکنیم و اگر حالتی EntityState.Added بود، تنها مقدار خاصیت DateAdded را به روز رسانی خواهیم کرد.
در یک چنین حالتی دیگر نیازی نیست تا مقادیر این خواص را در حین ثبت اطلاعات برنامه به صورت دستی مشخص کنیم.
یک نکته: اگر به ابتدای متد بازنویسی شده دقت کنید، فراخوانی متد this.ChangeTracker.DetectChanges در آن انجام شدهاست. علت اینجا است که این فراخوانی به صورت خودکار توسط متد base.SaveChanges انجام میشود، اما چون این مرحله را تا انتهای متد بازنویسی شده، به تاخیر انداختهایم، نیاز است خودمان به صورت دستی سبب محاسبهی مجدد تغییرات صورت گرفته شویم.
نکتهای در مورد بهبود کیفیت کدهای متد SaveChanges: استفادهی Change Tracker به این صورت با بازنویسی متد SaveChanges بسیار مرسوم است. اما پس از مدتی به متد SaveChanges ایی خواهید رسید که کنترل آن از دست خارج میشود. به همین جهت برای EF 6.x پروژههایی مانند EFHooks طراحی شدهاند تا کپسوله سازی بهتری را بتوان ارائه داد. انتقال کدهای آن به EF Core کار مشکلی نیست و اصل آن، بازنویسی HookedDbContext آن است که نحوهی مدیریت شکیلتر کوئری گرفتن از ChangeTracker را بیان میکند.
خواص سایهای یا Shadow properties
EF Core به همراه مفهوم کاملا جدیدی است به نام خواص سایهای. این نوع خواص در سمت کدهای ما و در کلاسهای موجودیتهای برنامه وجود خارجی نداشته، اما در سمت جداول بانک اطلاعاتی وجود دارند و اکنون امکان کوئری گرفتن و کار کردن با آنها در EF Core میسر شدهاست.
برای تعریف آنها، بجای افزودن خاصیتی به کلاسهای برنامه، کار از متد OnModelCreating به نحو ذیل شروع میشود:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>().Property<DateTime>("DateAdded");
سپس برای کار کردن و کوئری گرفتن از آن میتوان از متد جدید EF.Property، به نحو ذیل استفاده کرد:
var blogs = context.Blogs.OrderBy(b => EF.Property<DateTime>(b, "DateAdded"));
context.Entry(myBlog).Property("DateAdded").CurrentValue = DateTime.Now;
foreach (var addedEntry in addedEntries) { addedEntry.Property("DateAdded").CurrentValue = DateTime.UtcNow; }