Giter Club home page Giter Club logo

massive's Introduction

Massive, a small, happy, dynamic MicroORM for .NET that will love you forever

Massive was started by Rob Conery and has been transfered to Frans Bouma on March 4th, 2015. It's a small MicroORM based on the Expando or dynamic type and allows you to work with your database with almost no effort. The design is based on the idea that the code provided to you in this repository is a start: you get up and running in no-time and from there edit and alter it as you see fit.

Current Status

Massive is currently on v2.0. To obtain the old v1.0 code, please select the v1.0 branch in the GitHub menu or click here.

Installation

To use Massive in your project simply download the following files from the repository's src folder:

  • Massive.Shared.cs
  • Massive.YourDatabase.cs, e.g. Massive.SqlServer.cs for SQL Server
  • Massive.Shared.Async.cs, if you want to use the Massive API asynchronously. Requires .NET 4.5 or higher.

Due to its design, all files share the same namespace. If you need to use more than one database in your project, you have to change the namespace of the files and use per database a version of the Massive.Shared.* files.

Requirements

Massive has no external direct dependencies, just get the code, compile it with your project and enjoy. It does have an indirect dependency, namely on the ADO.NET provider of the database used. The ADO.NET provider is obtained through .NET's DbProviderFactory system. This requires that the ADO.NET provider has been setup properly so .NET can find the factory needed. The following ADO.NET providers are supported out of the box.

Migrating from v1.0 to v2.0

If you're using v1.0 currently and want to migrate to v2.0, please take a look at What's new in v2.0 page for more details about whether you'll run into the changes made. In general the breaking changes will be minor, if any.

What's new in v2.0

Besides some changes as documented in the What's new in v2.0, the following features / additions are new:

  • Async / Await support. Not all supported databases support asynchronous data-access under the hood, but the Massive API at least allows you to work with the code asynchronously. Full Async is supported by the ADO.NET providers of SQL Server and Npgsql (3.x). ODP.NET (Oracle) doesn't support async under the hood so using the Async API with Oracle will still use synchronous data-access under the hood (through the default DbCommand fall back code). SQLite's ADO.NET provider does support async using specific types but Massive doesn't support these.
  • Shared code. In v1.0 code which was effectively the same among all supported databases was copy/pasted, in v2.0 Massive uses partial classes and shares as much code as possible among all supported databases.
  • Unit Tests. In v1.0 there were no tests but in v2.0 we properly implemented a series of tests to see whether things indeed work the way they do. They can also be used as an example how to get started.
  • Culling of dead code.

Contributing

If you want to add new features to Massive or have a fix for a bug, that's great! There are some rules however and if you don't meet them, I won't merge your code, no matter how long you've worked on it. This is nothing personal, and to many of you these rules might sound rigid, but this repository isn't a playground for newbies: the code is used by many people and they depend on it. It therefore has to be of high quality and changes made to the repository will live on forever so they aren't accepted without review.

  • PRs which are solely about whitespace changes are ignored.
  • Before sumitting a PR, first open an issue and discuss your proposed change/feature there. This is mandatory. Any PR without a linked issue is closed without comment. The main reasoning behind this is that it prevents people wasting time on things that will never make it into the code base or that e.g. a PR requires refactoring before it's being accepted because it doesn't fit into the codebase. ORMs, even small ones like Massive aren't simple: there are a lot of pitfalls and in general non-ORM devs overlook a lot of them. Discussing a change before a PR is a good thing in this case. Also don't be afraid to ask if you don't know how to proceed: that's why the issue is opened.
  • If your PR contains submissions from others, I can't accept your PR: a committer owns the code of a change. If you commit code into Massive owned by others, it is unclear those others were willing to share that code with the main repository.
  • Don't change the API nor its current behavior. Massive doesn't have a fixed version number, and is distributed through text files, but I still want the API to be dependable: no method is removed nor has its signature changed. For instance if you want to add functionality to a method and it requires extra arguments for that, you have to add an overload of the method, you can't simply append arguments to a method's signature. Be very careful here. Adding a new overload to a method which has a params argument at the end can easily break existing code (by causing it to unintentionally compile against your new method instead of the params version). Even simply adding optional parameters to the end of an existing method will break the API, since code which is linked against a pre-compiled version of Massive will fail. I cannot accept changes like this.
  • Tests are preferred. If your change can be tested with the current tests, no new tests are needed. If your change requires additional tests because the current tests don't cover it, add them with your PR.
  • If possible support all databases supported by Massive. I've designed Massive in v2.0 to share as much code as possible across all supported databases. New submissions are required to follow that pattern: for instance large pieces of code specific for SQL Server which are also usable with some tweaks on Oracle are not accepted. Instead the code has to be made generic and added to Massive.Shared, using methods implemented in the specific database partial classes to configure the code for that particular database. This can be a great pain, e.g. because you don't have access to Oracle nor Postgresql. In that case, request what you should add for these databases or that I do that for you and test the changes for you locally using the tests you wrote.
  • No new files please. There's currently a Massive.Shared.Async, and the sooner I can merge that into Massive.Shared, the better (MS still supporting .NET 3.5 is currently the limitation on that, but it's likely it will be merged in the near future).
  • Code defensively. If your code accepts input from the user, be absolutely sure this input is passed on as parameters and user crap like null values and name mismatches are covered. You don't need to throw a myriad of exceptions, but at least make a bit of an effort.
  • If it takes less time for me to write the code myself than to look at your PR, tell you how to change things and go into an endless bikeshedding debate with you, chances are I'll write it myself instead of debating things with you.
  • If you add to the API, it's recommended you add a small example to the documentation in this readme below. Some people think tests are documentation, but tests are tests, they test things. Documentation document things, so it's preferable to have documentation as well.
  • For the databases which are currently supported there are free downloads available. You can freely assume code which works on SQL Server Express and Oracle Express / developer edition to work on the paid commercial versions, unless you use a feature only available in those paid versions.

