Introduction
EntityWorker.Core
is an object-relation mapper that enables .NET developers to work with relations data using objects. EntityWorker
is an alternative to entityframework
, is more flexible and much faster than entity framework.
Update
- Rename Attribute StringFy => Stringify
- Remove
EnabledMigration
and adding InitializeMigration()
method instead - Implementing
OnModuleStart
where we could create our database and apply db changes - Making Transaction Thread Safe
- Implementing
OnModuleConfiguration
where we could configrate our modules - Json Handler and
JsonIgnore
Attribute - Added
ColumnType
attribute to handle custom datatype
- Making transaction,
OnModuleConfiguration
and OnModuleStart abstract
- XML handler that doesn't require the object to be Serializable
EntityWorker.Core
Package Handler - Example of Many to Many Relationships
- Add attribute
KnownType
for unknown PropertyTypes like interfaces - Logger
- Added
JsonDocument
Attribute to save data as JsonObject in the database - Added
XmlDocument
Attribute to save data as Xml in the database - Implement db Schema
[Table("TableName", "dbo")]
- Implementing EntityType Builder
- Using store procedure in entityworker.core
- Able to ignore updating some properties when saving an item. exist only in EntityWorker.Core >= 2.2.8
Code
Github
Nuget
EntityWorker.Core
Test Project to Download
LightData.CMS
IProduct
EntityFrameWork vs EntityWorker.Core Performance Test
This is a test between EntityFramework
and EntityWorker.Core
.
Debug State is Release

