EntityWorker.Core یک نگاشت کنندهی شیء به ارتباط است که توسعه دهندگان .NET را قادر میسازد تا با دادههای روابط ، با استفاده از اشیاء کار کنند.
EntityWorker جایگزینی برای entityframework بوده، انعطاف پذیر و بسیار سریعتر از entityframework است.
بروز رسانی
- تغییر نام خاصیت StringFy <= Stringify
- حذف EnabledMigration و افزودن متد InitializedMigration() به جای آن
- پیاده سازی OnModuleStart در جاییکه بتوانیم پایگاه دادهی خود را ایجاد و تغییرات پایگاه داده را اعمال کنیم
- ایمن سازی زنجیره تراکنش (Transaction Thread)
- پیاده سازی OnModuleConfiguration در جاییکه امکان پیکر بندی ماژولهای خود را داشته باشیم
- گردانندهی ( Handler ( Json و خاصیت Json Ignore
- افزودن خاصیت ColumnType برای مدیریت انواع دادهای (datatype) دلخواه/سفارشی
- انتزاعی کردن تراکنش، OnModuleConfiguration و OnModuleStart
- گردانندهی XML که نیازی به توالی پذیر شدن (Serializable) شیء ندارد.
- گردانندهی پکیج (EntityWorker.Core ( Package Handler
- مثالی از روابط چند به چند
- افزودن خاصیت KnownType را به PropertyType های ناشناخته مانند رابطها (Interfaces)
- Logger
- افزودن JsonDocument Attribute برای ذخیرهی دادهها به عنوان JsonObject در پایگاه داده
- افزودن XmlDocument Attribute برای ذخیرهی دادهها به عنوان Xml در پایگاه داده
- پیاده سازی [(“dbSchema [Table(“TableName”, “dbo
- پیاده سازی سازندهی EntityType
- بکارگیری پروسهی (روند) ذخیره سازی (Store Procedure) در EntityWorker.Core
- امکان نادیده گرفتن بروزرسانی برخی ویژگیها هنگام ذخیرهی یک آیتم. این مورد تنها در EntityWorker.Core >= 2.2.8 (نسخه ۲.۲.۸ به بالا) وجود دارد.
کد
Nuget
پروژه ی آزمایشی برای دانلود
تست عملکرد EntityFrameWork در مقابل EntityWorker.Core
این آزمونی میان EntityFramework و EntityWorker.Core است. Debug State (وضعیت خطایابی) در حالت Release (آزاد) است.
پشت زمینه
EntityFramework کتابخانهای بسیار عالی است، اما مدیریت Migration ها و پیادهسازی یک ساختار موجود با entityframework در واقع خیلی انعطاف پذیر نیست.
بنابراین نه تنها در رابطه با ساخت کتابخانهای که بتواند با entityframework رقابت کرده، بلکه به تمام مواردی که entityframework کم دارد نیز فکر کردم.
EntityWorker.Core نه تنها در اجرای کوئری عملکردی بسیار عالی دارد، بلکه در استفاده بسیار منعطفتر است.
نحوه استفاده از EntityWorker.Core برای ساخت و مدیریت دادههای خود را ، به آسانی به شما نشان خواهم داد.
فراهم کنندگان/ارائه دهندگان پایگاه داده
- Mssql
- PostgreSql
- Sqlite
استفاده از کد
پیکربندی GlobalConfiguration
// Set those settings under Application_Start // Those two first settings is for DataEnode Attribute /// Set the key size for dataEncoding 128 or 256 Default is 128 EntityWorker.Core.GlobalConfiguration.DataEncode_Key_Size = DataCipherKeySize.Key_128; /// Set the secret key for encoding Default is "EntityWorker.Default.Key.Pass" EntityWorker.Core.GlobalConfiguration.DataEncode_Key = "the key used to Encode the data "; /// <summary> /// The Default Value for package encryption /// </summary> EntityWorker.Core.GlobalConfiguration.PackageDataEncode_Key = "packageSecurityKey" /// Last set the culture for converting the data EntityWorker.Core.GlobalConfiguration.CultureInfo = new CultureInfo("en");
حال شروع به ساخت ماژولهای (Modules) خود از ابتدا خواهیم کرد و به EntityWorker.Core اجازه میدهیم جداول ما را بسازد.
برای مثال شروع به ساخت User (کاربر)، role (نقش)، address (آدرس) خواهیم کرد.
public abstract class Entity{ [PrimaryKey] public Guid? Id { get; set; } } [Table("Roles")] public class Role : Entity{ [NotNullable] public string Name { get; set; } [Stringify] public EnumHelper.RoleDefinition RoleDefinition { get; set; } } [Table("Users")] public class User : Entity { [NotNullable] [DataEncode] public string UserName { get; set; } [NotNullable] [DataEncode] public string Password { get; set; } [ForeignKey(typeof(Role))] public Guid RoleId { get; set; } [IndependentData] public Role Role { get; set; } [ForeignKey(typeof(Person))] public Guid PersonId { get; set; } public Person Person { get; set; } } public class Person : Entity { public string FirstName { get; set; } public string LastName { get; set; } public string SureName { get; set; } public List <Address> Addresses { get; set; } } public class Address : Entity { [NotNullable] public string Name { get; set; } public string AddressLine1 { get; set; } public string AddressLine2 { get; set; } public string TownOrCity { get; set; } public string PostalCode { get; set; } public string Area { get; set; } public Country Country { get; set; } [ForeignKey(typeof(Person))] public Guid PersonId { get; set; } }
حال بیایید Migration ها/جابجاییهایمان را بسازیم. یک Migration ( مهاجرت) ، MigrationStartUp خواهیم داشت که برخی دادهها، مانند کاربر پیشفرض و نقش، را به پایگاه داده اضافه خواهد کرد.
public class MigrationStartUp : Migration { public override void ExecuteMigration(IRepository repository) { // See here by saving the user, all under classes will be created and // ForeignKeys will be assigned automatically var users = new List<User>(); users.AddRange(new List<User>() { new User() { UserName = "Admin", Password = Methods.Encode("Admin"), Role = new Role(){Name = "Admin", RoleDefinition= EnumHelper.RoleDefinition.Developer}, Person = new Person() { FirstName = "Alen", LastName = "Toma", SureName = "Nather", Addresses = new List <Address>() { new Address() { Name = "test" } } } } }); users.ForEach(x => repository.Save(x)); base.ExecuteMigration(repository); repository.SaveChanges(); } }
اکنون، باید تعیین کنیم که کدام Migration با چه ترتیبی باید اجرا شود، که این امر از طریق ایجاد یک migrationConfig که Migration های ما را در بر خواهد گرفت حاصل میشود.
// in database will be created Generic_LightDataTable_DBMigration // this table will keep an eye on which migrations have been executed already. public class MigrationConfig : IMigrationConfig { public IList<Migration> GetMigrations(IRepository repository) { return new List<Migration>() { new MigrationStartUp() }; } }
حالا، Repository خود را که تراکنشها را در بر خواهد گرفت ایجاد خواهیم کرد.
// Here we inherit from Transaction which contains the database // logic for handling the transaction. // well that's all we need right now. public class Repository : Transaction { // there is three databases types mssql, Sqllight and postgresql public Repository(DataBaseTypes dbType = DataBaseTypes.Mssql) : base(GetConnectionString(dbType), dbType) { } protected override void OnModuleStart() { if (!base.DataBaseExist()) base.CreateDataBase(); /// Limited support for sqlite // Get the latest change between the code and the database. // Property Rename is not supported. renaming property x will end up // removing the x and adding y so there will be dataloss // Adding a primary key is not supported either var latestChanges = GetCodeLatestChanges(); if (latestChanges.Any()) latestChanges.Execute(true); // Start the migration InitializeMigration(); } // We could configrate our modules here instead of adding attributes in the class, // of course, it upp to you to decide. protected override void OnModuleConfiguration(IModuleBuilder moduleBuilder) { moduleBuilder.Entity<User>() .TableName("Users", "dbo") .HasPrimaryKey(x => x.Id, false) .NotNullable(x => x.UserName) .HasDataEncode(x => x.UserName) .HasDataEncode(x => x.Password) .HasForeignKey<Role, Guid>(x => x.RoleId) .HasIndependentData(x => x.Role) .HasForeignKey<Person, Guid>(x => x.PersonId) .HasRule<UserRule>() .HasJsonIgnore(x=> x.Password) .HasXmlIgnore(x=> x.Password); moduleBuilder.Entity<Person>() .HasColumnType(x => x.FirstName, "varchar(100)"); // OR moduleBuilder.EntityType(typeof(User)) .TableName("Users", "geto") .HasKnownType("Person", typeof(Person)) .HasPrimaryKey("Id", false) .NotNullable("UserName") .HasDataEncode("UserName") .HasDataEncode("Password") .HasForeignKey<Role>("RoleId") .HasIndependentData("Role") .HasForeignKey<Person>("PersonId") .HasRule<UserRule>() .HasJsonIgnore("Password"); } // get the full connection string public static string GetConnectionString(DataBaseTypes dbType) { if (dbType == DataBaseTypes.Mssql) return @"Server=.\SQLEXPRESS; Database=CMS; User Id=root; Password=root;"; else if (dbType == DataBaseTypes.Sqlite) return @"Data Source=D:\Projects\CMS\source\App_Data\CMS.db"; else return @"Host=localhost;Username=postgres;Password=root;Database=CMS"; } }
حال بیایید برخی کوئریها را تست و اجرا کنیم.
عملیات حذف
Entityworker آیتم ها را بصورت سلسله مراتبی حذف میکند. بیایید ببینیم که چگونه کار میکند.
using (var rep = new Repository()) { int userId = 1; // See here i made sure to load children. Delete method will make sure to delete all children // that dose not contain IndependedData Attribute rep.Get<User>().Where(x=> x.Id == userId).LoadChildren().Delete().SaveChanges(); }
ذخیره و نادیده گرفتن برخی ویژگیها
گاهی اوقات میخواهیم برخی اشیاء را ذخیره کنیم اما برخی ویژگیها را نادیده بگیریم.
این امر زمانی مفید است که برخی دادهها را از Json که حاوی برخی دادههای قدیمی است بازیابی میکنیم، که آنها را در پایگاه داده نیاز نداریم. این ویژگی تنها در EntityWorker.Core >= 2.2.8 وجود دارد.
using (var rep = new Repository()) { var us = rep.Get<User>().OrderBy(x=> x.Id).LoadChildren().ExecuteFirstOrDefault(); us.Role.Name = "Yedsfsdft"; // Se here we have execluded both RoleName and AdressName from our Update operation rep.Save(us, x=> x.Role.Name, x => x.Person.Addresses.Select(a=> a.Name)); var m = rep.Get<User>().OrderBy(x => x.Id).LoadChildren().ExecuteFirstOrDefault(); Console.WriteLine("New Value for RoleName is " + m.Role.Name); rep.SaveChanges(); }
کوئری و عبارت (Query and Expression)
مشابه Entityframework ، میتواند بارگذاری فرزندان را در بر گرفته یا نادیده بگیرید.
بگذارید ببینیم که چگونه کوئری تا زمانی که Execute یا ExecuteAsync فراخوانی نشدهاند، اجرا نخواهد شد.
using (var rep = new Repository()) { // LoadChildren indicates to load all children hierarchy. // It has no problem handling circular references. // The query does not call the database before we invoke Execute or ExecuteAsync var users = rep.Get<User>().Where(x => (x.Role.Name.EndsWith("SuperAdmin") && x.UserName.Contains("alen")) || x.Address.Any(a=> a.AddressName.StartsWith("st")) ).LoadChildren().Execute(); // let's say that we need only to load some // children and ignore some others, then our select will be like this instead var users = rep.Get<User>().Where(x => (x.Role.Name.EndsWith("SuperAdmin") && x.UserName.Contains("alen")) || x.Address.Any(a=> a.AddressName.StartsWith("st")) ).LoadChildren(x=> x.Role.Users.Select(a=> a.Address), x=> x.Address) .IgnoreChildren(x=> x.Role.Users.Select(a=> a.Role)) .OrderBy(x=> x.UserName).Skip(20).Take(100).Execute(); Console.WriteLine(users.ToJson()); Console.ReadLine(); }
نمونه نتیجه LinqToSql
using (var rep = new Repository()) { var id = Guid.NewGuid(); ISqlQueriable<User> users =rep.Get<Person>().Where(x => x.FirstName.Contains("Admin") || string.IsNullOrEmpty(x.FirstName) || string.IsNullOrEmpty(x.FirstName) == false && x.Id != id) List<User> userList = users.Execute(); string sql = users.ParsedLinqToSql; }
-- And here is the generated Sql Query SELECT [Person].[Id], [Person].[FirstName], [Person].[LastName], [Person].[SureName] FROM [Person] WHERE ( (( CASE WHEN [Person].[FirstName] LIKE String[ % Admin % ] THEN ۱ ELSE ۰ END ) = ۱ OR ( ( CASE WHEN [Person].[FirstName] IS NULL THEN ۱ ELSE CASE WHEN [Person].[FirstName] = String[] THEN ۱ ELSE ۰ END END ) ) = ۱) OR ( ((( CASE WHEN [Person].[FirstName] IS NULL THEN ۱ ELSE CASE WHEN [Person].[FirstName] = String[] THEN ۱ ELSE ۰ END END )) = ۰) AND ( [Person].[Id] <> Guid[d82d1a00 - 5eb9 - 4017 - 8c6e - 23a631757532] ) ) ) GROUP BY [Person].[Id], [Person].[FirstName], [Person].[LastName], [Person].[SureName] ORDER BY Id OFFSET 0 ROWS FETCH NEXT 2147483647 ROWS ONLY; -- All String[], Date[] and Guid[] will be translated to Parameters later on.
Linq پویا
EntityWorker قادر است کوئریهایی از نوع رشته را اجرا و مجدد آن را به Sql تبدیل کند، در اینجا شیوهی کار آن آورده شدهاست:
using (var rep = new Repository()) { string expression ="x.Person.FirstName.EndsWith(\"n\") AND (x.Person.FirstName.Contains(\"a\") OR x.Person.FirstName.StartsWith(\"a\"))"; var users = rep.Get<User>().Where(expression).LoadChildren().Execute(); }
ایجاد ISqlQueryable/IList دلخواه
میتوانیم SQL دلخواه یا حتی رویه (Procedure) ذخیره را ایجاد کرده و دادههای آن را به اشیاء یا ISqlQueryable تبدیل کنیم.
using (var rep = new Repository()) { //Create a custom ISqlQueryable, you could have store proc or a row sql query var cmd = rep.GetSqlCommand("SELECT * FROM Users WHERE UserName = @userName"); AddInnerParameter(cmd, "userName", userName, System.Data.SqlDbType.NVarChar); // Convert the result to Data List<Users> users = DataReaderConverter<User>(cmd).LoadChildren().Execute(); // Or use this to convert an unknown object eg custom object List<Users> users = (List<Users>)DataReaderConverter(cmd, typeof(User)); }
سریال سازی و عدم سریال سازی Json
EntityWorker.Core گردانندهی Json خود را دارد. بیایید نحوهی کار آن را ببینیم.
using (var rep = new Repository()) { var usersJsonString = rep.Get<User>().LoadChildren().Json(); // Json() will exclude all properties that has JsonIgnore Attributes // Convert it Back // All JsonIgnore attributes will be loaded back from the database if Primary key exist within // the json string ISqlQueryable<User> users = rep.FromJson<User>(usersJsonString).LoadChildren(); List<User> userList = users.Execute(); /// Or users.Save(); user.SaveChanges() }
سریال سازی و عدم سریال سازی XML
EntityWorker.Core گردانندهی XML خود را دارد که نیازی به قابل سریال سازی بودن شیء ندارد. بگذارید نحوهی کار آن را ببینیم:
using (var rep = new Repository()) { var usersXmlString = rep.Get<User>().LoadChildren().Xml(); // Xml() will exclude all properties that has XmlIgnore Attributes // Convert it Back // AllProperties with XmlIgnore attributes will be loaded back from // the database if Primary key exist within the Xml string ISqlQueryable<User> users = rep.FromXml<User>(usersXmlString).LoadChildren(); List<User> userList = users.Execute(); /// Or users.Save(); user.SaveChanges() }
گرداننده پکیج:
پکیجی حفاظت شده (Protected) را ایجاد میکند که حاوی فایلها و دادههایی جهت امر پشتیبانگیری یا انتقال دادهها از یک مکان به مکانی دیگر است.
توجه داشته باشید که این پکیج تنها میتواند توسط EntityWorker.Core خوانده شود.
// Create class that inherit from PackageEntity public class Package : EntityWorker.Core.Object.Library.PackageEntity { // List of objects public override List<object> Data { get; set; } // List of files public override List<byte[]> Files { get; set; } } using (var rep = new Repository()) { var users = rep.Get<User>().LoadChildren().Execute(); // You could save the result to a file or even database byte[] package = rep.CreatePackage(new Package() { Data = users.Cast<object>().ToList() }); // read the package, convert the byte[] to Package var readerPackage = rep.GetPackage<Package>(package); Console.WriteLine((readerPackage.Data.Count <= 0 ? "Failed" : "Success")); }
مثالی از روابط چند به چند
در اینجا مثالی از نحوهی استفاده از روابط چند به چند EntityWorker.Core آورده شدهاست. دو کلاس ایجاد خواهیم کرد، Menus و Article.
public class Menus { [PrimaryKey] public Guid? Id { get; set; } [NotNullable] public string DisplayName { get; set; } [ForeignKey(typeof(Menus))] public Guid? ParentId { get; set; } /// <summary> /// This is a list so the where sats will be /// Select * from Menus where ParentId = Id /// the parentId in this list will be set to Id automatically and this list will /// be children to the current Menus /// </summary> public List<Menus> Children { get; set; } [NotNullable] public string Uri { get; set; } public bool Publish { get; set; } public string Description { get; set; } /// <summary> /// This is optional if you want to search or include articles in your queries /// </summary> public List <Article> Articles { get; set;} } [Table("Articles")] public class Article { [PrimaryKey] public Guid? Id { get; set; } [NotNullable] public string ArticleName { get; set; } public bool Published { get; set; } /// Its important to se propertyName in Manytomany relations [ForeignKey( type: typeof(Menus), propertyName: "Menus")] public Guid MenusId { get; set; } // Reference to menus [IndependentData] public Menus Menus { get; set; } [ForeignKey(typeof(Article))] public System.Guid? ArticleId { get; set; } // edited but not published yet public List <Article> ArticleTemp { get; set; } } }
رویه (Procedure)
در اینجا نحوهی استفاده از رویهی ذخیره در entityworker آورده شدهاست.
-- DB CREATE PROCEDURE [dbo].[GetPerson] @FirstName varchar(50) AS BEGIN SET NOCOUNT ON; select * from Person where FirstName like @FirstName +'%' END
// Code using (var rep = new Repository()) { var cmd = rep.GetStoredProcedure("GetPerson"); rep.AddInnerParameter(cmd, "FirstName", "Admin"); ISqlQueryable<Person> data = rep.DataReaderConverter<person>(cmd).LoadChildren(); List<Person> persons = data.Execute(); // Or custom Class List<Person> persons = (List<Person>)rep.DataReaderConverter(cmd, typeof(Person)); }
Logger (گزارش گیرنده)
در اینجا نحوه گرفتن تمامی گزارشهای entityworker آورده شدهاست.
using EntityWorker.Core.Helper; using EntityWorker.Core.Interface; using System; using System.IO; // create a class that inherit from EntityWorker.Core.Interface.ILog public class Logger : EntityWorker.Core.Interface.Ilog { private string logIdentifier = $"{DateTime.Now.ToString("yyyy-MM-dd")} Ilog.txt"; private string logPath = AppDomain.CurrentDomain.BaseDirectory; public Logger() { DirectoryInfo dinfo = new DirectoryInfo(logPath); var files = dinfo.GetFiles("*.txt"); foreach (FileInfo file in files) { var name = file.Name.Split(' ')[0]; if (name.ConvertValue<DateTime?>().HasValue && file.Name.Contains("Ilog")) { if (name.ConvertValue<DateTime>().Date == DateTime.Now.Date) { logIdentifier = file.Name; break; } } } logIdentifier = Path.Combine(logPath, logIdentifier); File.Open(logIdentifier, FileMode.OpenOrCreate).Close(); } public void Dispose() { } public void Error(Exception exception) { lock (this){ using (StreamWriter stream = new StreamWriter(logIdentifier, append:true)) stream.WriteLine($"{DateTime.Now} - {exception.Message}"); } } public void Info(string message, object infoData) { #if DEBUG lock (this){ using (StreamWriter stream = new StreamWriter(logIdentifier, append:true)) stream.WriteLine($"{DateTime.Now} - {message} - \n {infoData}"); } #endif } } } // now that we created the logg class we can now tell Entityworker to begin logging // in GlobalConfiguration we assign the new created class to ILog. only exist in nuget => 2.0.0 GlobalConfiguration.Log = new Logger(); // thats all
خاصیتها
/// <summary> /// Save the property as Json object in the database /// For the moment those values cant be searched by linq. /// you will have to use row sql(JSON_VALUE) to seach them /// </summary> [JsonDocument] /// <summary> /// Save the property as xml object in the database /// For the moment those values cant be searched by linq. /// </summary> [XmlDocument] /// <summary> /// Use this when you have types that are unknown like interface which it can takes more than one type /// </summary> [KnownType] /// <summary> /// Assign a different database type for the property. /// Attributes Stringify, DataEncode and ToBase64String will override this attribute. /// </summary> /// <param name="dataType">The database type ex nvarchar(4000)</param> /// <param name="dataBaseTypes">(Optional)null for all providers</param> [ColumnType] /// <summary> /// Ignore serializing and deserializing property /// when deserializing using entityWorker.Json all Xml ignored properties will be loaded back /// from the database as long as primary key exist within the xml string. /// </summary> [XmlIgnore] /// <summary> /// Ignore serializing and deserializing property /// when deserializing using entityWorker.Json all Json ignored properties will be loaded back /// from the database as long as primary key exist within the json string. /// </summary> [JsonIgnore] /// <summary> /// This indicates that the prop will not be saved to the database. /// </summary> [ExcludeFromAbstract] /// <summary> /// Will be saved to the database as base64string /// and converted back to its original string when its read /// </summary> [ToBase64String] /// <summary> /// Property is a ForeignKey in the database. /// </summary> [ForeignKey] /// <summary> /// This attr will tell EntityWorker.Core abstract /// to not auto Delete this object when deleting parent, /// it will however try to create new or update /// </summary> [IndependentData] /// This attribute is most used on properties with type string /// in-case we don't want them to be nullable /// </summary> [NotNullable] /// <summary> /// Property is a primary key /// PrimaryId could be System.Guid or number eg long and int /// </summary> [PrimaryKey] /// <summary> /// Have different Name for the property in the database /// </summary> [PropertyName] /// <summary> /// Define class rule by adding this attribute /// ruleType must inherit from IDbRuleTrigger /// ex UserRule : IDbRuleTrigger<User/> /// </summary> /// <param name="ruleType"></param> [Rule] /// <summary> /// Save the property as string in the database /// mostly used when we don't want an enum to be saved as integer in the database /// </summary> [Stringify] /// <summary> /// Define different name for the table /// </summary> [Table] /// <summary> /// Assign Default Value when Property is null /// </summary> [DefaultOnEmpty] /// <summary> /// Choose to protect the data in the database so no one could read or decrypt /// it without knowing the key. Those data will be decrypted when you read it from the database. /// LinqToSql will also Encode the value when you select a Search those columns. /// <Example> /// .Where(x=> x.UserName == "test" || x.UserName.StartWith("a") ) /// Will be equal to /// .Where(x=> x.UserName == Encode("test") || x.UserName.StartWith(Encode("a"))) /// So no need to worry when you search those column in the dataBase /// you could Encode Address, bankAccount information and so on with ease. /// entityWorker uses a default key to both encode and decode the data but you could /// also change it. /// </Example> /// </summary> [DataEncode]
هیچ دیدگاهی نوشته نشده است.