Usage

Note, the following is a work in progress and doesn't contain all the new API methods. It is primarily the original text written by Conery, and I'll update it when I have time. If you're unsure how to use a given method, please look at the tests.

Massive is a "wrapper" for your DB tables and uses System.Dynamic extensively. If you try to use this with C# 3.5 or below, it will explode and you will be sad. Me too honestly - I like how this doesn't require any DLLs other than what's in the GAC. Yippee.

  • Get a Database. Northwind will work nicely. Add a connection to your database in your web.config (or app.config). Don't forget the providerName! If you don't know what that is - just add providerName = 'System.Data.SqlClient' right after the whole connectionString stuff.
  • Create a class that wraps a table. You can call it whatever you like, but if you want to be cool just name it the same as your table.
  • Query away and have fun

Code Please

Let's say we have a table named "Products". You create a class like this:

public class Products:DynamicModel {
	//you don't have to specify the connection - Massive will use the first one it finds in your config
	public Products():base("northwind", "products","productid") {}
}

You could also just instantiate it inline, as needed:

var tbl = new DynamicModel("northwind", tableName:"Products", primaryKeyField:"ProductID");

Or ignore the object hierarchy altogether:

Massive.DB.Current.Query(...);

Now you can query thus:

var table = new Products();
//grab all the products
var products = table.All();
//just grab from category 4. This uses named parameters
var productsFour = table.All(columns: "ProductName as Name", where: "WHERE categoryID=@0",args: 4);

That works, but Massive is "dynamic" - which means that it can figure a lot of things out on the fly. That query above can be rewritten like this:

dynamic table = new Products(); //"dynamic" is important here - don't use "var"!
var productsFour = table.Find(CategoryID:4,columns:"ProductName");

The "Find" method doesn't exist, but since Massive is dynamic it will try to infer what you mean by using DynamicObject's TryInvokeMember. See the source for more details. There's more on the dynamic query stuff down below.

You can also run ad-hoc queries as needed:

var result = tbl.Query("SELECT * FROM Categories");

This will pull categories and enumerate the results - streaming them as opposed to bulk-fetching them (thanks to Jeroen Haegebaert for the code).

If you want to have a paged result set - you can:

var result = tbl.Paged(where: "UnitPrice > 20", currentPage:2, pageSize: 20);

In this example, ALL of the arguments are optional and default to reasonable values. CurrentPage defaults to 1, pageSize defaults to 20, where defaults to nothing.

What you get back is a Dynamic with three properties: Items, TotalPages and TotalRecords. Items is a Query which is lazily evaluated and you can enumerate it after casting it to IEnumerable<dynamic>. TotalPages is the total number of pages in the complete result set and TotalRecords is the total number of records in the result set. What's in the Items collection is totally up to you, it's dynamic: meaning that it's malleable and exciting. It will take the shape of whatever you return in your query, and it will have properties and so on. You can assign events to it, you can create delegates on the fly. You can give it chocolate, and it will kiss you.

That's pretty much it. One thing I really like is the groovy DSL that Massive uses - it looks just like SQL. If you want, you can use this DSL to query the database:

var table = new Products();
var productsThatILike = table.Query("SELECT ProductName, CategoryName FROM Products INNER JOIN Categories ON Categories.CategoryID = Products.CategoryID WHERE CategoryID = @0",5);
//get down!

Some of you might look at that and think it looks suspiciously like inline SQL. It does look sort of like it doesn't it! But I think it reads a bit better than Linq to SQL - it's a bit closer to the mark if you will.

Inserts and Updates

Massive is built on top of dynamics - so if you send an object to a table, it will get parsed into a query. If that object has a property on it that matches the primary key, Massive will think you want to update something. Unless you tell it specifically to update it.

You can send just about anything into the MassiveTransmoQueryfier and it will magically get turned into SQL:

var table = new Products();
var poopy = new {ProductName = "Chicken Fingers"};
//update Product with ProductID = 12 to have a ProductName of "Chicken Fingers"
table.Update(poopy, 12);

This also works if you have a form on your web page with the name "ProductName" - then you submit it:

var table = new Products();
//update Product with ProductID = 12 to have a ProductName of whatever was submitted via the form
table.Update(poopy, Request.Form);

Insert works the same way:

//pretend we have a class like Products but it's called Categories
var table = new Categories();
//do it up - the inserted object will be returned from the query as expando 
var inserted = table.Insert(new {CategoryName = "Buck Fify Stuff", Description = "Things I like"});
// the new PK value is in the field specified as PrimaryKeyField in the constructor of Categories. 
var newID = inserted.CategoryID;

Yippee Skippy! Now we get to the fun part - and one of the reasons I had to spend 150 more lines of code on something you probably won't care about. What happens when we send a whole bunch of goodies to the database at once!

var table = new Products();
//OH NO YOU DIDN't just pass in an integer inline without a parameter! 
//I think I might have... yes
var drinks = table.All("WHERE CategoryID = 8");
//what we get back here is an IEnumerable < ExpandoObject > - we can go to town
foreach(var item in drinks.ToArray()){
	//turn them into Haack Snacks
	item.CategoryID = 12;
}
//Let's update these in bulk, in a transaction shall we?
table.Save(drinks.ToArray());

Named Argument Query Syntax

I recently added the ability to run more friendly queries using Named Arguments and C#4's DynamicObject.TryInvokeMember method-on-the-fly syntax. In an earlier version this was trying to be like Rails ActiveRecord (so, calls were like var drinks = table.FindBy_CategoryID(8);), but I figured "C# is NOT Ruby, and Named Arguments can be a lot more clear". So now calls look like var drinks = table.FindBy(CategoryID:8); (examples below). In addition, Mark Rendle's Simple.Data is already supporting ActiveRecord style syntax, so ... why duplicate things?

If your needs are more complicated - I would suggest just passing in your own SQL with Query().

//important - must be dynamic
dynamic table = new Products();

var drinks = table.FindBy(CategoryID:8);
//what we get back here is an IEnumerable < ExpandoObject > - we can go to town
foreach(var item in drinks){
	Console.WriteLine(item.ProductName);
}
//returns the first item in the DB for category 8
var first = table.First(CategoryID:8);

//you dig it - the last as sorted by PK
var last = table.Last(CategoryID:8);

//you can order by whatever you like
var firstButReallyLast = table.First(CategoryID:8,OrderBy:"PK DESC");

//only want one column?
var price = table.First(CategoryID:8,Columns:"UnitPrice").UnitPrice;

//Multiple Criteria?
var items = table.Find(CategoryID:5, UnitPrice:100, OrderBy:"UnitPrice DESC");

Aggregates with Named Arguments

You can do the same thing as above for aggregates:

var sum = table.Sum(columns:"Price", CategoryID:5);
var avg = table.Avg(columns:"Price", CategoryID:3);
var min = table.Min(columns:"ID");
var max = table.Max(columns:"CreatedOn");
var count = table.Count();

Metadata

If you find that you need to know information about your table - to generate some lovely things like ... whatever - just ask for the Schema property. This will query INFORMATION_SCHEMA for you, and you can take a look at DATA_TYPE, DEFAULT_VALUE, etc for whatever system you're running on.

In addition, if you want to generate an empty instance of a column - you can now ask for a "Prototype()" - which will return all the columns in your table with the defaults set for you (getdate(), raw values, newid(), etc).

Factory Constructor

One thing that can be useful is to use Massive to just run a quick query. You can do that now by using "Open()" which is a static builder on DynamicModel:

