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
[JsonDocument]
[XmlDocument]
[KnownType]
[ColumnType]
[XmlIgnore]
[JsonIgnore]
[ExcludeFromAbstract]
[ToBase64String]
[ForeignKey]
[IndependentData]
[NotNullable]
[PrimaryKey]
[PropertyName]
[Rule]
[Stringify]
[Table]
[DefaultOnEmpty]
[DataEncode]
Points of Interest
Please feel free to write what you think, and also check the project site to see the full documentation.