Giter Site home page Giter Site logo

shaolinq's Introduction

Shaolinq

Shaolinq is a powerful ORM and Linq provider for C# and .NET. It provides a thoughtful, fast and powerful alternative to Linq to SQL and the Entity Framework.

Notable features:

  • Code first object model
  • Top class LINQ support with everything entity framework supports and beyond
  • First class composite primary key support
  • Fully configurable naming by convention using attributes or configuration
  • Full support for async/await that goes beyond what's possible with the Entity Framework
  • Full support for recursively including collection properties - handles all necessary and complex join(s) for you
  • Automatic schema creation and migration
  • Natural object model architecture with both high-level and super-fine-grained access to SQL via LINQ
  • Insanely fast performance by using dynamically generated code (System.Reflection.Emit) to avoid slow dynamic reflection calls (dynamic calls is used by most other ORMs)
  • Automatic LINQ query caching to avoid slow labmda expression compile times
  • Truely abstract object and query model: single code based, multiple backends
  • One-Many and Many-Many relations supported within the object model and LINQ
  • Advanced LINQ support including natural support of server side functions and operations including SQL LIKE, date time conversions, and C# operation converstions such as ToLower() and ToUpper() that work as naturally as native C#
  • Support for not just selecting but deleting sets of objects using LINQ based syntax
  • First class support for Sqlite, MySql and Postgres and SQL server
  • Support for client-side computed property values for automatic performance critical denormalization where necessary
  • Easily support new providers with full LINQ support (less than 100 lines of code)
  • Natural transaction model. Object graphs are saved together and saving objects individually is a thing of the past
  • Unique deflated reference architecture allowing partial object updates and references without pre-requisite reads required by most other ORMs

Motivation:

The lack of a suitable free code-first ORM with support for major open-source databases in 2007-2008 for .NET motivated me to design a write a new one with a query system based on C# operator overloading. I added LINQ support in mid 2008 after Microsoft released LINQ in .NET 3.5. The primary goals of Shaolinq was:

  • A code-first object model
    • Why write XML when you can write C#?
    • Why write SQL when you can write C#?
  • Performance close to or faster than using SQL directly
  • Avoiding reflection: Pushing the boundaries of bare-to-the-metal code in .NET
  • Tracking and commiting changes at the property/column level rather than the object level. Changing a single property on an object should only generate an UPDATE statement for the respective column.
  • Automatic cached pre-compiled LINQ query support
  • Support for executing and projecting stored procedures into objects
  • WYSIWYG schema
    • The schema should not look like it was designed for an ORM
  • Never generate special columns or special tables
    • The object nature of the object-model should not undermine the relational nature of the database-schema
    • It should be easy to consume any existing database schema without any special mapping
  • Comprehensive LINQ support
  • Fully support LINQ as a first class primary query language unlike other ORMs that support only base SELECT and WHERE clauses
  • Make it super easy to support new databases
  • Adding new providers to the ORM should not require understanding LINQ, expression trees, etc.
  • Support Sqlite, MySql and Postgres and SQL servver out of the box
  • Require only a configuration (web.config) change to switch underlying database providers

Code

Define data model:

// Object inheriting from generic DataAccessObject to get "Id" primary key property

[DataAccessObject]
public abstract class Person : DataAccessObject<Guid>
{
	[AutoIncrement]
	[PersistedMember]
	public abstract Guid Id { get; set; }
	
	[PersistedMember]
	public DateTime? Birthdate {get; set; }
	
	[PersistedMember]
	public abstract int Age { get; set; }
	
	[PersistedMember]
	public abstract string Name { get; set; }
	
	[PersistedMember]
	public abstract Person BestFriend { get; set; }
	
	[BackReference]
	public abstract BorrowedBook { get; set; }
	
	[Description]
	[Index(LowercaseIndex = true)]
	[ComputedTextMember("{Name} of age {Age}")]
	public abstract string Description { get; set; }
}

// Object inheriting from non generic DataAccessObject to manually define its own primary keys

