Loading...

EntityWorker.Core - An Alternative to Entity Framework

View: 671    Dowload: 0   Comment: 0   Post by: admin   Category: ASP.NET Framework   Fields: Other

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

1- Rename Attribute StringFy => Stringify.
2- Remove EnabledMigration and adding InitiolizeMigration() method instead.

3- Implementing OnModuleStart where we could create ower database and apply db changes.

4- Making Transaction Thread Safe.

Code

https://github.com/AlenToma/EntityWorker.Core

Nuget

Stable version: EntityWorker.Core >= 1.2.5

https://www.nuget.org/packages/EntityWorker.Core/

Test Project to Download is Available Here

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 also is much flexible to use.

I am going to show you how you could use EntityWorker.Core to build and manage your data with ease.

Updates

Implementing GetCodeLatestChanges,

it will return all the code changes and you will be able to apply it to the database.

We wont have to create an iniMigration anymore

Database Providers

1- Mssql

2- PostgreSql

3- Sqlite

Using the Code

Configurate 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 ";

/// Last set the culture for converting the data
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., Userrole and address.

[Table("Roles")]
public class Role{

    [PrimaryId]
    public Guid? Id { get; set; }

    [NotNullable]
    public string Name { get; set; }

    [Stringify]
    public EnumHelper.RoleDefinition RoleDefinition { get; set; }
}

[Table("Users")]
public class User{

    [PrimaryId]
    public Guid? Id { get; set; }

    [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 {

    [PrimaryId]
    public Guid Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string SureName { get; set; }

    public List<Address> Addresses { get; set; }
}

public class Address
{
    [PrimaryId]
    public Guid? Id { get; set; }

    [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 two migrations, MigrationIni that will create the tables structures and MigrationStartUp that will add some data to the database, like default user and role.

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();
    }
}

Now, we will have to specify which migration should be executed, by creating a migrationConfig that will include our migrations.

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

Now, we will create our Repository that will include the transaction.

  // Here we inherit from Transaction which contains the database 
    // logic for handling the transaction.
    // well thats 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
            InitiolizeMigration();
            base.OnModuleStart();
        }

        // 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";
        }
    }

Now let's test and execute some queries.

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())
{
     // 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 Result Example

using (var rep = new Repository())
{
   ISqlQueriable<User> users = rep.Get<User>().Where(x =>
   (x.Role.Name.EndsWith("SuperAdmin") &&
    x.UserName.Contains("alen")) ||
    x.Address.Any(a => (a.AddressName.StartsWith("st") ||
    a.AddressName.Contains("mt"))).
    Skip(20).Take(100).Execute();
    );

    List<User> userList = users.Execute();
    var sql = users.ParsedLinqToSql;
}

// And here is the generated SQL Query
 SELECT distinct Users.* FROM Users
 left join [Roles] CEjB on CEjB.[Id] = Users.[Role_Id]
 WHERE (([CEjB].[Name] like String[%SuperAdmin]
 AND [Users].[UserName] like String[%alen%])
 OR  EXISTS (SELECT 1 FROM [Address]
 INNER JOIN [Address] MJRhcYK on Users.[Id] = MJRhcYK.[User_Id]
 WHERE (([Address].[AddressName] like String[st%] OR
 [Address].[AddressName] like String[%mt%]))))
 ORDER BY Id
 OFFSET 20
 ROWS FETCH NEXT 100 ROWS ONLY;
 // All String[] and Date[] will be translated to Parameters later on.

Create Custom ISqlQueryable/IList

We could create custom sql or even store procedure and convert its data to objects or to 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)); 
    }

Attributes

/// <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 diffrent 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.

EntityWorker.Core - An Alternative to Entity Framework

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.

Posted on 08-02-2018 

Comment:

To comment you must be logged in members.

Files with category

 
File suggestion for you
Loading...
File top downloads
Loading...
Loading...
Codetitle - library source code to share, download the file to the community
Copyright © 2018. All rights reserved. codetitle Develope by Vinagon .Ltd