Background
Entityframework
is a great library, but managing migrations and implementing an existing structure with entityframework
is really not so flexible.
So I thought about building a library that could compete with entityframework
, but also think of all the things that entityframework
is missing.
EntityWorker.Core
has a great performance executing query, but is also much flexible to use.
I am going to show you how you could use EntityWorker.Core
to build and manage your data with ease.
Database Providers
- Mssql
- PostgreSql
- Sqlite
Using the Code
Configurate GlobalConfiguration
EntityWorker.Core.GlobalConfiguration.DataEncode_Key_Size = DataCipherKeySize.Key_128;
EntityWorker.Core.GlobalConfiguration.DataEncode_Key = "the key used to Encode the data ";
EntityWorker.Core.GlobalConfiguration.PackageDataEncode_Key = "packageSecurityKey"
EntityWorker.Core.GlobalConfiguration.CultureInfo = new CultureInfo("en");
Now we will start building our Modules from the start and let EntityWorker.Core
build our tables.
We will start building, e.g., User
, role
and 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; }
}
Now let's build our migrations. We will have one migration, MigrationStartUp
that will add some data to the database, like default user and role.
public class MigrationStartUp : Migration
{
public override void ExecuteMigration(IRepository repository)
{
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();
}
}
Now, we will have to specify which migration should be executed in wich order, by creating a migrationConfig
that will include our migrations.
public class MigrationConfig : IMigrationConfig
{
public IList<Migration> GetMigrations(IRepository repository)
{
return new List<Migration>()
{
new MigrationStartUp()
};
}
}
Now, we will create our Repository
that will include the transaction
.
public class Repository : Transaction
{
public Repository(DataBaseTypes dbType = DataBaseTypes.Mssql) :
base(GetConnectionString(dbType), dbType)
{
}
protected override void OnModuleStart()
{
if (!base.DataBaseExist())
base.CreateDataBase();
var latestChanges = GetCodeLatestChanges();
if (latestChanges.Any())
latestChanges.Execute(true);
InitializeMigration();
}
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)");
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");
}
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";
}
}
Now let's test and execute some queries.
Delete Operation
Entityworker delete items hierarki. lets se how this works
using (var rep = new Repository())
{
int userId = 1;
rep.Get<User>().Where(x=> x.Id == userId).LoadChildren().Delete().SaveChanges();
}
Save and Ignore some Propeties
Some time we would want to save some object but ignore some properties.
this is usefull when we retrive some data from json that containes some old data, that we dont want them in the db. Only exist in 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";
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
Like EntityframeWork
, you could Include and Ignore loading children. Let's see how the query won't be executed until Execute
or ExecuteAsync
is called.
using (var rep = new Repository())
{
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();
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 Result Example
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;
}
SELECT
[Person].[Id],
[Person].[FirstName],
[Person].[LastName],
[Person].[SureName]
FROM
[Person]
WHERE
(
((
CASE
WHEN
[Person].[FirstName] LIKE String[ % Admin % ]
THEN
1
ELSE
0
END
) = 1
OR
(
(
CASE
WHEN
[Person].[FirstName] IS NULL
THEN
1
ELSE
CASE
WHEN
[Person].[FirstName] = String[]
THEN
1
ELSE
0
END
END
)
)
= 1)
OR
(
(((
CASE
WHEN
[Person].[FirstName] IS NULL
THEN
1
ELSE
CASE
WHEN
[Person].[FirstName] = String[]
THEN
1
ELSE
0
END
END
)) = 0)
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;
Dynamic Linq
EntityWorker is able to execute querys of type string and convert it back to Sql, here is how it works
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();
}
Create Custom ISqlQueryable/IList
We could create custom SQL or even stored procedure and convert its data to objects or to ISqlQueryable
.
using (var rep = new Repository())
{
var cmd = rep.GetSqlCommand("SELECT * FROM Users WHERE UserName = @userName");
AddInnerParameter(cmd, "userName", userName, System.Data.SqlDbType.NVarChar);
List<Users> users = DataReaderConverter<User>(cmd).LoadChildren().Execute();
List<Users> users = (List<Users>)DataReaderConverter(cmd, typeof(User));
}
Json Serializing and Deserializing
Entityworker.Core
has its own json handler. Let's ses how it works.
using (var rep = new Repository())
{
var usersJsonString = rep.Get<User>().LoadChildren().Json();
ISqlQueryable<User> users = rep.FromJson<User>(usersJsonString).LoadChildren();
List<User> userList = users.Execute();
users.Save();
user.SaveChanges()
}
XML Serializing and Deserializing
Entityworker.Core
has its own XML handler that doesn't require the object to be Serializable. Let's see how it works:
using (var rep = new Repository())
{
var usersXmlString = rep.Get<User>().LoadChildren().Xml();
ISqlQueryable<User> users = rep.FromXml<User>(usersXmlString).LoadChildren();
List<User> userList = users.Execute();
users.Save();
user.SaveChanges()
}
Package Handler
Create Protected
package that contains files and data for backup purpose or moving data from one location to another.
Note that this package can only be read by EntityWorker.Core
.
public class Package : EntityWorker.Core.Object.Library.PackageEntity
{
public override List<object> Data { get; set; }
public override List<byte[]> Files { get; set; }
}
using (var rep = new Repository())
{
var users = rep.Get<User>().LoadChildren().Execute();
byte[] package = rep.CreatePackage(new Package() { Data = users.Cast<object>().ToList() });
var readerPackage = rep.GetPackage<Package>(package);
Console.WriteLine((readerPackage.Data.Count <= 0 ? "Failed" : "Success"));
}
Example of Many to Many Relationships
This is an example of how to use Many to Many Relationships in EntityWorker.Core
. We will create two classes, Menus
and Article
.
public class Menus
{
[PrimaryKey]
public Guid? Id { get; set; }
[NotNullable]
public string DisplayName { get; set; }
[ForeignKey(typeof(Menus))]
public Guid? ParentId { get; set; }
public List<Menus> Children { get; set; }
[NotNullable]
public string Uri { get; set; }
public bool Publish { get; set; }
public string Description { get; set; }
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; }
[ForeignKey( type: typeof(Menus), propertyName: "Menus")]
public Guid MenusId { get; set; }
[IndependentData]
public Menus Menus { get; set; }
[ForeignKey(typeof(Article))]
public System.Guid? ArticleId { get; set; }
public List<Article> ArticleTemp { get; set; }
}
}
Procedure
Here is how you could use store procedure in entityworker
CREATE PROCEDURE [dbo].[GetPerson]
@FirstName varchar(50)
AS
BEGIN
SET NOCOUNT ON;
select * from Person where FirstName like @FirstName +'%'
END
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();
List<Person> persons = (List<Person>)rep.DataReaderConverter(cmd, typeof(Person));
}
Logger
Here is how you could get all entityworker logs
using EntityWorker.Core.Helper;
using EntityWorker.Core.Interface;
using System;
using System.IO;
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
}
}
}
GlobalConfiguration.Log = new Logger();
Attributes
/// <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]
Points of Interest
Please feel free to write what you think, and also check the project site to see the full documentation.