[DataAccessObject]
public abstract Book : DataAccessObject
{
	[PrimaryKey("$(TYPE_NAME)$(PROPERTY_NAME)")]
	[PersistedMember]
	public abstract long SerialNumber { get; set; }
		
	[PrimaryKey]
	[PersistedMember]
	public abstract string PublisherName { get; set; }
	
	[PersistedMember]
	public abstract string Title { get; set; }
	
	[RelatedDataAccessObjects]
	public abstract RelatedDataAccessObjects<Person> Borrowers { get; }
}

// The data access model - defines all types/tables

[DataAccessModel]
public abstract class ExampleModel : DataAccessModel
{
    [DataAccessObjects]
    public abstract DataAccessObjects<Book> Books { get; }
    
    [DataAccessObjects]
    public abstract DataAccessObjects<Person> People { get; }
}

Create SQLite database:

using Shaolinq;
using Shaolinq.Sqlite;

static void Main()
{
	var configuration = SqliteConfiguration.Create(":memory:");
	var model = DataAccessModel.BuildDataAccessModel<ExampleModel>(configuration);

	model.Create(DatabaseCreationOptions.DeleteExistingDatabase);
}

Create MySQL database:

using Shaolinq;
using Shaolinq.MySql;

static void Main()
{
	var configuration = = MySqlConfiguration.Create("ExampleDatabase", "localhost", "root", "root");
	var model = DataAccessModel.BuildDataAccessModel<ExampleModel>(configuration);

	model.Create(DatabaseCreationOptions.DeleteExistingDatabase);
}

Insert objects:

using (var scope = new DataAccessScope())
{
	var person = model.people.Create();
	
	person.Name = "Steve";
	
	scope.Complete();
}

// Insert object using distributed transaction

using (var scope = new TransactionScope())
{
	var person = model.people.Create();
	
	person.Name = "Steve";
	person.Age = 18;
	
	scope.Complete();
}

Insert object asynchronously:

using (var scope = new DataAccessScope())
{
	var person = model.People.Create();
	
	person.Name = "Steve";
	person.Age = 18;
	
	await scope.CompleteAsync();
}

Update object asynchronously and without needing to performa SELECT query:

using (var scope = new DataAccessScope())
{
	// Gets a reference to an object with a composite primary key without hitting the database
	
	var book  =  model.Books.GetReference(new { Id = 100, PublisherName = "Penguin" });
	
	book.Title = "Expert Shaolinq";
	
	// Will throw if the book (above) does not exist on commit in a single trip to the database
	
	await scope.CompleteAsync();
}

Perform queries with implicit joins and explicit joins using Include. Query for all books and for each book all borrowers and for each borrower their best friend. Then print out the borrower and their best friends' name.

var books = await model.Books.Include(c => c.Borrowers.IncludedItems().BestFriend).ToListAsync();

foreach (var value in books.Borrowers.Items().SelectMany(c => new { c.Name, BestFriendName = c.BestFriend.Name })))
{
	Console.WriteLine($"Borrower: {value.Name} BestFriend: {value.BestFriend.Name}")
}

Asynchronously find the age of all people in the database

var averageAge = await model.People.AverageAsync(c => c.Age);

Console.WriteLine($"Average age is {averageAge}");

Delete all people named Steve from the database using LINQ syntax

using (var scope = new DataAccessScope())
{
	await model.People.Where(c => c.Name == "Steve").DeleteAsync();
	
	// or
	
	await model.People.DeleteAsync(c => c.Name == "Steve");
	
	Console.WriteLine("Deleted all people named Steve");
	
	await scope.CompleteAsync();
}

Asynchronously enumerate all people whos name starts with Steve using fast server-side case-insensitive index

using (var enumerator = model.People.Where(c => c.Description.ToLower().StartsWith("steve")).GetAsyncEnumerator())
{
	while (await enumerator.MoveNextAsync())
	{
		Console.WriteLine($"Name: {enumerator.Current.Name}");
	}
}

// Query using SELECT FOR UPDATE (lock individual row)

using (var scope = new DataAccessScope())
{
	var person = await model.People.SelectForUpdateAsync(c => c.Name == "Steve");
	
	person.Age = 19;
	
	await scope.CompleteAsync();
}

Find all people whos name contains 's' server-side two different ways

var people1 = await model.People.Where(c => c.Name.IsLike("%s%")).ToListAsync();
var people2 = await model.People.Where(c => c.Name.IndexOf("s") >= 0).ToListAsync();

Print the names all people who have a bestfriend who has a bestfriend whos name is "Steve"

// Will perform automatic implicit left join on BestFriend
var people = await model
	.People
	.Where(c => c.BestFriend.BestFriend.Name == "Steve")
	.WithEachAsync(Console.WriteLine);

Assign a person's best friend without querying for the best friend if you know the object primary keys.

using (var scope = new DataAccessScope())
{
	// No query performed
	var person1 = model.People.GeReference(personId);
	
	// No query performed
	person1.BestFriend = model.People.GetReference(bestFriendId);
	
	// A single UPDATE statement is performed
	await scope.CompleteAsync();
}

Find all people born in December using server-side date functions

var people = await model.Where(c => c.Birthdate.Year == 12).ToListAsync();

// Find all people and books that are related using classic linq join syntax

var result = await (from book in model.Books
	join person in model.People on book equals person.BorrowedBook
	select new { person }).ToListAsync();

// Asynchronously aggregate and enumerate (all people grouped by name with average age)

var values = await (from person in model.People
	group person by person.Name
	select new { name = person.Name, AverageAge = person.Average(c => Age) }).GetAsyncEnumerator();
	
while (await values.MoveNextAsync())
{
	var value = values.Current;
	
	Console.WriteLine($"Average age of person with name of {value.Name} is {value.AverageAge}");
}

Copyright (c) 2007-2016 Thong Nguyen ([email protected])

shaolinq's People

Contributors

asizikov avatar dependabot[bot] avatar jeffgos avatar maverik avatar samcook avatar tumtumtum avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shaolinq's Issues

Object should be inserted into cache before it is initialized

At the moment an object is submitted to the cache only after it has been member initalised. Performance can be improved by submitting an object to the cache in case any of its members or members members reference itself because the partially constructed reference should be used.

Timestamps

Hey, im new on .net dev
but i write some app, sites... in php using laravel. and this have a nice orm like this.

a nice function of eloquent is "timestamps"
Timestamps
By default, Eloquent expects created_at and updated_at columns to exist on your tables.

every new item is added a created_at time, and update populate column updated_at.

is possible to implement.

another function is SoftDelete.

nice working

tnk's
[]'s

GroupBy seems messed up (and do these ever get responded to?)

This query:
var salesTotals =
_postgresDataAccessModel.ItemSales.Where(
sales => sales.CompanyId == companyId && sales.LocationId == locationId &&
sales.ItemType == "Tap" &&
sales.DateSold >= adjustedStartTime && sales.DateSold <= endDate)
.GroupBy(sales => new {sales.MenuItemProductId, sales.DisplaySize})
.Select(sales => new
{
sales.Key.MenuItemProductId,
sales.Key.DisplaySize,
quantity = sales.Count()
}).ToList();

(over about 5000 records)
Takes two minutes to run in Shaolinq, and about 5 seconds in PGAdmin

ForUpdate behaviour

How should ForUpdate work (with Postgres specifically)?

This works as expected:
dataModel.Categories.Where(x => x.Id == categoryId).ForUpdate().ToList().First();

SELECT "T0"."CategoryId", "T0"."Name"
FROM "Category" AS "T0"
WHERE (("T0"."CategoryId") = ('28ac0432-a4ca-409b-9c9c-510e7d917d93')) FOR UPDATE

These don't:
dataModel.Categories.ForUpdate().Where(x => x.Id == categoryId).ToList().First();

SELECT "T0"."CategoryId", "T0"."Name"
FROM "Category" AS "T0"
WHERE (("T0"."CategoryId") = ('28ac0432-a4ca-409b-9c9c-510e7d917d93'))

dataModel.Categories.Where(x => x.Id == categoryId).ForUpdate().First(); or
dataModel.Categories.ForUpdate().First(x => x.Id == categoryId);

SELECT "T0"."CategoryId", "T0"."Name"
FROM "Category" AS "T0"
WHERE (("T0"."CategoryId") = ('28ac0432-a4ca-409b-9c9c-510e7d917d93')) LIMIT 1

SelectForUpdate missing. PrimaryKey attribute doesn't work

I'm decorating a property with the PrimaryKey attribute but I still get an error saying a Primary key is missing (it is still looking for Table Name + Id)

You're documentation shows a SelectForUpdate to do bulk updates, but it does not appear to be available