var db = Massive.DynamicModel.Open("myConnectionStringName");

You can execute whatever you like at that point.

Validations

One thing that's always needed when working with data is the ability to stop execution if something isn't right. Massive now has Validations, which are built with the Rails approach in mind:

public class Productions:DynamicModel {
	public Productions():base("MyConnectionString","Productions","ID") {}
	public override void Validate(dynamic item) {
		ValidatesPresenceOf("Title");
		ValidatesNumericalityOf(item.Price);
		ValidateIsCurrency(item.Price);
		if (item.Price <= 0)
			Errors.Add("Price can't be negative");
	}
}

The idea here is that Validate() is called prior to Insert/Update. If it fails, an Error collection is populated and an InvalidOperationException is thrown. That simple. With each of the validations above, a message can be passed in.

CallBacks

Need something to happen after Update/Insert/Delete? Need to halt before save? Massive has callbacks to let you do just that:

public class Customers:DynamicModel {
	public Customers():base("MyConnectionString","Customers","ID") {}
	
	//Add the person to Highrise CRM when they're added to the system...
	public override void Inserted(dynamic item) {
		//send them to Highrise
		var svc = new HighRiseApi();
		svc.AddPerson(...);
	}
}

The callbacks you can use are:

  • Inserted
  • Updated
  • Deleted
  • BeforeDelete
  • BeforeSave

massive's People

Contributors

alexarchive avatar alphageek509 avatar antigamez avatar asifshiraz avatar barracuda72 avatar codeimpossible avatar creepygnome avatar datachomp avatar davecowart avatar fatpanther avatar fransbouma avatar gutek avatar hgarcia avatar jarrettmeyer avatar jchannon avatar jecoute avatar jthope avatar kcprogrammer avatar kennybu avatar ketiko avatar kevm avatar majimenezp avatar mikebeaton avatar mountainberg avatar nemesv avatar randallsutton avatar robconery avatar subsonic avatar tomaszkubacki avatar tsquires 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  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

massive's Issues

Crashes if ProviderName is empty

Line 139 - Seems that if providerName is set to empty ("") it crashes...

Perhaps change the line to something like:
providerName = (!String.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[_connectionStringName].ProviderName) ? ConfigurationManager.ConnectionStrings[_connectionStringName].ProviderName : "System.Data.SqlClient");

DateTime

Hi,

With this code:

var table = new tblResource();
dynamic resources = table.All();

and:

@foreach(var r in resources){
   <tr>
        <td>@r.ResourceId</td>
        <td>@r.DateAdded.ToString("dd-MM-yyyy")</td>
        <td>@r.Overview</td>
        <td></td>
        <td></td>
    </tr>
}

I get an error message as follows:

The best overloaded method match for 'System.DBNull.ToString(System.IFormatProvider)' has some invalid arguments.

I am completely new to dynamics, Web Matrix and Massive but should Massive have picked up that this is a DateTime object or is there something further I need to know/do.

Thanks in advance

There is no Fetch

The README.markdown is lying. There is no Fetch method, is there?

Readme example has small error with Save.

Came across this issue of documentation due to a question on stack overflow.

In your readme you have the following code.

var table = new Products();
//OH NO YOU DIDN't just pass in an integer inline without a parameter! 
//I think I might have... yes
var drinks = table.All("WHERE CategoryID = 8");
//what we get back here is an IEnumerable < ExpandoObject > - we can go to town
foreach(var item in drinks){
    //turn them into Haack Snacks
    item.CategoryID = 12;
}
//Let's update these in bulk, in a transaction shall we?
table.Save(drinks);

drinks is an IEnumerable<dynamic> but Save takes a params object[] so the drinks is treated like one entity to save.

ToExpando() method fails when Update() called with Request.Form

When you use: tbl.Update( Request.Form, 25 ) (where 25 is the key), ToExpando() fails on line 75 (of Massive2) saying "TargetParameterCountException was unhandled..."
I'm pretty sure that the problem is line 69 where a test for NameValueCollection is performed, but the incoming form is of type System.Web.HttpValueCollection (despite what the docs say about HttpRequest.Form and its type!).

Here is a clue: http://dylanbeattie.blogspot.com/2008/12/mocking-querystring-collection-in.html

Rename

It was hard enough trying to google for subsonic and find relevant information. Trying to google anything on Massive returns vast amounts of irrelevant clutter. Please change the name to something ungeneric (ma55ive, massiv3)?

NuGet package - License file could use a rename.

The NuGet package puts the following structure into a project.

