Այս հոդվածը ցույց է տալիս մի քանի տեխնիկա՝ մասնակի C# դասերի օգտագործման համար՝ EF 8/ASP.NET8-ի ընդհանուր խնդիրները լուծելու համար:
Համառոտագիր. EF 8 – Տվյալների բազայի առաջին մոտեցման դեպքում ստեղծված EF դասերը չեն կարող ուղղակիորեն ընդլայնվել լրացուցիչ գործառույթներով, քանի որ դրանք վերագրվում են, երբ մոդելը վերականգնվում է: Սա հաղթահարելու համար մենք կարող ենք օգտագործել մասնակի C# դասեր: Այս հոդվածը ներկայացնում է EF/ASP.NET միջավայրում ֆունկցիոնալությունը ընդլայնելու օգտակար հնարքներ:
Իմ C#/ASP.NET 8 MVC նախագծում ես օգտագործում եմ Entity Framework Core Database First մոտեցումը: Սա անհրաժեշտ է, քանի որ տարբեր տեխնոլոգիաների մի քանի հավելվածներ հիմնված են նույն SQL Server տվյալների բազայի վրա: Տվյալների բազայից մոդել ստեղծելու համար ես օգտագործում եմ EFCorePowerTools-ը:
Խնդիրն առաջանում է, երբ ստեղծված EF մոդելը պետք է թարմացվի տվյալների բազայի սխեմայի փոփոխությունները արտացոլելու համար: Քանի որ ստեղծվող EF էության դասերը կվերագրվեն, դրանցում կատարված բոլոր փոփոխությունները կորչում են: Սա խնդիր է, քանի որ երբեմն մենք պետք է ընդլայնենք EF իրերը՝ հավելվածում օգտագործելու համար:
Այս խնդիրը լուծելու համար ես հիմնվում եմ C#-ի մասնակի դասերի հետ կապված հնարքների վրա, որոնք կբացատրվեն ստորև:
Սովորական C#/EF/ASP.NET 8 MVC հավելվածում իրավիճակը կարող է նմանվել հետևյալ կոդի.
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //this is typical class generated by EF Core Power Tools //in EF-Database-First approach // <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated> [PrimaryKey("Id")] public partial class Customer { //Customer-partial-class-1 [Key] [StringLength(15)] public string? Id { get; set; } [StringLength(15)] public string? NAME { get; set; } public short? Language { get; set; } } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //this is typical model calls for ASP.NET MVC public class CustomerEdit_ViewModel { //model public string? Id { get; set; } = null; //view model // this is our Customer Entity from EF Core public Customer? Customer1 { get; set; } = null; //this is flag for submit button public bool IsSubmit { get; set; } = false; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //this is typical controller for ASP.NET MVC public class CustomersController : Controller { // some Controller code here public async Task<ActionResult> CustomerEdit(CustomerEdit_ViewModel model) { if (model.IsSubmit) { // Model validation is done during model binding // we have to check if model is valid if (ModelState.IsValid) { if (model.Customer1 != null) { //we update existing customer in database //redirect to the list of customers } } else { // we go for presentation of validation errors ModelState.AddModelError("", "PleaseCorrectAllErrors"); } } else { ModelState.Clear(); //go for presentation of original data if (model.Id != null) { //get Customer by Id from database } } return View("CustomerEdit", model); } }
Այստեղ Customer-partial-class-1-ը EF-ի կողմից ստեղծված դաս է (շտեմարանից հակադարձ ճարտարագիտության միջոցով): Այն ներառում է որոշ վավերացման ատրիբուտներ, որոնք համապատասխանում են տվյալների բազայի սահմանափակումներին: Այս դասը սովորաբար օգտագործվում է մոդելային դասում (օրինակ՝ CustomerEdit_ViewModel ), որտեղ վավերացման ատրիբուտները մշակվում են գործողության կամ մեթոդի ընթացքում (օրինակ՝ CustomerEdit ):
Եթե մեզ անհրաժեշտ է գեներացված դասին հատկություններ ավելացնել, մենք չենք կարող ուղղակիորեն փոփոխել Customer-partial-class-1 , քանի որ դա կհանգեցնի փոփոխությունների վերագրանցմանը: Փոխարենը, մենք կարող ենք ստեղծել Customer-partial-class-2 և ավելացնել մեր հատուկ հատկությունները այնտեղ: Որպեսզի EF-ն չներառի այս հատկությունները մոդելում, մենք պետք է օգտագործենք [NotMapped] հատկանիշը :
Ահա մի օրինակ.
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //this is typical class generated by EF Core Power Tools //in EF-Database-First approach // <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated> [PrimaryKey("Id")] public partial class Customer { //Customer-partial-class-1 [Key] [StringLength(15)] public string? Id { get; set; } [StringLength(15)] public string? NAME { get; set; } public short? Language { get; set; } } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //this is our partial class that extends our generated class //and is not overwritten by EF Core Power Tools //we use it to add some additional properties public partial class Customer { //Customer-partial-class-2 [NotMapped] public int NumberOfCreditCards { get; set; } = 0; [NotMapped] public string? LanguageString { get { string? result = "Unknown"; if (Language == 1) { result = "English"; } else if (Language == 2) { result = "German"; } return result; } } } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //this is typical model calls for ASP.NET MVC public class CustomerEdit_ViewModel { //model public string? Id { get; set; } = null; //view model // this is our Customer Entity from EF Core public Customer? Customer1 { get; set; } = null; //this is flag for submit button public bool IsSubmit { get; set; } = false; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //this is typical controller for ASP.NET MVC public class CustomersController : Controller { // some Controller code here //............. //this is typical action for editing customer public async Task<ActionResult> CustomerEdit(CustomerEdit_ViewModel model) { if (model.IsSubmit) { // Model validation is done during model binding // we have to check if model is valid if (ModelState.IsValid) { if (model.Customer1 != null) { //we update existing customer in database //redirect to the list of customers } } else { // we go for presentation of validation errors ModelState.AddModelError("", "PleaseCorrectAllErrors"); } } else { ModelState.Clear(); //go for presentation of original data if (model.Id != null) { //get Customer by Id from database } } return View("CustomerEdit", model); } }
Ծածկագիրը՝ մանրամասն մեկնաբանություններով, պետք է լինի ինքնըստինքյան։
Երբեմն, EF-ում ավտոմատ ստեղծվող վավերացման ատրիբուտները բավարար չեն, և մենք պետք է ավելացնենք հատուկ վավերացման կանոններ: Կրկին օգնության են հասնում մասնակի դասերը։ Ստորև բերված է օրինակ.
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //this is typical class generated by EF Core Power Tools //in EF-Database-First approach // <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated> [PrimaryKey("Id")] public partial class Customer { //Customer-partial-class-1 [Key] [StringLength(15)] public string? Id { get; set; } [StringLength(15)] public string? NAME { get; set; } public short? Language { get; set; } } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //this is our partial class that extends our generated class //and is not overwritten by EF Core Power Tools //we use it to add some additional properties //and do some additional validation [MetadataType(typeof(Customer_MetaData))] public partial class Customer { //Customer-partial-class-2 public Customer() { TypeDescriptor.AddProviderTransparent( new AssociatedMetadataTypeTypeDescriptionProvider( typeof(Customer), typeof(Customer_MetaData)), typeof(Customer)); } [NotMapped] public int NumberOfCreditCards { get; set; } = 0; [NotMapped] public string? LanguageString { get { string? result = "Unknown"; if (Language == 1) { result = "English"; } else if (Language == 2) { result = "German"; } return result; } } } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //this is our metadata class for our partial class //purpose of this class is to add validation attributes to our partial class //in addition to those that are already in generated class public class Customer_MetaData { //main trick here is that we are adding more validation attributes //in addition to those in generated class [Required] [MinLength(5)] public string? Id { get; set; } = string.Empty; //main trick here is that we are adding more validation attributes //in addition to those in generated class [Required] [MinLength(3)] public string? NAME { get; set; } = string.Empty; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //this is typical model calls for ASP.NET MVC public class CustomerEdit_ViewModel { //model public string? Id { get; set; } = null; //view model // this is our Customer Entity from EF Core public Customer? Customer1 { get; set; } = null; //this is flag for submit button public bool IsSubmit { get; set; } = false; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //this is utility class for validation public class ValidationUtil { /// <summary> /// Validates the specified model object against custom validation rules. /// </summary> /// <param name="model">The model object to validate.</param> /// <param name="modelState">The ModelStateDictionary to store validation errors.</param> /// <param name="logger">The logger to log errors.</param> /// <param name="prefix">An optional prefix for error keys in the ModelStateDictionary.</param> /// <returns>True if validation is successful; otherwise, false.</returns> /// <exception cref="ArgumentNullException">Thrown when the model is null.</exception> public static bool ValidateModelForCustomRules( object model, ModelStateDictionary modelState, ILogger? logger, string? prefix = null) { bool validationSuccessful = false; try { if (model == null) { throw new ArgumentNullException(nameof(model)); } else { var validationContext = new ValidationContext(model); var validationResults = new List<ValidationResult>(); Validator.TryValidateObject(model, validationContext, validationResults, true); foreach (var result in validationResults) { foreach (var memberName in result.MemberNames) { string key = string.IsNullOrEmpty(prefix) ? memberName : $"{prefix}.{memberName}"; modelState.AddModelError(key, result.ErrorMessage ?? "Error"); } } //Go recursively into depth for all properties of the model object that are objects themselves //we must go manually recursively into depth because API Validator.TryValidateObject does validation //only for class properties on first level foreach (var property in model.GetType().GetProperties()) { if (property.PropertyType.IsClass && property.PropertyType != typeof(string)) { var propertyValue = property.GetValue(model); if (propertyValue != null) { validationSuccessful &= ValidateModelForCustomRules(propertyValue, modelState, logger, property.Name); } } } } } catch (Exception ex) { string methodName = $"Type: {System.Reflection.MethodBase.GetCurrentMethod()?.DeclaringType?.FullName}, " + $"Method: ValidateModel; "; logger?.LogError(ex, methodName); validationSuccessful = false; } return validationSuccessful; } } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //this is typical controller for ASP.NET MVC public class CustomersController : Controller { // some Controller code here //............. //this is typical action for editing customer public async Task<ActionResult> CustomerEdit(CustomerEdit_ViewModel model) { if (model.IsSubmit) { // Model validation is done during model binding but we need more //validating for custom validation rules ValidationUtil.ValidateModelForCustomRules(model, ModelState, null); // we have to check if model is valid if (ModelState.IsValid) { if (model.Customer1 != null) { //we update existing customer in database //redirect to the list of customers } } else { // we go for presentation of validation errors ModelState.AddModelError("", "PleaseCorrectAllErrors"); } } else { ModelState.Clear(); //go for presentation of original data if (model.Id != null) { //get Customer by Id from database } } return View("CustomerEdit", model); } }
Ինչպես տեսնում եք, մենք փոփոխել ենք Customer-partial-class-2-ը և ավելացրել ենք Customer_MetaData դաս: Այստեղ նպատակը NAME հատկության համար լրացուցիչ վավերացում ավելացնելն է, որը պահանջում է նվազագույնը 3 նիշ:
ASP.NET MVC-ում մենք կարող ենք օգտագործել ModelState սխալի հաղորդագրությունները վավերացնելու և առբերելու համար: Նկատի ունեցեք, որ մենք պետք է օգտագործեինք նաև ValidationUtil.ValidateModelForCustomRules օգտակար մեթոդը՝ հատուկ վավերացումը ձեռքով գործարկելու համար:
Կոդը և մեկնաբանությունները պետք է պարունակեն բոլոր անհրաժեշտ մանրամասները այս տեխնիկան հասկանալու համար:
Իմ C#/EF/ASP.NET 8 MVC հավելվածում ես օգտագործում եմ վերը նշված տեխնիկան մասնակի C# դասերի հետ՝ EF-ի կողմից ստեղծված դասերի ֆունկցիոնալությունը ընդլայնելու համար: Այս հնարքները պարզեցնում են սովորական սցենարները՝ դարձնելով ծածկագիրը ավելի հակիրճ և հեշտ պահպանելը: