سایت آموزشی: C# Station
به تازگی نگارش جدید کتاب برنامهنویسی زبان ++C توسط خالق این زبان Bjarne Stroustrup و انتشارات Addison-Wesley Professional منتشر شده است. در این کتاب به بررسی نگارش 11 زبان برنامهنویسی ++C پرداخته شده است.
تنظیمات مورد نیاز جهت شروع به کار با C# 9.0
Roslyn #1
context.Setup(c => c..AsNoTracking()).Returns(mockSet.Object);
ASP.NET MVC #22
دات نت 4 و کلاس Lazy
C# Language Specification 4.0
این کتابهایی که نام بردید تشکیل شده از تمام مؤلفههای دات نت. مثلا اون کتاب 1852 صفحهای از WCF تا WPF را هم دارد. خود WPF جامع حدود 800 صفحه است. یا WCF راحت 600 صفحه است.
به این صورت مدلهای خود را تعریف کرده و طبق مقالهی قبلی، Controllerهای هر یک را پیاده سازی نمایید:
public class Supplier { [Key] public string Key {get; set; } public string Name { get; set; } } public class Category { public int ID { get; set; } public string Name { get; set; } public virtual ICollection<Product> Products { get; set; } } public class Product { public int ID { get; set; } public string Name { get; set; } public decimal Price { get; set; } [ForeignKey("Category")] public int CategoryId { get; set; } public Category Category { get; set; } [ForeignKey("Supplier")] public string SupplierId { get; set; } public virtual Supplier Supplier { get; set; } }
پکیج Microsoft.AspNet.OData به تازگی ورژن 6 آن به صورت رسمی منتشر شده و شامل تغییراتی نسبت به نسخهی قبلی آن است. اولین نکتهی حائز اهمیت، Config آن است که به صورت زیر تغییر کرده و باید Optionهای مورد نیاز، کانفیگ شوند. در این نسخه DI نیز به Odata اضافه شده است:
public static void Register(HttpConfiguration config) { ODataModelBuilder odataModelBuilder = new ODataConventionModelBuilder(); var product = odataModelBuilder.EntitySet<Product>("Products"); var category = odataModelBuilder.EntitySet<Category>("Categories"); var supplier = odataModelBuilder.EntitySet<Supplier>("Suppliers"); var edmModel = odataModelBuilder.GetEdmModel(); supplier.EntityType.Ignore(c => c.Name); config.Select(System.Web.OData.Query.QueryOptionSetting.Allowed); config.MaxTop(25); config.OrderBy(System.Web.OData.Query.QueryOptionSetting.Allowed); config.Count(System.Web.OData.Query.QueryOptionSetting.Allowed); config.Expand(System.Web.OData.Query.QueryOptionSetting.Allowed); config.Filter(System.Web.OData.Query.QueryOptionSetting.Allowed); config.Count(System.Web.OData.Query.QueryOptionSetting.Allowed); //config.MapODataServiceRoute("ODataRoute", "odata", edmModel); // کانفیگ به صورت معمولی config.MapODataServiceRoute("ODataRoute", "odata", builder => { builder.AddService(ServiceLifetime.Singleton, sp => edmModel); builder.AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp => ODataRoutingConventions.CreateDefault()); }); }
[EnableQuery] public IQueryable<Product> Get() { return new List<Product> { new Product { Id = 1, Name = "name 1", Price = 11, Category = new Category {Id =1, Name = "Cat1" } }, new Product { Id = 2, Name = "name 2", Price = 12, Category = new Category {Id =2, Name = "Cat2" } }, new Product { Id = 3, Name = "name 3", Price = 13, Category = new Category {Id =3, Name = "Cat3" } }, new Product { Id = 4, Name = "name 4", Price = 14, Category = new Category {Id =4, Name = "Cat4" } }, }.AsQueryable(); ; }
Description | Option |
بسط دادن موجودیت مرتبط | $expand |
فیلتر کردن نتیجه، بر اساس شرطهای Boolean ی | $filter |
فرمان به سرور که تعداد رکوردهای بازگشتی را نیز نمایش دهد(مناسب برای پیاده سازی server-side pagging ) | $count |
مرتب کردن نتیجهی بازگشتی | $orderby |
select زدن روی پراپرتیهای درخواستی | $select |
پرش کردن از اولین رکورد به اندازهی n عدد | $skip |
فقط بازگرداندن n رکورد اول | $top |
/odata/Products?$select=Id,Name
/odata/Products?$top=3&$skip=2
/odata/Products?$count=true
{@odata.context: "http://localhost:4516/odata/$metadata#Products", @odata.count: 4,…} @odata.context:"http://localhost:4516/odata/$metadata#Products" @odata.count:4 value:[{Id: 1, Name: "name 1", Price: 11, SupplierId: 0, CategoryId: 0},…] 0:{Id: 1, Name: "name 1", Price: 11, SupplierId: 0, CategoryId: 0} 1:{Id: 2, Name: "name 2", Price: 12, SupplierId: 0, CategoryId: 0} 2:{Id: 3, Name: "name 3", Price: 13, SupplierId: 0, CategoryId: 0} 3:{Id: 4, Name: "name 4", Price: 14, SupplierId: 0, CategoryId: 0}
/odata/Products?$filter=Id eq 1
/odata/Products?$filter=Id gt 1 and Id lt 3
/odata/Products?$orderby=Id desc
/odata/Products?$expand=Category
/odata/Products?$expand=Category,Supplier
/odata/Categories(1)?$expand=Products/Supplier
[EnableQuery(PageSize = 10)]
محدود کردن Query Options
[EnableQuery (AllowedQueryOptions= AllowedQueryOptions.Skip | AllowedQueryOptions.Top)]
[EnableQuery(AllowedLogicalOperators=AllowedLogicalOperators.Equal)]
var product = odataModelBuilder.EntitySet<Product>("Products"); product.EntityType.Ignore(e => e.Price);
[EnableQuery(AllowedOrderByProperties = "Id,Name")]
public System.Web.Http.IHttpActionResult GetName(int key) { Product product = Get().Single(c => c.Id == key); return Ok(product.Name); }
/odata/Products(1)/Name/$value
HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 DataServiceVersion: 3.0 Content-Length: 3 Ali
Attribute Convention هایی هم برای اعتبارسنجی پراپرتیها موجود است که نام آنها واضح تعریف شدهاند:
Description | Attribute |
اجازهی فیلتر زدن بر روی آن پراپرتی داده نخواهد شد | NotFilterable |
اجازهی مرتب کردن بر روی آن پراپرتی داده نخواهد شد | NotSortable |
اجازهی select زدن بر روی آن پراپرتی داده نمیشود | NotNavigable |
اجازهی شمارش دهی بر روی آن Collection داده نمیشود | NotCountable |
اجازهی بسط دادن آن Collection داده نمیشود | NotExpandable |
و همچنین [AutoExpand] به صورت اتوماتیک آن موجودیت مورد نظر را بسط میدهد.
بطور مثال کدهای زیر را در مدل خود میتوانید مشاهده نمائید:
public class Product { public int Id { get; set; } public string Name { get; set; } [NotFilterable, NotSortable] public decimal Price { get; set; } [ForeignKey(nameof(SupplierId))] [NotNavigable] public virtual Supplier Supplier { get; set; } public int SupplierId { get; set; } [ForeignKey(nameof(CategoryId))] public virtual Category Category { get; set; } public int CategoryId { get; set; } [NotExpandable] public virtual ICollection<TestEntity> TestEnities { get; set; } }
فرض کنید پراپرتی زیر را به مدل خود اضافه کرده اید
public DateTimeOffset CreatedOn { get; set; }
حال کوئری زیر را برای فیلتر زدن، بر روی آن در اختیار داریم:
/odata/Products?$filter=year(CreatedOn) eq 2016
در اینجا فقط Product هایی بازگردانده میشوند که در سال 2016 ثبت شدهاند:
/odata/Products?$filter=CreatedOn lt cast(2017-04-01T04:11:31%2B08:00,Edm.DateTimeOffset)
کوئری فوق تاریخ مورد نظر را Cast کرده و همهی Product هایی را که قبل از این تاریخ ثبت شدهاند، باز میگرداند.
Nested Filter In Expand
/odata/Categories?$expand=Products($filter=Id gt 1 and Id lt 5)
همهی Categoryها به علاوه بسط دادن Product هایشان، در صورتیکه Id آنها بیشتر از 1 باشد
و یا حتی بر روی موجودیت بسط داده شده، select زده شود:
/odata/Categories?$expand=Products($select=Id,Name)
Custom Attribute
ضمنا به سادگی میتوان اتریبیوت سفارشی نوشت:
public class MyEnableQueryAttribute : EnableQueryAttribute { public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions) { // Don't apply Skip and Top. var ignoreQueryOptions = AllowedQueryOptions.Skip | AllowedQueryOptions.Top; return queryOptions.ApplyTo(queryable, ignoreQueryOptions); } }
روی هر متدی از کنترلر خود که اتریبیوت [MyEnableQuery] را قرار دهید، دیگر قابلیت Skip, Top را نخواهد داشت.
Dependency Injection در آخرین نسخهی OData اضافه شده است. بطور پیشفرض OData بصورت case-sensitive رفتار میکند. برای تغییر دادن آن در نسخههای قدیمی، Extension Methodی به نام EnableCaseSensitive وجود داشت. اما در نسخهی جدید شما میتوانید پیاده سازی خاص خود را از هر کدام از بخشهای OData داشته باشید و با استفاده از تزریق وابستگی، آن را به config برنامهی خود اضافه کنید؛ برای مثال:
public class CaseInsensitiveResolver : ODataUriResolver { private bool _enableCaseInsensitive; public override bool EnableCaseInsensitive { get { return true; } set { _enableCaseInsensitive = value; } } }
اینجا پیاده سازی از ODataUriResolver انجام شده و متد EnableCaseInsensitive به صورت جدیدی override و در حالت default مقدار true را برمیگرداند.
حال به صورت زیر آن را میتوان به وابستگیهای config برنامه، اضافه نمود:
config.MapODataServiceRoute("ODataRoute", "odata", builder => { builder.AddService(ServiceLifetime.Singleton, sp => edmModel); builder.AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp => ODataRoutingConventions.CreateDefault()); builder.AddService<ODataUriResolver>(ServiceLifetime.Singleton, sp => new CaseInsensitiveResolver()); // how enable case sensitive });
در قسمت بعدی به Actionها و Functionها در OData میپردازیم.
using Microsoft.ML.Data; namespace CreditCardFraudDetection.DataModels { public class ModelInput { [ColumnName("Time"), LoadColumn(0)] public float Time { get; set; } [ColumnName("V1"), LoadColumn(1)] public float V1 { get; set; } [ColumnName("V2"), LoadColumn(2)] public float V2 { get; set; } [ColumnName("V3"), LoadColumn(3)] public float V3 { get; set; } [ColumnName("V4"), LoadColumn(4)] public float V4 { get; set; } [ColumnName("V5"), LoadColumn(5)] public float V5 { get; set; } [ColumnName("V6"), LoadColumn(6)] public float V6 { get; set; } [ColumnName("V7"), LoadColumn(7)] public float V7 { get; set; } [ColumnName("V8"), LoadColumn(8)] public float V8 { get; set; } [ColumnName("V9"), LoadColumn(9)] public float V9 { get; set; } [ColumnName("V10"), LoadColumn(10)] public float V10 { get; set; } [ColumnName("V11"), LoadColumn(11)] public float V11 { get; set; } [ColumnName("V12"), LoadColumn(12)] public float V12 { get; set; } [ColumnName("V13"), LoadColumn(13)] public float V13 { get; set; } [ColumnName("V14"), LoadColumn(14)] public float V14 { get; set; } [ColumnName("V15"), LoadColumn(15)] public float V15 { get; set; } [ColumnName("V16"), LoadColumn(16)] public float V16 { get; set; } [ColumnName("V17"), LoadColumn(17)] public float V17 { get; set; } [ColumnName("V18"), LoadColumn(18)] public float V18 { get; set; } [ColumnName("V19"), LoadColumn(19)] public float V19 { get; set; } [ColumnName("V20"), LoadColumn(20)] public float V20 { get; set; } [ColumnName("V21"), LoadColumn(21)] public float V21 { get; set; } [ColumnName("V22"), LoadColumn(22)] public float V22 { get; set; } [ColumnName("V23"), LoadColumn(23)] public float V23 { get; set; } [ColumnName("V24"), LoadColumn(24)] public float V24 { get; set; } [ColumnName("V25"), LoadColumn(25)] public float V25 { get; set; } [ColumnName("V26"), LoadColumn(26)] public float V26 { get; set; } [ColumnName("V27"), LoadColumn(27)] public float V27 { get; set; } [ColumnName("V28"), LoadColumn(28)] public float V28 { get; set; } [ColumnName("Amount"), LoadColumn(29)] public float Amount { get; set; } [ColumnName("Class"), LoadColumn(30)] public bool Class { get; set; } } }
using Microsoft.ML.Data; namespace CreditCardFraudDetection.DataModels { public class ModelOutput { [ColumnName("PredictedLabel")] public bool Prediction { get; set; } public float Score { get; set; } } }
IDataView trainingDataView = mlContext.Data.LoadFromTextFile<ModelInput>( path: dataFilePath, hasHeader: true, separatorChar: ',', allowQuoting: true, allowSparse: false);
var dataProcessPipeline = mlContext.Transforms.Concatenate("Features", new[] { "Time", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9", "V10", "V11", "V12", "V13", "V14", "V15", "V16", "V17", "V18", "V19", "V20", "V21", "V22", "V23", "V24", "V25", "V26", "V27", "V28", "Amount" });
// Choosing algorithm var trainer = mlContext.BinaryClassification.Trainers.LightGbm(labelColumnName: "Class", featureColumnName: "Features"); // Appending algorithm to pipeline var trainingPipeline = dataProcessPipeline.Append(trainer);
ITransformer model = trainingPipeline.Fit(trainingDataView);mlContext.Model.Save(model , trainingDataView.Schema, <path>);
var crossValidationResults = mlContext.BinaryClassification.CrossValidateNonCalibrated(trainingDataView, trainingPipeline, numberOfFolds: 5, labelColumnName: "Class");
var predEngine = mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(mlModel); ModelInput sampleData = new ModelInput() { time = 0, V1 = -1.3598071336738, ... }; ModelOutput predictionResult = predEngine.Predict(sampleData); Console.WriteLine($"Actual value: {sampleData.Class} | Predicted value: {predictionResult.Prediction}");
برای شروع کار با ML خودکار در ML.NET، باید Visual Studio Extension - ML.NET Model Builder (Preview) را بارگیری کنیم. این کار را میتوان از طریق تب extensions انجام داد.
پس از نصب موفقیت آمیز افزونه، با کلیک راست روی پروژهی خود در داخل Solution Ex میتوانیم از Auto ML استفاده کنیم.
private ITransformer SetupMlnetModel(string tensorFlowModelFilePath) { var pipeline = _mlContext.<preprocess-data> .Append(_mlContext.Model.LoadTensorFlowModel(tensorFlowModelFilePath) .ScoreTensorFlowModel( outputColumnNames: new[]{TensorFlowModelSettings.outputTensorName }, inputColumnNames: new[] { TensorFlowModelSettings.inputTensorName }, addBatchDimensionInput: false)); ITransformer mlModel = pipeline.Fit(CreateEmptyDataView()); return mlModel; }