->Project Root
-----> App_Code
---------> LICENSE.txt
---------> Massive.cs

To avoid any conflicts with other projects and due to the root level of App_Code, it might make sense to rename LICENSE.txt to something like Massive.LICENSE.txt (if the license will allow it).

KeyValues() method not working

DescriptorField does not have a setter and even if you set it when you declare a DynamicModel the local variable is never assigned. We need to add a line in the DynamicModel:
_descriptorField = descriptorField;
After fixing this, when callling KeyValues() the return (IDictionary<string, object>)Query(sql); line will throw an invalid cast exception.

Thank you for your time!

illegal variable name/number on Massive.Oracle.cs

Hi Rob I got an issue with oracle.

//this code threw an exception: "ORA-01036: illegal variable name/number\n"
var matchDisplay = new MatchDisplay();
var query= matchDisplay.All("where Period=:0", args:5);

//but this one works fine
var matchDisplay = new MatchDisplay();
var query= matchDisplay.All("where Period=5");

what's wrong with my params?

Sqlite uses TOP instead of LIMIT in the BuildSelect method

The BuildSelect method in Massive.Sqlite needs changed to use the LIMIT keyword instead of the TOP syntax. Here's the fix:

private static string BuildSelect(string where, string orderBy, int limit) {
string sql = "SELECT {0} FROM {1} ";
if (!string.IsNullOrEmpty(where))
sql += where.Trim().StartsWith("where", StringComparison.CurrentCultureIgnoreCase) ? where : "WHERE " + where;
if (!String.IsNullOrEmpty(orderBy))
sql += orderBy.Trim().StartsWith("order by", StringComparison.CurrentCultureIgnoreCase) ? orderBy : " ORDER BY " + orderBy;
if (limit > 0)
sql += " LIMIT " + limit.ToString();
return sql;
}

DB.Current issue

Massive is returning a convenience class DB.Current that is using an if clause (ConfigurationManager.ConnectionStrings.Count >1) use the first connection from your web.config.

The problem I found is that if you develop using Oracle and you have the client installed, then in the machine.config file you will/may have 2 entries (one named "LocalSqlServer" and one "OraAspNetConString") and in this case DB.Current should use the 3-th connection string and not the 2-nd as it is now in Massive.cs.

