Asp.Net Identity #2
توسعه سیستم مدیریت محتوای DNTCms - قسمت سوم
var container = new UnityContainer();
container.RegisterType<ISomeService, SomeService>(new PerRequestLifetimeManager()); container.RegisterType<ISomeBusiness, SomeBusiness>(new PerRequestLifetimeManager()); container.RegisterType<ISomeController, SomeController>(new PerRequestLifetimeManager());
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
var someService=(ISomeService)DependencyResolver.Current.GetService(typeof(ISomeService)); var data=someService.GetData();
در لایه سرویس یک کلاس Service Factory داریم که قرار است همه سرویسها، برای برقراری ارتباط با یکدیگر از آن استفاده کنند.این کلاس معمولا در لایه سرویس به اشکال گوناگونی پیاده سازی میشود که کارش وهه سازی از Interfaceهای درخواستی است. اما برای یکپارچه کردن آن با Unity من آنرا به شکل زیر پیاده سازی کرده ام
public class ServiceFactory : MarshalByRefObject { static IUnityContainer uContainer = new UnityContainer(); public static Type DataContextType { get; set; } public static void Initialise(IUnityContainer unityContainer, Type dbContextType) { uContainer = unityContainer; DataContextType = dbContextType; uContainer.RegisterType(typeof(BaseDataContext), DataContextType, new HierarchicalLifetimeManager()); } public static T Create<T>() { return (T)Activator.CreateInstance<T>(); } public static T Create<T>(string fullTypeName) { return (T)System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(fullTypeName); } public static T Create<T>(Type entityType) { return (T)Activator.CreateInstance(entityType); } public static dynamic Create(Type entityType) { return Activator.CreateInstance(entityType); } public static T Get<T>() { return uContainer.Resolve<T>(); } public static object Get(Type type) { return uContainer.Resolve(type); } }
در این کلاس ما بجای ایجاد داینامیک آبجکتها، از Unity استفاده کردهایم. در همان ابتدا که برنامهی وب ما برای اولین بار اجرا میشود و بعد از Register کردن کلاسها، میتوانیم container را به صورت پارامتر سازنده به کلاس Service Factory ارسال کنیم. به این ترتیب برای استفاده از سرویسها در لایه Business از Unity بهره میبریم.
البته استفاده از Unity برای DataContext خیلی منطقی نیست و بهتر است نوع DataContext را در ابتدا بگیریم و هرجا نیاز داشتیم با استفاده از متد Create از آن وهله سازی بکنیم.
مثال 1: نام تمام کاربران را با قالب 'Surname, Firstname' نمایش دهید.
var members = context.Members .Select(member => new { Name = member.Surname + ", " + member.FirstName }) .ToList();
با این خروجی:
مثال 2: تمام امکاناتی را که با Tennis شروع میشوند، لیست کنید.
این گزارش به همراه تمام ستونهای جدول است.
var facilities = context.Facilities .Where(facility => facility.Name.StartsWith("Tennis")) .ToList();
با این خروجی:
مثال 3: تمام امکاناتی را که با tennis شروع میشوند، لیست کنید. این جستجو باید غیرحساس به بزرگی و کوچکی حروف باشد.
این گزارش به همراه تمام ستونهای جدول است.
نیازی به انجام مجزای این تمرین نیست؛ چون پاسخ آن همان پاسخ مثال 2 است. Collation پیشفرض در SQL Server، غیرحساس به بزرگی و کوچکی حروف است. بنابراین چه tennis را جستجو کنیم و یا TeNnis را، تفاوتی نمیکند.
مثال 4: شماره تلفنهای دارای پرانتز را لیست کنید.
این گزارش باید به همراه ستونهای memid, telephone باشد.
روش اول: در اینجا دوبار از متد Contains استفاده شدهاست:
var members = context.Members .Select(member => new { member.MemId, member.Telephone }) .Where(member => member.Telephone.Contains("(") && member.Telephone.Contains(")")) .ToList();
روش دوم: اگر میخواهیم کنترل بیشتری را بر روی خروجی نهایی LIKE تولیدی داشته باشیم، میتوان از متد سفارشی استاندارد EF.Functions.Like استفاده کرد که از حروف wild cards نیز پشتیبانی میکند:
members = context.Members .Select(member => new { member.MemId, member.Telephone }) .Where(member => EF.Functions.Like(member.Telephone, "%[()]%")) .ToList();
مثال 5: کد پستیها 5 رقمی هستند. گزارشی را تهیه کنید که در آن اگر کدپستی کمتر از 5 رقم بود، ابتدای آن با صفر شروع شود.
هدف اصلی از این مثال، اعمال متد PadLeft(5, '0') به خاصیت member.ZipCode است.
روش اول: EF-Core فعلا قابلیت ترجمهی PadLeft(5, '0') را به معادل SQL آنرا ندارد. به همین جهت مجبور هستیم ابتدا ZipCodeها را به صورت رشتهای بازگشت دهیم که در اینجا استفادهی از Convert.ToString مجاز است.
با این خروجی:
SELECT CONVERT (NVARCHAR (MAX), [m].[ZipCode]) AS [Zip] FROM [Members] AS [m] ORDER BY CONVERT (NVARCHAR (MAX), [m].[ZipCode]);
var members = context.Members .Select(member => new { ZipCode = Convert.ToString(member.ZipCode) }) .OrderBy(m => m.ZipCode) .ToList(); // Now using LINQ to Objects members = members.Select(member => new { ZipCode = member.ZipCode.PadLeft(5, '0') }) .OrderBy(m => m.ZipCode) .ToList();
روش دوم: SQL Server به همراه تابع استانداردی به نام Replicate است که از آن میتوان برای شبیه سازی PadLeft، بدون متوسل شدن به LINQ to Objects، استفاده کرد. اما چون این تابع هنوز به EF-Core معرفی نشدهاست، نیاز است خودمان اینکار را انجام دهیم. در این روش، از متد SqlDbFunctionsExtensions.SqlReplicate استفاده میشود. روش تعریف این نوع متدها را در مطلب «امکان تعریف توابع خاص بانکهای اطلاعاتی در EF Core» پیشتر بررسی کردهایم که برای مثال در اینجا چنین شکلی را پیدا میکند:
namespace EFCorePgExercises.Utils { public static class SqlDbFunctionsExtensions { public static string SqlReplicate(string expression, int count) => throw new InvalidOperationException($"{nameof(SqlReplicate)} method cannot be called from the client side."); private static readonly MethodInfo _sqlReplicateMethodInfo = typeof(SqlDbFunctionsExtensions) .GetRuntimeMethod( nameof(SqlDbFunctionsExtensions.SqlReplicate), new[] { typeof(string), typeof(int) } ); public static void AddCustomSqlFunctions(this ModelBuilder modelBuilder) { modelBuilder.HasDbFunction(_sqlReplicateMethodInfo) .HasTranslation(args => { return SqlFunctionExpression.Create("REPLICATE", args, _sqlReplicateMethodInfo.ReturnType, typeMapping: null); }); } } }
namespace EFCorePgExercises.DataLayer { public class ApplicationDbContext : DbContext { // ... protected override void OnModelCreating(ModelBuilder modelBuilder) { // ... modelBuilder.AddCustomSqlFunctions(); // ... } } }
var newMembers = context.Members .Select(member => new { ZipCode = SqlDbFunctionsExtensions.SqlReplicate( "0", 5 - Convert.ToString(member.ZipCode).Length) + member.ZipCode }) .OrderBy(m => m.ZipCode) .ToList();
مثال 6: اولین حرف نام خانوادگی کاربران در کل ردیفهای جدول چندبار تکرار شدهاست؟
این گزارش باید به همراه ستونهای letter, count باشد.
var members = context.Members .Select(member => new { Letter = member.Surname.Substring(0, 1) }) .GroupBy(m => m.Letter) .Select(g => new { Letter = g.Key, Count = g.Count() }) .OrderBy(r => r.Letter) .ToList();
با این خروجی:
مثال 7: حروف '-','(',')', ' ' را از شماره تلفنها حذف کنید.
این گزارش باید به همراه ستونهای memid, telephone باشد.
بانک اطلاعاتی PostgreSQL به همراه تابع استاندارد regexp_replace است و میتوان از آن برای حل یک چنین مسایلی استفاده کرد:
select memid, regexp_replace(telephone, '[^0-9]', '', 'g') as telephone from members order by memid;
var members = context.Members .Select(member => new { member.MemId, Telephone = member.Telephone.Replace("-", "") .Replace("(", "") .Replace(")", "") .Replace(" ", "") }) .OrderBy(r => r.MemId) .ToList();
کدهای کامل این قسمت را در اینجا میتوانید مشاهده کنید.
import { Component, Input, OnInit } from '@angular/core'; import { AbstractControl } from '@angular/forms'; @Component({ selector: 'validation-message', template: ` <ng-container *ngIf="control.invalid && control.touched"> {{ message }} </ng-container> ` }) export class ValidationMessageComponent implements OnInit { @Input() control: AbstractControl; @Input() fieldDisplayName: string; @Input() rules: { [key: string]: string }; get message(): string { return this.control.hasError('required') ? `${this.fieldDisplayName} را وارد نمائید.` : this.control.hasError('pattern') ? `${this.fieldDisplayName} را به شکل صحیح وارد نمائید.` : this.control.hasError('email') ? `${this.fieldDisplayName} را به شکل صحیح وارد نمائید.` : this.control.hasError('minlength') ? `${this.fieldDisplayName} باید بیشتر از ${ this.control.errors.minlength.requiredLength } کاراکتر باشد.` : this.control.hasError('maxlength') ? `${this.fieldDisplayName} باید کمتر از ${ this.control.errors.maxlength.requiredLength } کاراکتر باشد.` : this.control.hasError('min') ? `${this.fieldDisplayName} باید بیشتر از ${ this.control.errors.min.requiredLength } باشد.` : this.control.hasError('max') ? `${this.fieldDisplayName} باید کمتر از ${ this.control.errors.max.requiredLength } باشد.` : this.hasRule() ? this.findRule() : this.control.hasError('model') ? `${this.control.errors.model.messages[0]}` : ''; } constructor() {} private hasRule() { return ( this.rules && Object.keys(this.control.errors).some(ruleKey => this.rules[ruleKey] ? true : false ) ); } private findRule(): string { let message = ''; Object.keys(this.control.errors).forEach(ruleKey => { if (this.rules[ruleKey]) { message += `${this.rules[ruleKey]} `; } }); return message; } ngOnInit(): void {} }
<mat-error *ngIf="form.controls['userName'].invalid && form.controls['userName'].touched" class="mat-text-warn"> <validation-message [control]="form.controls['userName']" fieldDisplayName="نام کاربری" [rules]="{rule1:'پیغام متناظر با rule1'}"> </validation-message> </mat-error>
this.form = this.formBuilder.group({ userName: [ '', [Validators.required, UserNameValidators.rule1)] ], password: ['', Validators.required], rememberMe: [false] }); export class UserNameValidators{ static rule1(control: AbstractControl) { if (control.value.indexOf(' ') >= 0) { return { rule1: true }; } return null; } }
عملگرهای تولید، برای ما توالی ایجاد میکنند و تفاوتهای عمدهای با سایر عملگرهای پرس و جو دارند که در بخش زیر به آنها اشاره میکنیم:
1- هیچ توالی ورودی را دریافت نمیکنند.
2- این عملگرها بصورت متد الحاقی پیاده سازی نشدهاند و بصورت متدهای استاتیک در کلاس Enumerable قرار گرفتهاند.
امضاء زیر مربوط به متد Empty میباشد:
public static IEnumerable<TResult> Empty<TResult>()
Empty
عملگر Empty یک توالی بدون عنصر (Empty) را بر اساس نوع مشخص شده، ایجاد میکند.
در کد زیر نحوه ایجاد یک توالی خالی از نوع Ingredient نشان داده شده است.
IEnumerable<Ingredient> ingredients = Enumerable.Empty<Ingredient>(); Console.WriteLine(ingredients.Count());
0
معادل عملگر Empty، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
Range
عملگر پرس و جوی Range، یک توالی از مقادیر صحیح متوالی را برای ما ایجاد میکند. اولین پارامتر این عملگر عنصر آغاز کننده توالی است و دومین پارامتر این عملگر تعداد کل عناصر توالی تولید شده، با احتساب عنصر اول خواهد بود.
مثال:
IEnumerable<int> fiveToTen = Enumerable.Range(5,6); foreach (var num in fiveToTen) { Console.WriteLine(num); }
5 6 7 8 9 10
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Range، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
Repeat
عملگر پرس و جوی Repeat یک عدد را به تعداد بار مشخصی در توالی خروجی تکرار میکند.
مثال:
IEnumerable<int> fiveToTen = Enumerable.Repeat(42, 6); foreach (var num in fiveToTen) { Console.WriteLine(num); }
42 42 42 42 42 42
معادل عملگر Repeat، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگرهای کمی (Quantifier Operators)
عملگرهای Quantifier یک توالی ورودی را گرفته، آن را ارزیابی کرده و یک مقدار منطقی را باز میگردانند.
عملگر Contains
عملگر Contains عناصر یک توالی را ارزیابی میکند و در صورتیکه مقدار مورد نظر ما در توالی وجود داشته باشد، ارزش True باز میگرداند.
مثال:
int[] nums = {1, 2, 3}; bool isTowThere = nums.Contains(2); bool isFiveThere = nums.Contains(5); Console.WriteLine(isTowThere); Console.WriteLine(isFiveThere);
True False
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Contains، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Any
عملگر Any دو امضاء مختلف را دارد:
1- اولین امضاء: در صورتیکه توالی شامل حداقل یک عنصر باشد، ارزش True بازگردانده میشود.
2- دومین امضاء: یک عبارت پیش بینی را قبول میکند. در صورتیکه حداقل یکی از عناصر توالی، عبارت پیش بینی را تامین کند، ارزش صحیح باز گردانده میشود.
مثال: بررسی امضاء اول عملگر Any
int[] nums = { 1, 2, 3 }; IEnumerable<int> noNums = Enumerable.Empty<int>(); Console.WriteLine(nums.Any()); Console.WriteLine(noNums.Any());
True False
int[] nums = { 1, 2, 3 }; bool areAnyEvenNumbers = nums.Any(x => x % 2 == 0); Console.WriteLine(areAnyEvenNumbers);
True
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Any، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر All
عملگر پرس و جوی All، یک عبارت پیش بینی را دریافت میکند و عناصر توالی ورودی را بر مبنای آن ارزیابی میکند تا مشخص شود همه عناصر، شرط پیش بینی را تامین میکنند.
در کد زیر بررسی میکنیم که آیا همه عناصر توالی مواد غذایی، جزء مواد غذایی کم چرب میباشند یا خیر .
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient { Name = "Egg", Calories = 100 }, new Ingredient { Name = "Milk", Calories = 150 }, new Ingredient { Name = "Flour", Calories = 50 }, new Ingredient { Name = "Butter", Calories = 400 } }; bool isLowFatRecipe = ingredients.All(x => x.Calories < 200); Console.WriteLine(isLowFatRecipe);
False
نکته : عملگر All به محض پیدا کردن عنصری که شرط مشخص شده را نقض کند، ارزش False را باز میگرداند و ادامه بررسی عناصر باقی مانده را متوقف میکند.
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Any، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر SequenceEqual
عملگر SequenceEqual دو توالی را با هم مقایسه کرده و در صورتیکه عناصر هر دو توالی برابر و ترتیب قرار گیری آنها نیز یکسان باشند، ارزش True باز گردانده میشود.
مثال:
IEnumerable<int> sequence1 = new[] {1, 2, 3}; IEnumerable<int> sequence2 = new[] { 1, 2, 3 }; bool isSeqEqual = sequence1.SequenceEqual(sequence2); Console.WriteLine(isSeqEqual);
True
در صورتی که دو توالی عناصر یکسانی داشته باشند، ولی ترتیب قرار گیری عناصر با هم یکسان نباشند، عملگر ارزش False را باز میگرداند.
مثال :
IEnumerable<int> sequence1 = new[] { 1, 2, 3 }; IEnumerable<int> sequence2 = new[] { 3, 2, 1 }; bool isSeqEqual = sequence1.SequenceEqual(sequence2); Console.WriteLine(isSeqEqual);
False
معادل عملگر SequenceEqual، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگرهای تجمیع/تجمعی Aggregate Operators
عملگرهای Aggregate یک توالی ورودی را دریافت و یک مقدار عددی (Scalar Value) را باز میگردانند. مقدار بازگردانده شده، حاصل یک عملیات محاسباتی میباشد.
لیستی از عملگرهای تجمیع ( Aggregate Operators ):
• Count
• LongCount
• Sum
• Min
• Max
• Average
• Aggregate
عملگر Count
عملگر Count، تعداد عناصر توالی ورودی را باز میگرداند. عملگر Count، دو امضاء مختلف دارد. یکی از این امضاءها یک عبارت پیش بینی را میپذیرد.
کد زیر، امضاء اول عملگر Count را نشان میدهد:
int[] nums = { 1, 2, 3 }; int numberOfElements = nums.Count(); Console.WriteLine(numberOfElements);
3
وقتی عبارت پیش بینی بکار گرفته میشود، عملگر Count تنها عناصری را که شرط را تامین کنند، شمارش میکند.
در کد زیر عملگر Count، همه عناصر زوج توالی ورودی را شمارش میکند:
int[] nums = { 1, 2, 3 }; int numberOfEvenElements = nums.Count(x => x % 2 == 0); Console.WriteLine(numberOfEvenElements);
1
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Count ، کلمهی کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر LongCount
این عملگر مثل عملگر Count عمل میکند، اما با این تفاوت که خروجی آن به جای نوع int از نوع long میباشد. این عملگر برای شمارش توالیهای ورودی بسیار بزرگ مورد استفاده قرار میگیرد.
پیاده سازی توسط عبارتهای جستجو
معادل عملگر LongCount، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Sum
این عملگر مجموع تمامی عناصر یک توالی را باز میگرداند.
در کد زیر جمع عناصر یک توالی از نوع int را مشاهده میکنید:
int[] nums = { 1, 2, 3 }; int total = nums.Sum(); Console.WriteLine(total);
6
عملگر Sum میتواند بر روی توالیهایی از نوع <IEnumerable<T و بر روی اعضای عددی آنها اعمال شود.
مثال:
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient { Name = "Egg", Calories = 100 }, new Ingredient { Name = "Milk", Calories = 150 }, new Ingredient { Name = "Flour", Calories = 50 }, new Ingredient { Name = "Butter", Calories = 400 } }; int totalCalories = ingredients.Sum(x => x.Calories); Console.WriteLine(totalCalories);
1200
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Sum، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Average
این عملگر میانگین عناصر توالیهای عددی را محاسبه میکند.
مثال:
int[] nums = { 1, 2, 3 }; var avg = nums.Average(); Console.WriteLine(avg);
2
همانطور که در کد بالا مشاهده میکنید، نوع متغیر avg صراحتا مشخص نشده و از نوع var استفاده شده است. تابع average بر اساس توالی ورودی، انواع مختلفی از نوع دادههای عددی را به خروجی ارسال میکند (double,float,decimal).
همانند عملگر Sum، عملگر Average میتواند بر روی اعضای عددی توالیهایی که از نوع<IEnumarable<T هستند، اعمال شود.
مثال:
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient { Name = "Egg", Calories = 100 }, new Ingredient { Name = "Milk", Calories = 150 }, new Ingredient { Name = "Flour", Calories = 50 }, new Ingredient { Name = "Butter", Calories = 400 } }; var avgCalories = ingredients.Average(x => x.Calories); Console.WriteLine(avgCalories);
240
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Average، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Min
عملگر Min کوچکترین عنصر توالی را باز میگرداند.
مثال:
int[] nums = { 3, 2, 1 }; var smallest = nums.Min(); Console.WriteLine(smallest);
1
مثال:
Ingredient[] ingredients = { new Ingredient { Name = "Sugar", Calories = 500 }, new Ingredient { Name = "Egg", Calories = 100 }, new Ingredient { Name = "Milk", Calories = 150 }, new Ingredient { Name = "Flour", Calories = 50 }, new Ingredient { Name = "Butter", Calories = 400 } }; var smallestCalories = ingredients.Min(x => x.Calories); Console.WriteLine(smallestCalories);
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Min ، کلمه کلیدی در عبارتهای جستجو وجود ندارد.ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
عملگر Max
عملگر Max بزرگترین عنصر توالی را باز میگرداند.
مثال:
int[] nums = { 1 ,3, 2 }; var largest = nums.Max(); Console.WriteLine(largest);
3
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Max، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
Aggregate
عملگرهای تجمعی که تا اینجا معرفی شدند، تنها یک کار را انجام میدادند. اما عملگر Aggregate امکان تعریف یک پرس و جوی تجمیع سفارشی و پیشرفتهتر را که بر روی توالی ورودی اعمال میشود نیز مهیا میکند.
عملگر Aggregate دو نسخه دارد:
1- نسخهای که اجازه استفاده از یک عدد را به عنوان مقدار Seed، به ما میدهد (مقدار آغازین یا Seed).
2- نسخهای که از عنصر ابتدایی توالی به عنوان مقدار Seed استفاده میکند.
هر دو نسخه این عملگر به یک تابع انباره (accumulator function) جهت نگهداری نتیجه نیاز دارند.
کد زیر شبیه سازی عملگر Sum توسط عملگر Aggregate میباشد:
int[] nums = {1, 2, 3}; var result = nums.Aggregate(0, (currentElement, runningTotal) => runningTotal + currentElement); Console.WriteLine(result);
6
کد زیر شبیه سازی عملیات فاکتوریل را با در نظر گرفتن عنصر اول توالی، به عنوان مقدار Seed نشان میدهد:
int[] nums = { 1, 2, 3 ,4,5}; var result = nums.Aggregate((runningProduct, nextfactor) => runningProduct * nextfactor); Console.WriteLine(result);
120
پیاده سازی توسط عبارتهای جستجو
معادل عملگر Aggregate، کلمه کلیدی در عبارتهای جستجو وجود ندارد. ترکیب دو روش میتواند خروجی دلخواه را تولید کند.
EF Code First #12
interface IPostService { void AddPost(Post post); IList<Post> GetPosts(); Post GetPost(int PostId); int RemovePost(Post post); int UpdatePost(Post post); }
public class PostService<T>:IPostService where T:Post { private readonly IUnitOfWork _uow; private readonly IDbSet<T> _post; public PostService(IUnitOfWork uow) { _uow = uow; _post = _uow.Set<T>(); } public void AddPost(T post) {} public IList<T> GetPosts() {} //... {