paint-brush
Entity Framework 8 – Részleges osztályok Trükkök, amelyeket tudnia kelláltal@markpelf
Új történelem

Entity Framework 8 – Részleges osztályok Trükkök, amelyeket tudnia kell

által Mark Pelf17m2025/02/19
Read on Terminal Reader

Túl hosszú; Olvasni

Az EF 8 – Database First megközelítésben a generált EF osztályok nem bővíthetők közvetlenül további funkciókkal. Ennek kiküszöbölésére részleges C# osztályokat használhatunk. Ez a cikk hasznos trükköket mutat be a funkciók bővítéséhez EF/ASP.NET környezetben.
featured image - Entity Framework 8 – Részleges osztályok Trükkök, amelyeket tudnia kell
Mark Pelf HackerNoon profile picture

Ez a cikk számos technikát mutat be részleges C# osztályok használatával az EF 8/ASP.NET8 gyakori problémáinak megoldására.


Absztrakt: Az EF 8 – Database First megközelítésben a generált EF osztályok nem bővíthetők közvetlenül további funkciókkal, mivel a modell újragenerálásakor felülíródnak. Ennek kiküszöbölésére részleges C# osztályokat használhatunk. Ez a cikk hasznos trükköket mutat be a funkciók bővítéséhez EF/ASP.NET környezetben.

1 Entity Framework Core – Adatbázis első megközelítés

A C#/ASP.NET 8 MVC projektemben az Entity Framework Core Database First megközelítést használom. Erre azért van szükség, mert a különböző technológiákban több alkalmazás ugyanarra az SQL Server adatbázisra támaszkodik. Az adatbázisból modell generálásához az EFCorePowerTools programot használom.


A probléma akkor merül fel, ha az előállított EF-modellt frissíteni kell, hogy tükrözze az adatbázisséma változásait. Mivel a generált EF entitásosztályok felülírásra kerülnek, a rajtuk végzett módosítások elvesznek. Ez azért jelent problémát, mert időnként ki kell terjesztenünk az EF entitásokat az alkalmazáson belüli használathoz.


A probléma megoldásához részleges C# osztályokat tartalmazó trükkökre támaszkodom, amelyeket alább ismertetünk.

2 Tipikus helyzet a C#/EF/ASP.NET alkalmazásban

Egy tipikus C#/EF/ASP.NET 8 MVC alkalmazásban a helyzet a következő kódhoz hasonlíthat:

 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //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); } }

Itt a Customer-partial-class-1 egy EF által generált osztály (az adatbázisból visszafejtve). Tartalmaz néhány érvényesítési attribútumot, amelyek megfelelnek az adatbázis megkötéseinek. Ezt az osztályt általában egy modellosztályban használják (pl. CustomerEdit_ViewModel ), ahol az érvényesítési attribútumok egy művelet vagy metódus során kerülnek feldolgozásra (pl. CustomerEdit ).

3 1. trükk – Használjon részleges osztályokat egyéni tulajdonságok hozzáadásához

Ha tulajdonságokat kell hozzáadnunk a generált osztályhoz, akkor közvetlenül nem módosíthatjuk a Customer-partial-class-1 , mert ez a változtatások felülírásához vezetne. Ehelyett létrehozhatjuk a Customer-partial-class-2-t, és hozzáadhatjuk egyéni tulajdonságainkat. Ahhoz, hogy az EF ne vegye fel ezeket a tulajdonságokat a modellbe, a [NotMapped] attribútumot kell használnunk.


Íme egy példa:

 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //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); } }

A részletes megjegyzésekkel ellátott kódnak magától értetődőnek kell lennie.

4 2. trükk – Használjon részleges osztályokat egyéni érvényesítési attribútumok hozzáadásához

Néha az EF-ben automatikusan generált érvényesítési attribútumok nem elegendőek, és egyéni érvényesítési szabályokat kell hozzáadnunk. Ismét a részleges osztályok jönnek a segítségre. Alább egy példa:

 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //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); } }

Amint láthatja, módosítottuk a Customer-partial-class-2-t, és hozzáadtunk egy Customer_MetaData osztályt. A cél az, hogy további ellenőrzést adjunk hozzá a NAME tulajdonsághoz, amely legalább 3 karaktert igényel.


Az ASP.NET MVC-ben a ModelState segítségével ellenőrizhetjük és lekérhetjük a hibaüzeneteket. Vegye figyelembe, hogy a ValidationUtil.ValidateModelForCustomRules segédprogram metódusát is használnunk kellett az egyéni érvényesítés kézi indításához.


A kódnak és a megjegyzéseknek tartalmazniuk kell minden szükséges részletet a technika megértéséhez.

5 Következtetés

A C#/EF/ASP.NET 8 MVC alkalmazásomban a fenti technikákat részleges C# osztályokkal használom az EF által generált osztályok funkcionalitásának kiterjesztésére. Ezek a trükkök leegyszerűsítik a gyakori forgatókönyveket, így a kód tömörebb és könnyebben karbantartható.