AsyncRewriter fails when used on expression body method

Steps to reproduce:

  • Create class library project targeting .NET 4.6
  • Install Shaolinq.AsyncRewriter 1.1.1.989
  • Create new class:
    public partial class Class1
    {
        [RewriteAsync]
        public int Foo() => 10;
    }
  • Try to build the project

This will produce an invalid C# code in GeneratedAsync.cs file:

namespace ClassLibrary3
{
#pragma warning disable
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using global::ClassLibrary3;

    public partial class Class1
    {
        public Task<int> FooAsync()
        {
            return FooAsync(CancellationToken.None);
        }

        => 10;
        public async Task<int> FooAsync(CancellationToken cancellationToken) => 10;
    }
}

CreateDatabaseAndSchema does not honour PostgresSqlDatabaseContextInfo.SchemaName

First of all, I just discovered your ORM an hour back, and want to thank you for creating something that worked with postgresql out of the box. Right now I can't even get Npgsql.EntityFramework to do that (ticket open on their site).

Now for the actual problem:

This may be a specific implementation issue, but I'm trying to work with custom schema name where all the new tables should reside and I keep getting exception that the schema doesn't exist (which is perfectly fine since it just created fresh database).

Having a browser through relevant source, I've concluded that we basically need to override this in PostgresSharedSqlDatabaseSchemaManager and create the schema explicitly and then proceed with base CreateSchema which assumes that the relevant schema name is already there.

I'll keep digging on how to fix this for a bit (pointers welcome!).

AsyncRewriter generates invalid code within a lock

AsyncRewriter inserts async method calls inside code that is within a lock section, however this won't compile (Cannot await in the body of a lock statement):

lock (lockObj)
{
    // can't do this inside a lock
    await this.MethodAsync(cancellationToken).ConfigureAwait(false);
}

Can be worked around by using something like SemaphoreSlim instead:

await semaphoreSlim.WaitAsync();
try
{
    // synchronised code here
}
finally
{
    semaphoreSlim.Release();
}

Shaolinq.Sqlite wrong dependency

I installed Shaolinq and Shaolinq.Sqlite through NPM. The last version "1.1.1.989" installs System.Data.Sqlite 1.0.103.0.
When this insruction :
var model = DataAccessModel.BuildDataAccessModel(configuration);
is about to be executed the debugger says that "System.Data.Sqlite 1.0.102.0" could not be found.
I forced the uninstall of version "1.0.103.0" and installed MSIL version 1.0.102 witch is deprecated. Now it gives me an ArgumentNullException on the same instruction.
Any suggestions would be greatly appreciated.

Add a fallback for Column recommended lenght for enum fields

When I have a DataAcsessObject with enum property, and property has no values defined:

   [DataAccessObject]
   public abstract class DbType : DataAccessObject<Guid>
   {
        [PersistedMember]
        public abstract Enum? EnumMember { get; set; }
   }

    public enum Enum
    {
            // No values defined here!
    }

Shaolinq is trying to guess the size of the column and fails with the following exception:

XXX : System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
  ----> System.InvalidOperationException : Sequence contains no elements
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark)
   at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
   at System.Activator.CreateInstance(Type type, Object[] args)
   at Shaolinq.Persistence.DefaultSqlDataTypeProvider.GetEnumDataType(Type type)
   at Shaolinq.Persistence.DefaultSqlDataTypeProvider.GetSqlDataType(Type type)
   at Shaolinq.Persistence.Linq.SqlDataDefinitionExpressionBuilder.BuildColumnDefinition(ColumnInfo columnInfo)
   at Shaolinq.Persistence.Linq.SqlDataDefinitionExpressionBuilder.BuildCreateTableExpression(TypeDescriptor typeDescriptor)
   at Shaolinq.Persistence.Linq.SqlDataDefinitionExpressionBuilder.Build()
   at Shaolinq.Persistence.SqlDatabaseSchemaManager.BuildDataDefinitonExpressions(DatabaseCreationOptions options)
   at Shaolinq.Persistence.SqlDatabaseSchemaManager.CreateDatabaseAndSchema(DatabaseCreationOptions options)
   at Shaolinq.DataAccessModel.Create(DatabaseCreationOptions options)
   at Namespace.Class.MethodName() in path/to/class.cs:line 34