My quick fix was to replace this (lines #98 and #99)
if (ConfigurationManager.ConnectionStrings.Count > 1) {
return new DynamicModel(ConfigurationManager.ConnectionStrings[1].Name);
with following code:
var allConnections = ConfigurationManager.ConnectionStrings.Count;
if ( allConnections > 1)
{
for (int i = 1; i < allConnections; i++)
{
if (ConfigurationManager.ConnectionStrings[i].ProviderName == "System.Data.SqlClient")
return new DynamicModel(ConfigurationManager.ConnectionStrings[i].Name);
}
throw new InvalidOperationException("Can't find a valid 'System.Data.SqlClient' connection provider");
}

Thank you for your time!

Should use SCOPE_IDENTITY() rather than @@IDENTITY

You should change the SQL in Insert(object o) from:
cmd.CommandText = "SELECT @@IDENTITY as newID";
to
cmd.CommandText = "SELECT SCOPE_IDENTITY() as newID";

With the current code, you may get the wrong ID if users have triggers (that perform inserts) on their tables. Using SCOPE_IDENTITY() ensures you get the right ID back, no matter what.

SQL 2008 Spatial Data Types

Line 201: ExecuteNonQuery() when the parameter type is a User Defined Type, as the SQL Server 2008 spatial types are, throws the following exception:

UdtTypeName property must be set for UDT parameters

table.Scaler

How do I use table.Scaler? I am using the following code which somehow returns an int value instead of the object.

table.Scalar("select * from articles where articleId=@0", id);

System.Configuration.ConfigurationManager

Just a suggestion more than an issue: You should mention in the Readme that you rely on System.Configuration.dll, and that users will have to add this reference to a brand new Console application if they want to use Massive.

It only took me a few moments to realize, but it may help a newbie.

Refactor the cs file for cleaner and better coding

Hi Rob,

I just want to ask if you are using Resharper? The massive cs file has so many issues found by Resharper such wrong naming convention, redundant arguments, redundant qualifiers, redundant .ToString() conversion, and many more. I do suggest use Resharper for a better, nicer, and cleaner massive cs file.

Better issue report for the Sqlite version of Massive

In the Sqlite version of Massive, the Schema property returns a CREATE TABLE string. It is certainly possible to parse it to get similar data as is returned from the Sql Server version of Massive. However, the following code will return column names and types like the Sql Server version of Massive:

IEnumerable<dynamic> _schema;
public IEnumerable<dynamic> Schema
{
    get
    {
        if (_schema == null) {
            var rows = new List<dynamic>();
            foreach (var row in Query("PRAGMA table_info('" + TableName + "')")) {
                rows.Add(new {
                    COLUMN_NAME = (row as IDictionary<string,object>)["name"].ToString(),
                    DATA_TYPE   = (row as IDictionary<string,object>)["type"].ToString(),
                    IS_NULLABLE = (row as IDictionary<string,object>)["notnull"].ToString() == "0" ? "NO" : "YES",
                });
            }
            _schema = rows;
        }
        return _schema;
    }
}

Sqlite Schema should be...

The current Schema property for the Sqlite code isn't quite right (imo). Here's one that will return column names instead of a table name and a create table blob:

    IEnumerable<dynamic> _schema;
    public IEnumerable<dynamic> Schema
    {
        get
        {
            if (_schema == null) {
                var rows = new List<dynamic>();
                foreach (var row in Query("PRAGMA table_info('" + TableName + "')")) {
                    rows.Add(new {
                        COLUMN_NAME = (row as IDictionary<string,object>)["name"].ToString(),
                        DATA_TYPE   = (row as IDictionary<string,object>)["type"].ToString(),
                        IS_NULLABLE = (row as IDictionary<string,object>)["notnull"].ToString() == "0" ? "NO" : "YES",
                    });
                }
                _schema = rows;
            }
            return _schema;
        }
    }

unhandled error when table class is not named the same as sql table

I know Rob said that cool people name their DynamicModel class the same as their table, but he didn't say that uncool people will get uncool errors.
:(

So let's say my SQL table name is blog_Posts.

Instantiating the class below will cause "SqlException was unhandled by user code" in Massive.cs. I assume because it's attempting to use the class name as the table name somewhere.

public class MyPosts : DynamicModel
{
    public MyPosts()
        : base("blogdb")
    {
        PrimaryKeyField = "PostID";
    }
}

No one else has noticed this because everyone else is cool and names the class the same as the table. For instance, everything works fine if I use the code below:

public class blog_Posts: DynamicModel
{
    public blog_Posts()
        : base("blogdb")
    {
        PrimaryKeyField = "PostID";
    }
}

Not Referring the DynamicModel Class

I am having trouble to refer to the DynamicModel class. Is the table based class some sort of special class that I need to generate or is it generated automatically?

Convert DBNull values to null

When returning data from a Query, is it possible to convert DBNull values to null so can use ?? operator?

    public IEnumerable<dynamic> Query(string sql, params object[] args)
    {
        using (var conn = OpenConnection())
        {
            var rdr = CreateCommand(sql, conn, args).ExecuteReader(CommandBehavior.CloseConnection);
            while (rdr.Read())
            {
                var e = new ExpandoObject();
                var d = e as IDictionary<string, object>;
                for (var i = 0; i < rdr.FieldCount; i++)
                    d.Add(rdr.GetName(i), DBNull.Value.Equals(rdr[i]) ? null : rdr[i]);
                yield return e;
            }
        }
    }

Hardcoding of Provider Name

Was trying to use MySQL with Massive and the following line in DynamicModel gave me a lot of pain ie keyword port not supported.

I would suggest that you change the line

var _providerName = "System.Data.SqlClient";

to

var _providerName = ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName;

BTW Massive rocks!

Massive does not handle columns that are sql keywords

I have an email table which has a few columns such as To and From. These need to be surrounded in square braces when constructing the query.

It's probably not good practice to use these column names in your tables and I know massive is small and provided as-is so I was a little hesitant to write this issue report but I thought it was best to mention it :)

Can massive just wrap all column names in square braces?

Can I make this change? Would be good to contribute to something!

Feature request: updates on criteria other than ID

An overload for Update that takes an object that specifies criteria for what in the table should be updated and an object for what values to set on all matching records. I might add this, but just wanted to register the feature in case anyone has any ideas for better ways to do this. Would be useful to use the same method for Deletes too.

Can't update a column to NULL

In CreateUpdateCommand Properties changed to Null are ignored and will not be persisted.

Suggestion:
Remove "&& item.Value != null" so that AddParam can hanlde changing null to DBNull.Value.

specifying a limit parameter creates incorrect MySQL query, MySQL doesn't support the TOP keyword

Example:
var videos = new Videos(); var headlines = videos.All(where: "headline = @0", orderBy: "create DESC", limit: 5, columns: "videoid", args: 1);

Create a SQL query like "SELECT TOP 5 videoid FROM videos WHERE headline = 1 ORDER BY create DESC;", which MySQL throws the following error:

/* SQL Error (1064): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '5 videoid FROM videos WHERE headline = 1 ORDER BY create DESC' at line 1 */

Workaround using Linq:

var videos = new Videos(); var headlines = videos.All(where: "headline = @0", orderBy: "create DESC", columns: "videoid", args: 1).Take(5);

'tbl' does not contain a definition for 'Query' and no extension method 'Query'

I got this error:

'tbl' does not contain a definition for 'Query' and no extension method 'Query' accepting a first argument of type 'TableName' could be found (are you missing a using directive or an assembly reference?)"

from the latest version of massive.

In your "README.markdown" you still have Query method sample such as var result = tbl.Query("SELECT * FROM Categories"); but Query method is no longer accessible. Intellisense does not contain Query method anymore.

One to Many Relationship in Massive

I have an Articles table and a Comments table and I want to get a one to many relationship between them. I also want that when I pull a single article all the comments are automatically retrieved.

Documentation Bulk Update/Insert does not work

I set up the code according to the front page docs, and set up a sql server express table Products with appropriate columns to test the code

var table = new Products();
//OH NO YOU DIDN't just pass in an integer inline without a parameter!
//I think I might have... yes
var drinks = table.All("WHERE CategoryID = 8");
//what we get back here is an IEnumerable < ExpandoObject > - we can go to town
foreach(var item in drinks){
//turn them into Haack Snacks
item.CategoryID = 12;
}
//Let's update these in bulk, in a transaction shall we?
table.Save(drinks);

This throws an error @ line 400 within Massive.cs v 100644 from Aug 31.

System.InvalidOperationException: Can't parse this object to the database - there are no properties set

the dynamic objects "item" never actually remember their new values.

lots of OpenConnection() not optimal for use with Sql Ce

OpenConnection() is called quite a lot from Massive, that's not very good if you're using it with a sql ce db since each openconnection is quite costly. ~100 ms on my pc. I don't know how to deal with this the best way really. For now I changed it to keep the connection open.

MySQL Connection: Howto?

Hello everyone I'm very new to ORM and C#. I would like to know how can I use massive in MySQL?

Small issue with Paged

Line 303+, missing a space
var countSQL = string.Format("SELECT COUNT({0}) FROM {1}", PrimaryKeyField, TableName);
...
countSQL += where;

results in "...FROM MyTableWHERE ..."

Feature request: pass connection string to constructor

In our system, we store a template of the connection string in the config file, not the actual string. I'd like to be able to use Massive in this scenario, which would require the ability to pass in a fully formatted connection string. Would anyone else like this behavior?

Question: How to load results into Model object?

Not an issue, just a question but couldn't find a better place to post...

Once I get my results as expando objects, is there a way to convert each expando into the appropriate model object? I think I must be missing something...

For example, if I have 'Products'

In the Controller:

Product product_search = new Product();
var results = product_search.All(...);
return View("Browse","_Layout",results);

Rather than IEnumerable I want IEnumerable so when I iterate through the results in my Browse view I have access to all of my methods in Product...

I've tried casting in a few different ways with no luck. Am I approaching this wrong?

Idea to make Massive even less...er...Massive

Hi Rob,

I've been playing with including some sort of dynamic Database/Repository class such as below:

This eliminates having to create new Model classes that derive from DynamicModel for each table in your database.
Instead, you simply call:

Database.Default.Products.Find(Id:1).

Using Database.Default makes some assumptions:

  1. Uses first connection string in web.config by default
  2. Assumes table is named the same as the property you access
  3. Assumes the primary key field is named id

Great convention over configuration approach.

You could also call:

Database.Open("MyConnectionString").Products.Find(Id:1)

OR

Database.Default.Products("tblProducts","ProductID").Find(ProductID:1)

I don't really like this last one above because it could get messy dragging that around everywhere, but maybe a simple fluent interface for one-time registration/mapping would work. Maybe something like:

Database.Default.Products.Map("tblProducts", "ProductID")

From then on any call to Database.Default.Products would create a new DynamicModel using "tblProducts" as the table name and "ProductID" as the primary key field.

Maybe even something similar to register your default connection string too. Something like:

Database.SetDefault("ConnectionStringName")

Curious to get your thoughts?

Here's the class I'm using and it works quite nicely. For DI/IoC, simply add an interface that defines the Default property and Open method.

public class Database : DynamicObject
{
    private string _connectionStringName = ConfigurationManager.ConnectionStrings[0].Name;

    private Database()
    {
    }

    public static dynamic Default
    {
        get { return new Database(); }
    }

    public static dynamic Open(string connectionStringName)
    {
        _connectionStringName = connectionStringName;
        return new Database();
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        var model = new DynamicModel(_connectionStringName, binder.Name, "Id");
        result = model;
        return true;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        var tableName = string.Empty;
        var primaryKeyField = string.Empty;

        var argCount = args.Length;

        if (binder.CallInfo.ArgumentNames.Count > 0)
        {
            var arguments = binder.CallInfo.ArgumentNames.Select((item, index) => new { Name = item, Index = index });
            tableName = args[arguments.Where(n => n.Name == "tableName").FirstOrDefault().Index].ToString();
            primaryKeyField = args[arguments.Where(n => n.Name == "primaryKeyField").FirstOrDefault().Index].ToString();
        }
        else
        {
            if (argCount > 0)
            {
                tableName = args[0].ToString();
            }
            if (argCount > 1)
            {
                primaryKeyField = args[1].ToString();
            }
        }
        var model = new DynamicModel(_connectionStringName, tableName, primaryKeyField);
        result = model;
        return true;
    }
}

QueryAsync broken / does not close connections properly

Just a quick first feedback:

    /// <summary>
    /// Executes the reader using SQL async API - thanks to Damian Edwards
    /// </summary>
    public void QueryAsync(string sql, Action<List<dynamic>> callback, params object[] args) {
        //using (var conn = new SqlConnection(_connectionString)) { // remove using statment?
            var cmd = new SqlCommand(sql, new SqlConnection(_connectionString)); //new connection is created
            cmd.AddParams(args);
            cmd.Connection.Open();
            var task = Task.Factory.FromAsync<IDataReader>(cmd.BeginExecuteReader, cmd.EndExecuteReader, null);
            //task.ContinueWith(x => callback.Invoke(x.Result.ToExpandoList()));
            task.ContinueWith(x => {
                callback.Invoke(x.Result.ToExpandoList());
                cmd.Connection.Close(); //does this also get called when an error occured? will look into it.
                                  }
               );

        //}
    }

Creating an overload with SynchronisationContext works great with WPF, btw.

Problems get result count and args with date time

dynamic _users = new User();
var count0 = _users.Count(); // it works

var result = _users.AllI(where: "WHERE CheckDate>@0", args: DateTime.Today.ToString()); 
var count1 = result.Count(); // not work 

and I found that args cannot receive a datetime type argument, a InvalidOperationException occurred, which says "an expression of type 'System.DateTime' cannot be used to initialize an array of type 'System.Object'".

Massive is hard coded to use System.Data.SqlClient

Line 102 of the current commit is hard coded to System.Data.SqlClient. It should be reverted to the fix for Issue #4

var _providerName = "System.Data.SqlClient";

#4

var _providerName = ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName; if(String.IsNullOrEmpty(_providerName)) _providerName = "System.Data.SqlClient"; _factory = DbProviderFactories.GetFactory(_providerName);

problem in paged function

"Where" is Paged function should have a space before it.

public virtual dynamic Paged(string where = "", string orderBy = "", string columns = "*", int pageSize = 20, int currentPage = 1, params object[] args) {

   -----
   -----
        if (!string.IsNullOrEmpty(where)) {
            if (!where.Trim().StartsWith("where", StringComparison.OrdinalIgnoreCase)) {
                //original
                //where = "WHERE " + where;
                //should be
                where = " WHERE " + where;
            }
        }

    }

logic for generated SQL on a Paged result with a WHERE clause seems broken

Created a console app, table has two columns - ID, LocationText. This works fine:

var productsFour = table.Paged(columns:"ID, LocationText");
foreach (var o in productsFour.Items)
{
Console.WriteLine(o.LocationText);
}

        Console.ReadKey();

However, if I add a WHERE clause the query generated is incorrect.
e.g var productsFour = table.Paged(columns:"ID, LocationText", where:"WHERE Id > 1000);
produces this:
"SELECT ID, LocationText FROM (SELECT ROW_NUMBER() OVER (ORDER BY ID) AS Row, ID, LocationText FROM Seq) AS Paged WHERE Row >=0 AND Row <=20 WHERE ID > 10000"

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.