/moranbernate

Light weight, high performance ORM for simple and robust data access for .Net applications

Primary LanguageC#MIT LicenseMIT

Moranbernate

Moranbernate is a light weight, high performance, high throughput ORM for .Net applications, allowing simple and robust data access for large scale applications.

Highlights

  • Built for high performance with scale and throughput in mind
  • Bulk operations for CRUD queries, allowing optimized performance when doing data manipulations
  • Operation atomicity - no transactions
  • Object based mapping and queries
  • Fluent Mapping compatibility - easier to convert from other ORMs
  • Composite keys support
  • .Net Core support - targeting .Net Standard 1.4 & 2.0

Getting started

Installation

NuGet

Mapping

Moranbernate requires mapping the POCO objects that will be used to access the database:

// UserDto.cs
public class UserDto
{
	public long Id { get; set; }
	public string Name { get; set; }
	public int? Age { get; set; }
	public DateTime LastLoginDate { get; set; }
}

// UserDtoMap.cs
using OhioBox.Mornabernate.Mapping;
...

public class UserDtoMap : ClassMap<UserDto>
{
	public UserDtoMap()
	{
		Table("users"); // Name of the table

		Id(x => x.Id).GeneratedBy.AutoGenerated(); // Declaring an id column, with value that is generated from the database as auto increment column
		Map(x => x.Name); // Map property Name to column Name. Note that the casing must be the same if your database is case sensative.
		Map(x => x.Age); // Since Age is nullable, the column will also be considered as nullable
		Map(x => x.LastLoginDate,"last_login_date"); // Map with custom column name
	}
}

Configuration

To use Moranbernate, initialize it in your application startup:

var assemblies = new[] { GetType().Assembly, ...  }; // Put all assemblies that includes ClassMap implementation here
MappingRepoDictionary.InitializeAssemblies(assemblies);

Running queries

Queries on Moranbernate are done by using extensions on a IDbConnection instance.

using(var conn = new MySqlConnection(ConfigurationManager.ConnectionStrings["MySql"].ConnectionString))
{
	conn.Open();
	// Run all queries here
}

Query by id

var user = conn.GetById<UserDto>(1L);

Query

var todaysUsers = conn.Query<UserDto>(q => q
		.Where(w => w
			.GreaterOrEqual(x => x.LastLoginDate, DateTime.UtcNow.Date))
		.OrderByDescending(x => x.LastLoginDate))
	.ToArray();

var usersStartingWithY = conn.Query<UserDto>(q => q
		.Where(w => w
			.StartsWith(x => x.Name, "Y")))
	.ToArray();

Insert

var newUser = new UserDto { Name = "Max Payne", LastLoginDate = DateTime.UtcNow };

conn.Insert(newUser);

Console.WriteLine(newUser.Id); // Will print the id as set from the DB;

Bulk Insert

Bulk insert allows you to insert several rows as a single query to the database, avoiding insert loops

var newUsers = new[] { new UserDto {...}, ... };

conn.BulkInsert(newUsers);

Update

Update is very easy with Moranbernate:

var user = conn.GetById<UserDto>(1L);
user.LastLoginDate = DateTime.UtcNow;

conn.Update(user);

By default update is done according to the Id property, as defined in the mapping.

Same result can be achieved by user UpdateByQuery:

conn.UpdateByQuery<UserDto>(
		u => u.Set(x => x.LastLoginDate, DateTime.UtcNow), 
		q => q.Equal(x => x.Id, 1L));

Delete

Delete works the same as update. You can delete an object, which is then deleted by its Id, or by specifing a query, which you can use to delete a bulk of rows at a time:

var user = conn.GetById<UserDto>(1L);
conn.Delete(user);

// And with query:
conn.DeleteByQuery<UserDto>(q => q.LessThan(x => x.LastLoginDate, DateTime.UtcNow.Date - 365));

Composite keys

Moranbernate supports mapping of composite keys:

public class UserPermissionDto : ClassMap<UserPermissionDto>
{
	public UserPermissionDto()
	{
		Table("users_permissions");

		CompositeId()
			.KeyProperty(x => x.UserId)
			.KeyProperty(x => x.PermissionId);
	}
}

Custom types

Moranbernate also support mapping of custom types to columns.

For example, lets assume UserDto has a list of tags:

public class UserDto
{
	...

	IList<string> Tags;
}

Moranbernate allows you to create a custom mapper which knows how to resolve and map this time into a single Text column in the DB:

public class CsvPersister<T> : ICustomTypeMapper<T[]>
{
	private readonly char[] _separator = new[] { ',' };

	public object ToParameter(T[] input)
	{
		return string.Join(",", input.ToString());
	}

	public T[] FromDb(object input)
	{
		return input.ToString()
			.Split(_separator, StringSplitOptions.RemoveEmptyEntries)
			.Select(x => Convert.ChangeType(x, typeof(T))).OfType<T>()
			.ToArray();
	}
}

In the mapping, map the column with the new mapper:

public class UserDtoMap : ClassMap<UserDto>
{
	public UserDtoMap()
	{
		...
		Map(x => x.Tags).CustomType(new CsvPersister<string>());
	}
}

Database support

Current version of Mornabernate natively support MySql dialect, and tested with MySql.Data 6.9.9 MySql 5.6 database.
Moranbernate can be expended to support more databases by implementing dialect.

Development

How to contribute

We encorage contribution via pull requests on any feature you see fit.

When submitting a pull request make sure to do the following:

  • Check that new and updated code follows OhioBox existing code formatting and naming standard
  • Run all unit and integration tests to ensure no existing functionality has been affected
  • Write unit or integration tests to test your changes. All features and fixed bugs must have tests to verify they work Read GitHub Help for more details about creating pull requests

Running integration tests

Moranbernate has unit tests that covers its logic, and integration tests to test actual queries against a real, live database to validate query functionality.

Running integration tests requires an instance of MySql database on your local machine (can be hosted on Docker of course).
Once an instance is ran, run these from command line:

scripts\import_databases.bat PUT_ROOT_DB_USER_NAME_HERE PUT_ROOT_DB_PASSWORD_HERE
scripts\generate_connection_strings.bat PUT_DB_USER_NAME_HERE PUT_DB_PASSWORD_HERE

The first script generates a schema named moranbernate on the local instance with the tables required by the tests.
The second one generates a connection string configuration file for the test project which references this database with the given user and password.

Now you can simply run the tests in Visual Studio or with NUnit test runner.