--InvalidOperationException
   at System.Linq.Enumerable.Max(IEnumerable`1 source)
   at Shaolinq.Persistence.DefaultStringEnumSqlDataType`1.GetRecommendedLength(Type enumType)
   at Shaolinq.Persistence.DefaultStringEnumSqlDataType`1.CreateConstraintDefaults(ConstraintDefaultsConfiguration defaultsConfiguration, Type type)
   at Shaolinq.Persistence.DefaultStringEnumSqlDataType`1..ctor(ConstraintDefaultsConfiguration constraintDefaultsConfiguration)

That would be nice to have a more descriptive error message here, or use a fallback value for RecommendedLength.

Scope classes missing

I added Shaolinq postgres to my project. (its .net 4.5.2).,
I have the PostgresConfiguration and DataAccessModel classes but for some reason no DataAccessScope or TransactionScope classes. :/

PostgreSQL and timestamp

Hi,

I'm evaluating Shaolinq hoping to replace our npgsql-only implementation, to get real ORM and support for Linq, and so far I really like it!

I do have an issue when I insert new item with a timestamp (without timezone) column, the time ending up in the db is UTC (currently one hour off for me in Sweden). In the docs for npgsql (http://www.npgsql.org/doc/datetime.html) you can see that they don't touch the DateTime when the column is timestamp, but Shaolinq seems to always change it to UTC (see UniversalTimeNormalisingDateTimeSqlDateType.cs).
Is that really a correct behavior?

Thanks!
/Roger

I can't find any documentation

Despite numerous Google searches, I can't seem to find any documentation.

So I have downloaded and installed Shaolinq, and am ready to get started writing code. But I have no idea what to write in order to create classes that ShaoLinq will understand, how to get ShaoLinq to create a database to match those classes, how to create, update, delete and retrieve objects, or anything.

Surely there must be some instructions somewhere, but I can't find them!

CASCADE action not supported for Postgres

Hello,

It seems that CASCADE foreign key action is not supported at all. Is this by design or is this a bug?

There's a method FixAction(SqlColumnReferenceAction action) in the

D:\Dev\Shaolinq\src\Shaolinq\Persistence\Linq\SqlDataDefinitionExpressionBuilder.cs file that checks if the current Sql dialect supports Restrict when one tries to apply Cascade:

case SqlColumnReferenceAction.Cascade:
            return this.sqlDialect.SupportsCapability(SqlCapability.RestrictAction) ? SqlColumnReferenceAction.Restrict : SqlColumnReferenceAction.NoAction;

Restrict is not enabled for Postgres. Postgres does support cascade actions for foreign key constraints.

Default constraint does not add space between constraint & value

Haven't had any success in figuring this out yet, so thought I'll open an issue. If i manage to figure it out, i'll send a pull for it.

I can see that it's likely happening from Sql92QueryFormatter but even when i give it space in formatter, it still doesn't get picked up. Will try to investigate more tomorrow.

Conditional indices

By default, Shaolinq generates an index with a NOT NULL condition on each of the columns included in the index, e.g.:

CREATE UNIQUE NONCLUSTERED INDEX [Person_IsAdult_Name] ON [dbo].[Person] ([Name], [IsAdult]) WHERE ([Name] IS NOT NULL AND [IsAdult] IS NOT NULL)

But sometimes we might don't want the IS NOT NULL conditions at all, e.g.:

CREATE UNIQUE NONCLUSTERED INDEX [Person_IsAdult_Name] ON [dbo].[Person] ([Name], [IsAdult])

In other cases, we might want to be able to specify some specific NOT NULL conditions, e.g.:

CREATE UNIQUE NONCLUSTERED INDEX [Person_IsAdult_Name] ON [dbo].[Person] ([Name], [IsAdult]) WHERE ([Name] IS NOT NULL)

And in other cases, we need to be able to specify a totally different condition, e.g.:

CREATE UNIQUE NONCLUSTERED INDEX [Person_IsAdult_Name] ON [dbo].[Person] ([Name], [IsAdult]) WHERE ([Name] IS NOT NULL AND [IsAdult] = 1)

Would it be possible to implement this type of flexibility via the [Index] attribute in Shaolinq?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.