├── ShimmyMySherbet.MySQLEntityFramework ├── Models │ ├── ESerializeFormat.cs │ ├── EInsertMode.cs │ ├── SQLOmitInsert.cs │ ├── SQLOmitOnNull.cs │ ├── Exceptions │ │ ├── NoConnectionException.cs │ │ ├── SQLInvalidLengthException.cs │ │ ├── NoSQLFieldsException.cs │ │ ├── NoPrimaryKeyException.cs │ │ ├── SQLInvalidCastException.cs │ │ └── SQLIncompatableTypeException.cs │ ├── TypeModel │ │ ├── SQLNoSign.cs │ │ ├── Custom │ │ │ ├── SQLIPv4.cs │ │ │ └── SQLIPv6.cs │ │ ├── SQLNetType.cs │ │ ├── SQLLength.cs │ │ ├── SQLSigned.cs │ │ ├── SQLTypeName.cs │ │ ├── SQLVarChar.cs │ │ ├── Types │ │ │ └── SQLTypes.cs │ │ └── SQLTypeHelper.cs │ ├── SQLOmitUpdate.cs │ ├── SQLIngoreProperties.cs │ ├── SQLNull.cs │ ├── SQLIgnore.cs │ ├── SQLIndex.cs │ ├── SQLUnique.cs │ ├── SQLAutoIncrement.cs │ ├── SQLPrimaryKey.cs │ ├── IBulkInserter.cs │ ├── Internals │ │ ├── SQLFieldStoreMatch.cs │ │ ├── SQLFieldReferance.cs │ │ ├── LocalFieldReferance.cs │ │ ├── SQLBuildField.cs │ │ ├── SQLMetaField.cs │ │ └── SQLType.cs │ ├── SQLOmit.cs │ ├── SQLSerialize.cs │ ├── Interfaces │ │ ├── IDatabaseTableInitializer.cs │ │ ├── IConnectionProvider.cs │ │ └── IDatabaseTable.cs │ ├── SQLDatabaseEngine.cs │ ├── SQLDefault.cs │ ├── SQLPropertyName.cs │ ├── SQLForeignKey.cs │ ├── SQLCharSetAttribute.cs │ ├── SQLCharSet.cs │ ├── DatabaseSettings.cs │ ├── ConnectionProviders │ │ ├── TransientConnectionProvider.cs │ │ ├── SingleConnectionProvider.cs │ │ ├── SingletonConnectionProvider.cs │ │ └── ThreadedConnectionProvider.cs │ ├── ConnectionStringParser.cs │ ├── TransactionalBulkInserter.cs │ ├── Extensions.cs │ ├── BulkExecutor.cs │ └── BulkInserter.cs ├── Internals │ ├── Delegates.cs │ ├── ParamObject.cs │ ├── BuildProperty.cs │ ├── Extensions.cs │ ├── PrefixAssigner.cs │ ├── IClassField.cs │ ├── SQlMetaBuilder.cs │ ├── ClassField.cs │ ├── ClassProperty.cs │ ├── PropertyList.cs │ ├── SerializationProvider.cs │ ├── ClassFieldBase.cs │ ├── InternalExtensions.cs │ ├── MySQLEntityReader.cs │ ├── EntityCommandBuilder.cs │ └── SQLConverter.cs ├── ShimmyMySherbet.MySQL.EF.csproj └── Core │ ├── DatabaseClient.cs │ └── DatabaseTable.cs ├── ExampleApp ├── Database │ ├── Tables │ │ ├── UsersTable.cs │ │ ├── PermissionsTable.cs │ │ ├── UserCommentsTable.cs │ │ └── BalanceTable.cs │ ├── Models │ │ ├── UserBalance.cs │ │ ├── Permission.cs │ │ ├── UserPermissions.cs │ │ ├── UserComment.cs │ │ └── UserAccount.cs │ └── DatabaseManager.cs ├── App.config ├── packages.config ├── Properties │ └── AssemblyInfo.cs ├── ExampleApp.csproj └── Program.cs ├── MySQLEntityFramework.sln ├── .gitattributes ├── README.md └── .gitignore /ShimmyMySherbet.MySQLEntityFramework/Models/ESerializeFormat.cs: -------------------------------------------------------------------------------- 1 | namespace ShimmyMySherbet.MySQL.EF.Models 2 | { 3 | public enum ESerializeFormat 4 | { 5 | JSON, 6 | XML 7 | } 8 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/EInsertMode.cs: -------------------------------------------------------------------------------- 1 | namespace ShimmyMySherbet.MySQL.EF.Models 2 | { 3 | public enum EInsertMode 4 | { 5 | INSERT, 6 | INSERT_IGNORE 7 | } 8 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLOmitInsert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ShimmyMySherbet.MySQL.EF.Models 4 | { 5 | public sealed class SQLOmitInsert : Attribute 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/Delegates.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace ShimmyMySherbet.MySQL.EF.Internals 4 | { 5 | public delegate object TypeReader(IDataReader reader, int index); 6 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLOmitOnNull.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ShimmyMySherbet.MySQL.EF.Models 4 | { 5 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 6 | public class SQLOmitOnNull : Attribute 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Exceptions/NoConnectionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ShimmyMySherbet.MySQL.EF.Models.Exceptions 4 | { 5 | public sealed class NoConnectionException : Exception 6 | { 7 | public NoConnectionException(string msg) : base(msg) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /ExampleApp/Database/Tables/UsersTable.cs: -------------------------------------------------------------------------------- 1 | using ExampleApp.Database.Models; 2 | using ShimmyMySherbet.MySQL.EF.Core; 3 | 4 | namespace ExampleApp.Database.Tables 5 | { 6 | public class UsersTable : DatabaseTable 7 | { 8 | public UsersTable(string tableName) : base(tableName) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Exceptions/SQLInvalidLengthException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ShimmyMySherbet.MySQL.EF.Models.Exceptions 4 | { 5 | public class SQLInvalidLengthException : Exception 6 | { 7 | public SQLInvalidLengthException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /ExampleApp/Database/Models/UserBalance.cs: -------------------------------------------------------------------------------- 1 | using ShimmyMySherbet.MySQL.EF.Models; 2 | 3 | namespace ExampleApp.Database.Models 4 | { 5 | [SQLCharSet(SQLCharSet.utf8mb4)] 6 | public class UserBalance 7 | { 8 | [SQLPrimaryKey] 9 | public ulong UserID { get; set; } 10 | 11 | public double Balance { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/TypeModel/SQLNoSign.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models.TypeModel 8 | { 9 | public sealed class SQLNoSign : Attribute 10 | { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ExampleApp/Database/Tables/PermissionsTable.cs: -------------------------------------------------------------------------------- 1 | using ExampleApp.Database.Models; 2 | using ShimmyMySherbet.MySQL.EF.Core; 3 | 4 | namespace ExampleApp.Database.Tables 5 | { 6 | public class PermissionsTable : DatabaseTable 7 | { 8 | public PermissionsTable(string tableName) : base(tableName) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /ExampleApp/Database/Tables/UserCommentsTable.cs: -------------------------------------------------------------------------------- 1 | using ExampleApp.Database.Models; 2 | using ShimmyMySherbet.MySQL.EF.Core; 3 | 4 | namespace ExampleApp.Database.Tables 5 | { 6 | public class UserCommentsTable : DatabaseTable 7 | { 8 | public UserCommentsTable(string tableName) : base(tableName) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLOmitUpdate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ShimmyMySherbet.MySQL.EF.Models 4 | { 5 | /// 6 | /// Omits the field when updating 7 | /// 8 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 9 | 10 | public sealed class SQLOmitUpdate : Attribute 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/ParamObject.cs: -------------------------------------------------------------------------------- 1 | namespace ShimmyMySherbet.MySQL.EF.Internals 2 | { 3 | public struct ParamObject 4 | { 5 | public string Key; 6 | public object Value; 7 | 8 | public ParamObject(string key, object value) 9 | { 10 | Key = key; 11 | Value = value; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLIngoreProperties.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | [AttributeUsage(AttributeTargets.Class)] 10 | public sealed class SQLIngoreProperties : Attribute 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLNull.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 10 | public sealed class SQLNull : Attribute 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLIgnore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 10 | public sealed class SQLIgnore : Attribute 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 10 | public sealed class SQLIndex : Attribute 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLUnique.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 10 | public sealed class SQLUnique : Attribute 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/TypeModel/Custom/SQLIPv4.cs: -------------------------------------------------------------------------------- 1 | using ShimmyMySherbet.MySQL.EF.Models.Internals; 2 | 3 | namespace ShimmyMySherbet.MySQL.EF.Models.TypeModel.Custom 4 | { 5 | public class SQLIPv4 : SQLType 6 | { 7 | public override int Length => 15; 8 | public override bool NoSign => true; 9 | public override string TypeName => "VARCHAR"; 10 | } 11 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/TypeModel/Custom/SQLIPv6.cs: -------------------------------------------------------------------------------- 1 | using ShimmyMySherbet.MySQL.EF.Models.Internals; 2 | 3 | namespace ShimmyMySherbet.MySQL.EF.Models.TypeModel.Custom 4 | { 5 | public class SQLIPv6 : SQLType 6 | { 7 | public override int Length => 39; 8 | public override string TypeName => "VARCHAR"; 9 | public override bool NoSign => true; 10 | } 11 | } -------------------------------------------------------------------------------- /ExampleApp/Database/Models/Permission.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ShimmyMySherbet.MySQL.EF.Models; 3 | 4 | namespace ExampleApp.Database.Models 5 | { 6 | [SQLCharSet(SQLCharSet.utf8mb4)] 7 | public class Permission 8 | { 9 | public string PermissionID { get; set; } 10 | public ulong GrantedBy { get; set; } 11 | public DateTime GrantedAt { get; set; } = DateTime.Now; 12 | } 13 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLAutoIncrement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 10 | public sealed class SQLAutoIncrement: Attribute 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLPrimaryKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 10 | 11 | public sealed class SQLPrimaryKey : Attribute 12 | { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/IBulkInserter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | public interface IBulkInserter 10 | { 11 | 12 | void Insert(T instance); 13 | 14 | 15 | int Commit(); 16 | 17 | Task CommitAsync(); 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Exceptions/NoSQLFieldsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models.Exceptions 8 | { 9 | public class NoSQLFieldsException : Exception 10 | { 11 | public override string Message => "The type does not contain any SQL accessible fields."; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Internals/SQLFieldStoreMatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models.Internals 8 | { 9 | public class SQLFieldStoreMatch 10 | { 11 | public LocalFieldReferance LocalFieldReferance; 12 | public SQLFieldReferance SQLFieldReferance; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Exceptions/NoPrimaryKeyException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models.Exceptions 8 | { 9 | public sealed class NoPrimaryKeyException : Exception 10 | { 11 | public override string Message => "The object does not have an associated Primary Key."; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/TypeModel/SQLNetType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models.TypeModel 8 | { 9 | public sealed class SQLNetType : Attribute 10 | { 11 | public Type Type; 12 | public SQLNetType(Type T) 13 | { 14 | Type = T; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/TypeModel/SQLLength.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models.TypeModel 8 | { 9 | public sealed class SQLLength : Attribute 10 | { 11 | public int Length; 12 | public SQLLength(int Length) 13 | { 14 | this.Length = Length; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/TypeModel/SQLSigned.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models.TypeModel 8 | { 9 | public sealed class SQLSigned : Attribute 10 | { 11 | public bool Signed; 12 | public SQLSigned(bool Signed) 13 | { 14 | this.Signed = Signed; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/TypeModel/SQLTypeName.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models.TypeModel 8 | { 9 | public sealed class SQLTypeName : Attribute 10 | { 11 | public string Name; 12 | public SQLTypeName(string TypeName) 13 | { 14 | Name = TypeName; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ExampleApp/Database/Tables/BalanceTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using ExampleApp.Database.Models; 7 | using ShimmyMySherbet.MySQL.EF.Core; 8 | 9 | namespace ExampleApp.Database.Tables 10 | { 11 | public class BalanceTable : DatabaseTable 12 | { 13 | public BalanceTable(string tableName) : base(tableName) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLOmit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | /// 10 | /// Omits the field on insert and update 11 | /// 12 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 13 | public sealed class SQLOmit : Attribute 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLSerialize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ShimmyMySherbet.MySQL.EF.Models 4 | { 5 | public sealed class SQLSerialize : Attribute 6 | { 7 | public ESerializeFormat Format { get; } 8 | 9 | public SQLSerialize() 10 | { 11 | Format = ESerializeFormat.JSON; 12 | } 13 | 14 | public SQLSerialize(ESerializeFormat fomat) 15 | { 16 | Format = fomat; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/BuildProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Internals 8 | { 9 | public struct BuildProperty 10 | { 11 | public string Key; 12 | public object Value; 13 | 14 | public BuildProperty(string key, object value) 15 | { 16 | Key = key; 17 | Value = value; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Interfaces/IDatabaseTableInitializer.cs: -------------------------------------------------------------------------------- 1 | using ShimmyMySherbet.MySQL.EF.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace ShimmyMySherbet.MySQL.EF.Models.Interfaces 9 | { 10 | public interface IDatabaseTableInitializer 11 | { 12 | string TableName { get; } 13 | 14 | void CheckSchema(); 15 | 16 | void SendClient(MySQLEntityClient client); 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Internals/SQLFieldReferance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ShimmyMySherbet.MySQL.EF.Internals; 3 | 4 | namespace ShimmyMySherbet.MySQL.EF.Models.Internals 5 | { 6 | public class SQLFieldReferance 7 | { 8 | public int Index; 9 | public string Name; 10 | public Type Type; 11 | 12 | public SQLFieldReferance(int Index, string Name, Type T) 13 | { 14 | this.Index = Index; 15 | this.Name = Name; 16 | Type = T; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLDatabaseEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | [AttributeUsage(AttributeTargets.Class)] 10 | public sealed class SQLDatabaseEngine : Attribute 11 | { 12 | public readonly string DatabaseEngine; 13 | public SQLDatabaseEngine(string Engine) 14 | { 15 | DatabaseEngine = Engine; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLDefault.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 10 | public sealed class SQLDefault : Attribute 11 | { 12 | public readonly object DefaultValue; 13 | public SQLDefault(object Default) 14 | { 15 | DefaultValue = Default; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLPropertyName.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 10 | public sealed class SQLPropertyName : Attribute 11 | { 12 | public readonly string Name; 13 | public SQLPropertyName(string Name) 14 | { 15 | this.Name = Name; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ExampleApp/Database/Models/UserPermissions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ShimmyMySherbet.MySQL.EF.Models; 3 | 4 | namespace ExampleApp.Database.Models 5 | { 6 | [SQLCharSet(SQLCharSet.utf8mb4)] 7 | public class UserPermissions 8 | { 9 | [SQLPrimaryKey] 10 | public ulong UserID { get; set; } 11 | 12 | public bool IsAdmin { get; set; } 13 | 14 | public bool IsRoot { get; set; } 15 | 16 | [SQLSerialize(ESerializeFormat.JSON)] // Object serializing 17 | public List Permissions { get; set; } = new List(); 18 | } 19 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Internals/LocalFieldReferance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models.Internals 8 | { 9 | public class LocalFieldReferance 10 | { 11 | public string FieldName; 12 | public string SQLName; 13 | public Type Type; 14 | 15 | public LocalFieldReferance(string FName, string SName, Type T) 16 | { 17 | FieldName = FName; 18 | SQLName = SName; 19 | Type = T; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Internals 8 | { 9 | public static class Extensions 10 | { 11 | public static IEnumerable CastEnumeration(this IEnumerable Enumerable, Func Converter) 12 | { 13 | var Res = new List(); 14 | foreach (var Item in Enumerable) 15 | Res.Add(Converter(Item)); 16 | return Res; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/PrefixAssigner.cs: -------------------------------------------------------------------------------- 1 | namespace ShimmyMySherbet.MySQL.EF.Internals 2 | { 3 | public class PrefixAssigner 4 | { 5 | private int m_PrefixLevel = -1; 6 | private object lockObj = new object(); 7 | 8 | public int AssignPrefix() 9 | { 10 | lock (lockObj) 11 | { 12 | m_PrefixLevel++; 13 | return m_PrefixLevel; 14 | } 15 | } 16 | 17 | public void Reset() 18 | { 19 | lock(lockObj) 20 | { 21 | m_PrefixLevel = -1; 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Exceptions/SQLInvalidCastException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ShimmyMySherbet.MySQL.EF.Models.Exceptions 4 | { 5 | public class SQLInvalidCastException : Exception 6 | { 7 | public SQLInvalidCastException(string fieldname, string from, string to) : base($"Cannot convert SQL Type {from} to .NET type {to} in column '{fieldname}'") 8 | { 9 | } 10 | } 11 | 12 | public class SQLConversionFailedException : Exception 13 | { 14 | public SQLConversionFailedException(int column, Type type) : base($"Failed to read type '{type.Name}' from data reader on column {column}") 15 | { 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/TypeModel/SQLVarChar.cs: -------------------------------------------------------------------------------- 1 | using ShimmyMySherbet.MySQL.EF.Models.Exceptions; 2 | using ShimmyMySherbet.MySQL.EF.Models.Internals; 3 | using ShimmyMySherbet.MySQL.EF.Models.TypeModel; 4 | 5 | namespace ShimmyMySherbet.MySQL.EF.Models.TypeModel 6 | { 7 | [SQLTypeName("VarChar"), SQLNoSign] 8 | public class SQLVarChar : SQLType 9 | { 10 | private int m_Length; 11 | public override int Length => m_Length; 12 | 13 | public SQLVarChar(int Length) 14 | { 15 | if (Length <= 0) 16 | { 17 | throw new SQLInvalidLengthException("VarChar length cannot be less than 1"); 18 | } 19 | m_Length = Length; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLForeignKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ShimmyMySherbet.MySQL.EF.Models 4 | { 5 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 6 | public class SQLForeignKey : Attribute 7 | { 8 | private Func m_Ref; 9 | private string m_Field; 10 | 11 | public string Table => m_Ref(); 12 | private string Field => m_Field; 13 | 14 | public SQLForeignKey(string table, string field) 15 | { 16 | m_Ref = () => table; 17 | m_Field = field; 18 | } 19 | 20 | public SQLForeignKey(Func table, string field) 21 | { 22 | m_Ref = table; 23 | m_Field = field; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /ExampleApp/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ExampleApp/Database/Models/UserComment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ShimmyMySherbet.MySQL.EF.Models; 3 | 4 | namespace ExampleApp.Database.Models 5 | { 6 | [SQLCharSet(SQLCharSet.ServerDefault)] // Specifying default charset 7 | public class UserComment 8 | { // Composite primary key 9 | [SQLPrimaryKey] 10 | public ulong UserID { get; set; } 11 | 12 | [SQLPrimaryKey] 13 | public ulong PostID { get; set; } 14 | 15 | public string Content { get; set; } 16 | 17 | [SQLOmitInsert, SQLNull] // Only provide this value when updating, leave default on insert 18 | public DateTime? Updated { get; set; } // Nullable / optional value 19 | 20 | [SQLOmitUpdate] // Only provide this value when inserting, leave default on insert 21 | public DateTime Posted { get; set; } = DateTime.Now; 22 | } 23 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Interfaces/IConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | using MySql.Data.MySqlClient; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace ShimmyMySherbet.MySQL.EF.Models.Interfaces 6 | { 7 | public interface IConnectionProvider : IDisposable 8 | { 9 | bool Connected { get; } 10 | 11 | string ConnectionString { get; } 12 | 13 | MySqlConnection GetConnection(bool autoOpen = true, bool forceNew = false); 14 | 15 | Task GetConnectionAsync(bool autoOpen = true, bool forceNew = false); 16 | 17 | void ReleaseConnection(MySqlConnection connection); 18 | 19 | Task ReleaseConnectionAsync(MySqlConnection connection); 20 | 21 | void Open(); 22 | 23 | Task OpenAsync(); 24 | 25 | void Disconnect(); 26 | 27 | Task DisconnectAsync(); 28 | } 29 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLCharSetAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ShimmyMySherbet.MySQL.EF.Models 4 | { 5 | public class SQLCharSetAttribute : Attribute 6 | { 7 | public SQLCharSet CharSet { get; } 8 | public string CharSetName { get; } 9 | 10 | public SQLCharSetAttribute(SQLCharSet charset = SQLCharSet.ServerDefault) 11 | { 12 | CharSet = charset; 13 | if (charset == SQLCharSet.ServerDefault) 14 | { 15 | CharSetName = string.Empty; 16 | } 17 | else 18 | { 19 | CharSetName = charset.ToString(); 20 | } 21 | } 22 | 23 | public SQLCharSetAttribute(string charset) 24 | { 25 | CharSet = SQLCharSet.Other; 26 | CharSetName = charset; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Interfaces/IDatabaseTable.cs: -------------------------------------------------------------------------------- 1 | using ShimmyMySherbet.MySQL.EF.Core; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace ShimmyMySherbet.MySQL.EF.Models.Interfaces 6 | { 7 | public interface IDatabaseTable : IDatabaseTableInitializer 8 | { 9 | T QuerySingle(string command, params object[] args); 10 | 11 | List Query(string command, params object[] args); 12 | 13 | Task QuerySingleAsync(string command, params object[] args); 14 | 15 | Task> QueryAsync(string command, params object[] args); 16 | 17 | void Insert(T obj); 18 | 19 | void Delete(T obj); 20 | 21 | void InsertUpdate(T obj); 22 | 23 | Task InsertAsync(T obj); 24 | 25 | Task DeleteAsync(T obj); 26 | 27 | Task InsertUpdateAsync(T obj); 28 | } 29 | } -------------------------------------------------------------------------------- /ExampleApp/Database/Models/UserAccount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using ShimmyMySherbet.MySQL.EF; 7 | using ShimmyMySherbet.MySQL.EF.Models; 8 | using ShimmyMySherbet.MySQL.EF.Models.TypeModel; 9 | namespace ExampleApp.Database.Models 10 | { 11 | [SQLCharSet(SQLCharSet.utf8mb4)] 12 | public class UserAccount 13 | { 14 | [SQLPrimaryKey, SQLAutoIncrement] 15 | public ulong ID { get; set; } 16 | 17 | [SQLUnique, SQLIndex, SQLVarChar(28)] 18 | public string Username { get; set; } 19 | 20 | public string EmailAddress { get; set; } 21 | 22 | public DateTime LastLogin { get; set; } = DateTime.Now; 23 | 24 | [SQLOmitUpdate] // Do not update this value, only provide on insert 25 | public DateTime AccountCreated { get; set; } = DateTime.Now; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Internals/SQLBuildField.cs: -------------------------------------------------------------------------------- 1 | namespace ShimmyMySherbet.MySQL.EF.Models.Internals 2 | { 3 | public class SQLBuildField 4 | { 5 | public SQLType Type; 6 | public SQLType OverrideType = null; 7 | 8 | public bool SetLength = false; 9 | 10 | public string SQLRepresentation 11 | { 12 | get 13 | { 14 | if (OverrideType != null) 15 | { 16 | return OverrideType.SQLRepresentation; 17 | } 18 | return Type.SQLRepresentation; 19 | } 20 | } 21 | 22 | public string Name; 23 | public bool PrimaryKey = false; 24 | public bool Unique = false; 25 | public bool Indexed = false; 26 | public bool AutoIncrement = false; 27 | public object Default = null; 28 | public bool Null = false; 29 | } 30 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/IClassField.cs: -------------------------------------------------------------------------------- 1 | using ShimmyMySherbet.MySQL.EF.Models; 2 | using ShimmyMySherbet.MySQL.EF.Models.Internals; 3 | using System; 4 | 5 | namespace ShimmyMySherbet.MySQL.EF.Internals 6 | { 7 | public interface IClassField 8 | { 9 | string Name { get; } 10 | string SQLName { get; } 11 | Type FieldType { get; } 12 | 13 | Type ReadType { get; } 14 | 15 | SQLType OverrideType { get; } 16 | int FieldIndex { get; } 17 | SQLMetaField Meta { get; } 18 | 19 | Attribute[] GetCustomAttributes(); 20 | 21 | bool AttributeDefined() where T : Attribute; 22 | 23 | T GetAttribute() where T : Attribute; 24 | 25 | void SetValue(object instance, object obj); 26 | 27 | object GetValue(object instance); 28 | 29 | bool ShouldOmit(object instance); 30 | 31 | TypeReader Reader { get; } 32 | } 33 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/SQLCharSet.cs: -------------------------------------------------------------------------------- 1 | namespace ShimmyMySherbet.MySQL.EF.Models 2 | { 3 | public enum SQLCharSet 4 | { 5 | /// 6 | /// Uses the default charset of the MySQL server 7 | /// 8 | ServerDefault, 9 | 10 | /// 11 | /// Unicode compliant, cannot store emojis. 12 | /// 13 | utf8, 14 | 15 | utf16, 16 | utf16le, 17 | utf32, 18 | 19 | /// 20 | /// Unicode compliant, can also store emojis. 21 | /// 22 | utf8mb4, 23 | 24 | binary, 25 | latin1, 26 | latin2, 27 | latin5, 28 | latin7, 29 | ascii, 30 | hebrew, 31 | greek, 32 | 33 | /// 34 | /// Reserved for specifying charsets by name that are not present in this enum 35 | /// 36 | Other 37 | } 38 | } -------------------------------------------------------------------------------- /ExampleApp/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ExampleApp/Database/DatabaseManager.cs: -------------------------------------------------------------------------------- 1 | using ExampleApp.Database.Tables; 2 | using ShimmyMySherbet.MySQL.EF.Core; 3 | using ShimmyMySherbet.MySQL.EF.Models.Interfaces; 4 | 5 | namespace ExampleApp.Database 6 | { 7 | public class DatabaseManager : DatabaseClient 8 | { 9 | // The DatabaseClient will automatically find all Table fields in the class, and init them 10 | // This can be disabled by setting autoInit to false in DatabaseClient's constructor 11 | 12 | // Calling CheckSchema will call CheckSchema for all tables, automatically generating missing tables 13 | public UsersTable Users { get; } = new UsersTable("Test_Users"); 14 | 15 | public PermissionsTable Permissions { get; } = new PermissionsTable("Test_Perms"); 16 | 17 | public UserCommentsTable Comments { get; } = new UserCommentsTable("Test_Comments"); 18 | 19 | public BalanceTable Balances { get; } = new BalanceTable("Test_Balances"); 20 | 21 | public DatabaseManager(IConnectionProvider connectionProvider) : base(connectionProvider) 22 | { 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Exceptions/SQLIncompatableTypeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models.Exceptions 8 | { 9 | public class SQLIncompatableTypeException : Exception 10 | { 11 | private string Message_; 12 | public SQLIncompatableTypeException(string FieldName = null) 13 | { 14 | if (FieldName == null) 15 | { 16 | Message_ = "An incompatible type was supplied. Change the field type or manually declare it's SQL type."; 17 | } else 18 | { 19 | Message_ = $"Field '{FieldName}' has an incompatible type. Change the field type, manually declare it's SQL type, or declare it as SQLIgnore."; 20 | } 21 | } 22 | 23 | public SQLIncompatableTypeException(string columns, string requestedType) 24 | { 25 | Message_ = $"Couldn't find a compatible column to read from. Requested .NET Type: {requestedType}, Columns: {columns}"; 26 | } 27 | public override string Message => Message_; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/ShimmyMySherbet.MySQL.EF.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net48;net5;net6;net7 5 | A lightweight high-performance .NET MySQL Wrapper and Entity Framework. Designed to make working with a MySQL database as easy as possible 6 | 7 | GPL-3.0-or-later 8 | https://github.com/ShimmyMySherbet/MySQLEntityFramework 9 | MySQL Wrapper EntityFramework 10 | en 11 | ~ Fixed an issue with schema generation errors around SQLDefault 12 | 13 | + Added .NET 6 and .NET 7 support 14 | 1.8.9 15 | https://github.com/ShimmyMySherbet/MySQLEntityFramework 16 | 1.8.9.0 17 | 1.8.9.0 18 | ShimmyMySherbet 19 | ShimmyMySherbet 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ExampleApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ExampleApp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ExampleApp")] 13 | [assembly: AssemblyCopyright("Copyright © 2020")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("91665f0e-10c8-47cd-bcf9-5d079b7abdbb")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/SQlMetaBuilder.cs: -------------------------------------------------------------------------------- 1 | using ShimmyMySherbet.MySQL.EF.Models; 2 | using ShimmyMySherbet.MySQL.EF.Models.Internals; 3 | using System; 4 | 5 | namespace ShimmyMySherbet.MySQL.EF.Internals 6 | { 7 | public static class SQlMetaBuilder 8 | { 9 | public static SQLMetaField GetMeta(IClassField field, int index) 10 | { 11 | var nameF = field.GetAttribute(); 12 | var meta = new SQLMetaField() 13 | { 14 | DBNull = field.AttributeDefined(), 15 | Field = field, 16 | FieldIndex = index, 17 | IsForeignKey = field.AttributeDefined(), 18 | IsPrimaryKey = field.AttributeDefined(), 19 | Omit = field.AttributeDefined(), 20 | OmitOnNull = field.AttributeDefined(), 21 | OmitOnUpdate = field.AttributeDefined(), 22 | OmitOnInsert = field.AttributeDefined(), 23 | Unique = field.AttributeDefined(), 24 | Name = nameF != null ? nameF.Name : field.Name, 25 | Ignore = field.AttributeDefined(), 26 | AutoIncrement = field.AttributeDefined() 27 | }; 28 | 29 | 30 | 31 | 32 | /* 33 | * TO IMPLEMENT: 34 | * 35 | * SQLForeignKey 36 | * 37 | */ 38 | 39 | return meta; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Internals/SQLMetaField.cs: -------------------------------------------------------------------------------- 1 | using ShimmyMySherbet.MySQL.EF.Internals; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ShimmyMySherbet.MySQL.EF.Models.Internals 10 | { 11 | public class SQLMetaField 12 | { 13 | public string Name; 14 | public int FieldIndex; 15 | public IClassField Field; 16 | public bool Omit = false; 17 | public bool OmitOnNull = false; 18 | public bool OmitOnUpdate = false; 19 | public bool OmitOnInsert = false; 20 | 21 | public bool IsPrimaryKey = false; 22 | public bool IsForeignKey = false; 23 | public bool AutoIncrement = false; 24 | public bool DBNull = false; 25 | public bool Unique = false; 26 | public bool Ignore = false; 27 | 28 | 29 | 30 | public bool IncludeUpdate 31 | { 32 | get => !OmitOnUpdate; 33 | set => OmitOnUpdate = !value; 34 | } 35 | public SQLMetaField(string Name = null, int Index = 0, IClassField field = null, bool omitupdate = false) 36 | { 37 | this.Name = Name; 38 | this.FieldIndex = Index; 39 | this.Field = field; 40 | } 41 | public SQLMetaField(string Name = null, int Index = 0, FieldInfo field = null, bool omitupdate = false) 42 | { 43 | this.Name = Name; 44 | this.FieldIndex = Index; 45 | this.Field = new ClassField(field, Index); 46 | } 47 | public SQLMetaField() 48 | { 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /MySQLEntityFramework.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30320.27 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShimmyMySherbet.MySQL.EF", "ShimmyMySherbet.MySQLEntityFramework\ShimmyMySherbet.MySQL.EF.csproj", "{71AE8410-534C-4F13-8AF1-7DD7C6F8D7B2}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleApp", "ExampleApp\ExampleApp.csproj", "{91665F0E-10C8-47CD-BCF9-5D079B7ABDBB}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {71AE8410-534C-4F13-8AF1-7DD7C6F8D7B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {71AE8410-534C-4F13-8AF1-7DD7C6F8D7B2}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {71AE8410-534C-4F13-8AF1-7DD7C6F8D7B2}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {71AE8410-534C-4F13-8AF1-7DD7C6F8D7B2}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {91665F0E-10C8-47CD-BCF9-5D079B7ABDBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {91665F0E-10C8-47CD-BCF9-5D079B7ABDBB}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {91665F0E-10C8-47CD-BCF9-5D079B7ABDBB}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {91665F0E-10C8-47CD-BCF9-5D079B7ABDBB}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {A06FBB5A-0A6C-4267-A0B4-3B5DB1D7CC58} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/ClassField.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace ShimmyMySherbet.MySQL.EF.Internals 6 | { 7 | public class ClassField : ClassFieldBase 8 | { 9 | private FieldInfo m_info; 10 | 11 | public ClassField(FieldInfo info, int fieldIndex) : base(fieldIndex) 12 | { 13 | m_info = info; 14 | Reader = SQLConverter.GetTypeReader(ReadType); 15 | } 16 | public override TypeReader Reader { get; } 17 | 18 | public override Type FieldType => m_info.FieldType; 19 | 20 | public override string Name => m_info.Name; 21 | 22 | public override bool AttributeDefined() 23 | { 24 | return GetAttribute() != null; 25 | } 26 | 27 | public override T GetAttribute() 28 | { 29 | return m_info.GetCustomAttribute(); 30 | } 31 | 32 | public override Attribute[] GetCustomAttributes() 33 | { 34 | return m_info.GetCustomAttributes().ToArray(); 35 | } 36 | 37 | public override object GetValue(object instance) 38 | { 39 | var value = m_info.GetValue(instance); 40 | if (SerializeFormat != null) 41 | { 42 | return SerializationProvider.Serialize(SerializeFormat.Value, value, FieldType); 43 | } 44 | return value; 45 | } 46 | 47 | public override void SetValue(object instance, object obj) 48 | { 49 | if (SerializeFormat != null) 50 | { 51 | obj = SerializationProvider.Deserialize(SerializeFormat.Value, obj, FieldType); 52 | } 53 | 54 | m_info.SetValue(instance, obj); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/ClassProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace ShimmyMySherbet.MySQL.EF.Internals 6 | { 7 | public class ClassProperty : ClassFieldBase 8 | { 9 | private PropertyInfo m_info; 10 | 11 | public ClassProperty(PropertyInfo info, int index) : base(index) 12 | { 13 | m_info = info; 14 | Reader = SQLConverter.GetTypeReader(ReadType); 15 | } 16 | public override TypeReader Reader { get; } 17 | 18 | public override Type FieldType => m_info.PropertyType; 19 | 20 | public override string Name => m_info.Name; 21 | 22 | public override bool AttributeDefined() 23 | { 24 | return GetAttribute() != null; 25 | } 26 | 27 | public override T GetAttribute() 28 | { 29 | return m_info.GetCustomAttribute(); 30 | } 31 | 32 | public override Attribute[] GetCustomAttributes() 33 | { 34 | return m_info.GetCustomAttributes().ToArray(); 35 | } 36 | 37 | public override object GetValue(object instance) 38 | { 39 | var value = m_info.GetValue(instance); 40 | if (SerializeFormat != null) 41 | { 42 | value = SerializationProvider.Serialize(SerializeFormat.Value, value, FieldType); 43 | } 44 | return value; 45 | } 46 | 47 | public override void SetValue(object instance, object obj) 48 | { 49 | if (m_info.CanWrite) 50 | { 51 | if (SerializeFormat != null) 52 | { 53 | obj = SerializationProvider.Deserialize(SerializeFormat.Value, obj, FieldType); 54 | } 55 | m_info.SetValue(instance, obj); 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/PropertyList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace ShimmyMySherbet.MySQL.EF.Internals 6 | { 7 | public class PropertyList : IEnumerable 8 | { 9 | internal List m_Properties = new List(); 10 | 11 | public IEnumerator GetEnumerator() 12 | { 13 | return m_Properties.GetEnumerator(); 14 | } 15 | 16 | IEnumerator IEnumerable.GetEnumerator() 17 | { 18 | return m_Properties.GetEnumerator(); 19 | } 20 | 21 | public void Add(string key, object value) 22 | { 23 | lock (m_Properties) 24 | { 25 | m_Properties.Add(new BuildProperty(key, value)); 26 | } 27 | } 28 | 29 | public void Add(IEnumerable param) 30 | { 31 | lock (m_Properties) 32 | { 33 | foreach (var p in param) 34 | { 35 | m_Properties.Add(new BuildProperty(p.Key, p.Value)); 36 | } 37 | } 38 | } 39 | 40 | public void Reset() 41 | { 42 | lock (m_Properties) 43 | { 44 | m_Properties.Clear(); 45 | } 46 | } 47 | 48 | public void Merge(PropertyList properties) 49 | { 50 | if (properties == this) throw new NotSupportedException("Cannot merge properties into self."); 51 | lock (m_Properties) 52 | { 53 | properties.ReciveMerge(ref m_Properties); 54 | } 55 | } 56 | 57 | internal void ReciveMerge(ref List properties) 58 | { 59 | lock (m_Properties) 60 | { 61 | m_Properties.AddRange(properties); 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/DatabaseSettings.cs: -------------------------------------------------------------------------------- 1 | namespace ShimmyMySherbet.MySQL.EF.Models 2 | { 3 | /// 4 | /// Provides basic connection settings, useful to use as part of an app config. 5 | /// 6 | public class DatabaseSettings 7 | { 8 | public string DatabaseAddress; 9 | public string DatabaseUsername; 10 | public string DatabasePassword; 11 | public string DatabaseName; 12 | public ushort DatabasePort; 13 | 14 | public DatabaseSettings() 15 | { 16 | } 17 | 18 | public DatabaseSettings(string address, string username, string password, string database, ushort port = 3306) 19 | { 20 | DatabaseAddress = address; 21 | DatabaseUsername = username; 22 | DatabasePassword = password; 23 | DatabaseName = database; 24 | DatabasePort = port; 25 | } 26 | 27 | public static DatabaseSettings Default => new DatabaseSettings("127.0.0.1", "Username", "Password", "Database"); 28 | 29 | public override bool Equals(object obj) 30 | { 31 | if (obj is DatabaseSettings set) 32 | { 33 | return 34 | set.DatabaseAddress == DatabaseAddress && 35 | set.DatabaseName == DatabaseName && 36 | set.DatabasePassword == DatabasePassword && 37 | set.DatabasePort == DatabasePort && 38 | set.DatabaseUsername == DatabaseUsername; 39 | } 40 | return false; 41 | } 42 | 43 | public override int GetHashCode() 44 | { 45 | return base.GetHashCode(); 46 | } 47 | 48 | public static DatabaseSettings Parse(string connectionString) => ConnectionStringParser.Parse(connectionString); 49 | 50 | public override string ToString() 51 | { 52 | return $"{(DatabaseAddress != null ? $"Server={DatabaseAddress};" : "")}{(DatabaseName != null ? $"Database={DatabaseName};" : "")}{(DatabaseUsername != null ? $"User Id={DatabaseUsername};" : "")}{(DatabasePassword != null ? $"Password={DatabasePassword};" : "")}{(DatabasePort != 3306 ? $"Port={DatabasePort};" : "")}"; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/ConnectionProviders/TransientConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | using MySql.Data.MySqlClient; 2 | using ShimmyMySherbet.MySQL.EF.Models.Interfaces; 3 | using System.Threading.Tasks; 4 | 5 | namespace ShimmyMySherbet.MySQL.EF.Models.ConnectionProviders 6 | { 7 | /// 8 | /// Creates a new connection each time one is requested using the thread pool 9 | /// 10 | public class TransientConnectionProvider : IConnectionProvider 11 | { 12 | private string m_ConnectionString { get; set; } 13 | public string ConnectionString => m_ConnectionString; 14 | 15 | public bool Connected => true; 16 | 17 | public TransientConnectionProvider(MySqlConnection connection) 18 | { 19 | m_ConnectionString = connection.ConnectionString; 20 | } 21 | 22 | public TransientConnectionProvider(DatabaseSettings settings) 23 | { 24 | m_ConnectionString = settings.ToString(); 25 | } 26 | 27 | public TransientConnectionProvider(string connectionString) 28 | { 29 | m_ConnectionString = connectionString; 30 | } 31 | 32 | public MySqlConnection GetConnection(bool autoOpen = true, bool forceNew = false) 33 | { 34 | var conn = new MySqlConnection(m_ConnectionString); 35 | if (autoOpen) 36 | { 37 | conn.Open(); 38 | } 39 | return conn; 40 | } 41 | 42 | public async Task GetConnectionAsync(bool autoOpen = true, bool forceNew = false) 43 | { 44 | var conn = new MySqlConnection(m_ConnectionString); 45 | if (autoOpen) 46 | { 47 | await conn.OpenAsync(); 48 | } 49 | return conn; 50 | } 51 | 52 | public void ReleaseConnection(MySqlConnection connection) 53 | { 54 | connection.Close(); 55 | } 56 | 57 | public async Task ReleaseConnectionAsync(MySqlConnection connection) 58 | { 59 | await connection.CloseAsync(); 60 | } 61 | 62 | public void Open() 63 | { 64 | using(var con = GetConnection(forceNew: true, autoOpen: true)) 65 | { 66 | } 67 | } 68 | 69 | public async Task OpenAsync() 70 | { 71 | using (var con = await GetConnectionAsync(forceNew: true, autoOpen: true)) 72 | { 73 | } 74 | } 75 | 76 | public void Disconnect() 77 | { 78 | } 79 | 80 | public Task DisconnectAsync() 81 | { 82 | return Task.CompletedTask; 83 | } 84 | 85 | public void Dispose() 86 | { 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/SerializationProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.IO; 4 | using System.Text; 5 | using System.Xml; 6 | using System.Xml.Serialization; 7 | using Newtonsoft.Json; 8 | using ShimmyMySherbet.MySQL.EF.Models; 9 | using ShimmyMySherbet.MySQL.EF.Models.Internals; 10 | using ShimmyMySherbet.MySQL.EF.Models.TypeModel; 11 | 12 | namespace ShimmyMySherbet.MySQL.EF.Internals 13 | { 14 | public static class SerializationProvider 15 | { 16 | private static ConcurrentDictionary m_SerializerCache = new ConcurrentDictionary(); 17 | 18 | private static XmlSerializer GetOrCreateSerializer(Type type) 19 | { 20 | if (m_SerializerCache.TryGetValue(type, out var ser)) 21 | { 22 | return ser; 23 | } 24 | 25 | var n = new XmlSerializer(type); 26 | m_SerializerCache[type] = n; 27 | return n; 28 | } 29 | 30 | public static SQLType GetTypeFor(ESerializeFormat format) 31 | { 32 | switch (format) 33 | { 34 | case ESerializeFormat.JSON: 35 | case ESerializeFormat.XML: 36 | return SQLTypeHelper.GetSQLType(typeof(string)); 37 | } 38 | return null; 39 | } 40 | 41 | public static object Serialize(ESerializeFormat format, object instance, Type type) 42 | { 43 | switch (format) 44 | { 45 | case ESerializeFormat.XML: 46 | var serializer = GetOrCreateSerializer(type); 47 | var sb = new StringBuilder(); 48 | using (var xm = XmlWriter.Create(sb)) 49 | { 50 | serializer.Serialize(xm, instance); 51 | xm.Flush(); 52 | return sb.ToString(); 53 | } 54 | case ESerializeFormat.JSON: 55 | return JsonConvert.SerializeObject(instance); 56 | } 57 | return null; 58 | } 59 | 60 | public static object Deserialize(ESerializeFormat format, object data, Type target) 61 | { 62 | switch (format) 63 | { 64 | case ESerializeFormat.XML: 65 | var serializer = GetOrCreateSerializer(target); 66 | using (var re = new StringReader((string)data)) 67 | { 68 | return serializer.Deserialize(re); 69 | } 70 | case ESerializeFormat.JSON: 71 | return JsonConvert.DeserializeObject((string)data, target); 72 | } 73 | return null; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/ConnectionStringParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | /// 10 | /// Provides a parser to parse a MySQL connection string to 11 | /// 12 | public static class ConnectionStringParser 13 | { 14 | private static readonly Dictionary m_Aliases = new Dictionary(StringComparer.InvariantCultureIgnoreCase) 15 | { 16 | {"Host", new string[] { "Host", "Server", "Data Source", "DataSource", "Address", "Addr", "Network Address" } }, 17 | {"Port", new string[] { "Port" } }, 18 | {"Password", new string[] { "Password", "pwd" } }, 19 | {"Username", new string[] { "Username", "User Id", "Uid", "User name" } }, 20 | {"Database", new string[] { "Database", "db" } } 21 | }; 22 | /// 23 | /// Parses a MySQL connection string into a 24 | /// 25 | public static DatabaseSettings Parse(string conn) 26 | { 27 | string[] parts = conn.Split(';').ToArray(); 28 | Dictionary connParts = new Dictionary(); 29 | foreach (var prtts in parts) 30 | { 31 | if (prtts.Contains("=")) 32 | { 33 | string k = prtts.Split('=')[0]; 34 | string v = prtts.Substring(k.Length + 1); 35 | connParts[k.Trim(' ')] = v.Trim(' '); 36 | } 37 | } 38 | 39 | Dictionary parsed = new Dictionary(StringComparer.InvariantCultureIgnoreCase); 40 | 41 | foreach (var ent in connParts) 42 | { 43 | var h = m_Aliases.FirstOrDefault(x => x.Value.Contains(ent.Key, StringComparer.InvariantCultureIgnoreCase)); 44 | parsed[h.Key] = ent.Value; 45 | } 46 | 47 | DatabaseSettings settings = DatabaseSettings.Default; 48 | 49 | if (parsed.ContainsKey("Host")) 50 | { 51 | settings.DatabaseAddress = parsed["Host"]; 52 | } 53 | 54 | if (parsed.ContainsKey("Password")) 55 | { 56 | settings.DatabasePassword = parsed["Password"]; 57 | } 58 | 59 | if (parsed.ContainsKey("Username")) 60 | { 61 | settings.DatabaseUsername = parsed["Username"]; 62 | } 63 | 64 | if (parsed.ContainsKey("Database")) 65 | { 66 | settings.DatabaseName = parsed["Database"]; 67 | } 68 | 69 | if (parsed.ContainsKey("Port") && ushort.TryParse(parsed["Port"], out var p)) 70 | { 71 | settings.DatabasePort = p; 72 | } 73 | return settings; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Internals/SQLType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using ShimmyMySherbet.MySQL.EF.Models.TypeModel; 7 | 8 | namespace ShimmyMySherbet.MySQL.EF.Models.Internals 9 | { 10 | public abstract class SQLType : Attribute 11 | { 12 | public virtual bool Signed 13 | { 14 | get 15 | { 16 | bool Signed = false; 17 | foreach(Attribute attrib in Attribute.GetCustomAttributes(this.GetType())) 18 | { 19 | if (attrib is SQLSigned) 20 | { 21 | Signed = ((SQLSigned)attrib).Signed; 22 | } 23 | } 24 | return Signed; 25 | } 26 | } 27 | public virtual int Length 28 | { 29 | get 30 | { 31 | int Length = -1; 32 | foreach (Attribute attrib in Attribute.GetCustomAttributes(this.GetType())) 33 | { 34 | if (attrib is SQLLength) 35 | { 36 | Length = ((SQLLength)attrib).Length; 37 | } 38 | } 39 | return Length; 40 | } 41 | } 42 | 43 | public virtual string TypeName 44 | { 45 | get 46 | { 47 | string TypeName = ""; 48 | foreach (Attribute attrib in Attribute.GetCustomAttributes(this.GetType())) 49 | { 50 | if (attrib is SQLTypeName) 51 | { 52 | TypeName = ((SQLTypeName)attrib).Name; 53 | } 54 | } 55 | return TypeName; 56 | } 57 | } 58 | 59 | public virtual bool NoSign 60 | { 61 | get 62 | { 63 | bool NoSign = false; 64 | 65 | foreach (Attribute attrib in Attribute.GetCustomAttributes(this.GetType())) 66 | { 67 | if (attrib is SQLNoSign) 68 | { 69 | NoSign = true; 70 | } 71 | } 72 | return NoSign; 73 | } 74 | } 75 | 76 | 77 | public virtual string SQLRepresentation 78 | { 79 | get 80 | { 81 | string Ustat = ""; 82 | 83 | 84 | if (!Signed && !NoSign) 85 | { 86 | Ustat = " UNSIGNED"; 87 | } 88 | 89 | if (Length == -1) 90 | { 91 | return $"{TypeName}{Ustat}"; 92 | } 93 | else 94 | { 95 | return $"{TypeName}({Length}){Ustat}"; 96 | } 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/TransactionalBulkInserter.cs: -------------------------------------------------------------------------------- 1 | using MySql.Data.MySqlClient; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace ShimmyMySherbet.MySQL.EF.Models 6 | { 7 | /// 8 | /// Similar to BulkInserter, but splits up commands as to now pass the max packet count 9 | /// Use this for really large inserts (e.g., 10K+). 10 | /// 11 | /// 12 | /// 13 | public class TransactionalBulkInserter : IBulkInserter 14 | { 15 | private List> m_Inserters = new List>(); 16 | private BulkInserter m_Current; 17 | private MySqlConnection m_Connection; 18 | public string Table { get; private set; } 19 | private int m_Inserts = 0; 20 | private int m_max; 21 | private bool m_cnew = true; 22 | private EInsertMode m_Mode; 23 | 24 | public TransactionalBulkInserter(MySqlConnection connection, string table, int maxInsertsPerTransaction = 5000, EInsertMode mode = EInsertMode.INSERT) 25 | { 26 | Table = table; 27 | m_Connection = connection; 28 | m_max = maxInsertsPerTransaction; 29 | m_Mode = mode; 30 | } 31 | 32 | /// 33 | /// Adds an object to the insert list 34 | /// 35 | public void Insert(T instance) 36 | { 37 | if (m_cnew) 38 | { 39 | m_Inserts = 0; 40 | m_Current = new BulkInserter(m_Connection, Table, m_Mode); 41 | m_Inserters.Add(m_Current); 42 | m_cnew = false; 43 | } 44 | 45 | lock (m_Current) 46 | m_Current.Insert(instance); 47 | 48 | m_Inserts++; 49 | 50 | if (m_Inserts >= m_max) 51 | { 52 | m_cnew = true; 53 | } 54 | } 55 | 56 | /// 57 | /// Writes all inserts to the database 58 | /// 59 | /// Rows modified 60 | public int Commit() 61 | { 62 | int c = 0; 63 | 64 | lock (m_Current) 65 | { 66 | foreach (var t in m_Inserters) 67 | { 68 | c += t.Commit(); 69 | } 70 | 71 | m_Inserters.Clear(); 72 | 73 | m_cnew = true; 74 | } 75 | 76 | return c; 77 | } 78 | 79 | /// 80 | /// Writes all inserts to the database 81 | /// 82 | /// Rows modified 83 | public async Task CommitAsync() 84 | { 85 | int c = 0; 86 | 87 | IReadOnlyCollection> inserters; 88 | 89 | lock (m_Current) 90 | { 91 | inserters = m_Inserters.ToArray(); 92 | } 93 | 94 | foreach (var t in inserters) 95 | { 96 | c += await t.CommitAsync(); 97 | } 98 | 99 | m_Inserters.Clear(); 100 | 101 | m_cnew = true; 102 | return c; 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/ClassFieldBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ShimmyMySherbet.MySQL.EF.Models; 3 | using ShimmyMySherbet.MySQL.EF.Models.Internals; 4 | 5 | namespace ShimmyMySherbet.MySQL.EF.Internals 6 | { 7 | public abstract class ClassFieldBase : IClassField 8 | { 9 | public abstract string Name { get; } 10 | public abstract TypeReader Reader { get; } 11 | 12 | public string SQLName 13 | { 14 | get 15 | { 16 | var ovr = Meta?.Name; 17 | 18 | if (ovr != null) 19 | { 20 | return ovr; 21 | } 22 | 23 | return Name; 24 | } 25 | } 26 | 27 | public abstract Type FieldType { get; } 28 | 29 | public ClassFieldBase(int index) 30 | { 31 | FieldIndex = index; 32 | } 33 | 34 | public int FieldIndex 35 | { 36 | get; 37 | protected set; 38 | } 39 | 40 | private SQLMetaField m_meta; 41 | 42 | public SQLMetaField Meta 43 | { 44 | get 45 | { 46 | if (m_meta == null) 47 | { 48 | m_meta = SQlMetaBuilder.GetMeta(this, FieldIndex); 49 | } 50 | return m_meta; 51 | } 52 | } 53 | 54 | private ESerializeFormat? m_format = null; 55 | private bool m_checkedFormat = false; 56 | 57 | public ESerializeFormat? SerializeFormat 58 | { 59 | get 60 | { 61 | if (!m_checkedFormat) 62 | { 63 | m_checkedFormat = true; 64 | 65 | var ttr = GetAttribute(); 66 | if (ttr != null) 67 | { 68 | m_format = ttr.Format; 69 | } 70 | } 71 | 72 | return m_format; 73 | } 74 | } 75 | 76 | private SQLType m_OverrideType = null; 77 | private bool m_CheckedOverideType = false; 78 | 79 | public SQLType OverrideType 80 | { 81 | get 82 | { 83 | if (!m_CheckedOverideType) 84 | { 85 | m_CheckedOverideType = true; 86 | m_OverrideType = GetAttribute(); 87 | 88 | if (m_OverrideType == null && SerializeFormat != null) 89 | { 90 | m_OverrideType = SerializationProvider.GetTypeFor(SerializeFormat.Value); 91 | } 92 | } 93 | return m_OverrideType; 94 | } 95 | } 96 | 97 | public Type ReadType => SerializeFormat != null ? typeof(string) : FieldType; 98 | 99 | public abstract bool AttributeDefined() where T : Attribute; 100 | 101 | public abstract T GetAttribute() where T : Attribute; 102 | 103 | public abstract Attribute[] GetCustomAttributes(); 104 | 105 | public abstract object GetValue(object instance); 106 | 107 | public abstract void SetValue(object instance, object obj); 108 | 109 | public bool ShouldOmit(object instance) 110 | { 111 | if (Meta.Omit) return true; 112 | if (Meta.OmitOnNull && GetValue(instance) == null) 113 | { 114 | return true; 115 | } 116 | return false; 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/TypeModel/Types/SQLTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ShimmyMySherbet.MySQL.EF.Models.Internals; 3 | 4 | namespace ShimmyMySherbet.MySQL.EF.Models.TypeModel.Types 5 | { 6 | [SQLTypeName("INT"), SQLSigned(true), SQLNetType(typeof(int))] 7 | public sealed class SQLTypeInt : SQLType 8 | { 9 | } 10 | 11 | [SQLTypeName("INT"), SQLSigned(false), SQLNetType(typeof(uint))] 12 | public sealed class SQLTypeUInt : SQLType 13 | { 14 | } 15 | 16 | [SQLTypeName("SMALLINT"), SQLSigned(true), SQLNetType(typeof(short))] 17 | public sealed class SQLTypeShort : SQLType 18 | { 19 | } 20 | 21 | [SQLTypeName("SMALLINT"), SQLSigned(false), SQLNetType(typeof(ushort))] 22 | public sealed class SQLTypeUShort : SQLType 23 | { 24 | } 25 | 26 | [SQLTypeName("TINYINT"), SQLSigned(false), SQLNetType(typeof(byte))] 27 | public sealed class SQLTypeByte : SQLType 28 | { 29 | } 30 | [SQLTypeName("TINYINT"), SQLSigned(true), SQLNetType(typeof(sbyte))] 31 | public sealed class SQLTypeSByte : SQLType 32 | { 33 | } 34 | 35 | [SQLTypeName("MEDIUMINT"), SQLSigned(true), SQLNetType(null)] 36 | public sealed class SQLTypeMediumInt : SQLType 37 | { 38 | } 39 | 40 | [SQLTypeName("BIGINT"), SQLSigned(true), SQLNetType(typeof(long))] 41 | public sealed class SQLTypeLong : SQLType 42 | { 43 | } 44 | 45 | [SQLTypeName("BIGINT"), SQLSigned(false), SQLNetType(typeof(ulong))] 46 | public sealed class SQLTypeULong : SQLType 47 | { 48 | } 49 | 50 | [SQLTypeName("FLOAT"), SQLSigned(true), SQLNetType(typeof(float))] 51 | public sealed class SQLTypeFloat : SQLType 52 | { 53 | } 54 | 55 | [SQLTypeName("DOUBLE"), SQLSigned(true), SQLNetType(typeof(double))] 56 | public sealed class SQLTypeDouble : SQLType 57 | { 58 | } 59 | 60 | [SQLTypeName("DECIMAL"), SQLSigned(true), SQLNetType(typeof(decimal))] 61 | public sealed class SQLTypeDecimal : SQLType 62 | { 63 | } 64 | 65 | [SQLTypeName("DATE"), SQLNetType(null), SQLNoSign] 66 | public sealed class SQLTypeDate : SQLType 67 | { 68 | } 69 | 70 | [SQLTypeName("DATETIME"), SQLNetType(typeof(DateTime)), SQLNoSign] 71 | public sealed class SQLTypeDateTime : SQLType 72 | { 73 | } 74 | 75 | [SQLTypeName("TIMESTAMP"), SQLNetType(null), SQLNoSign] 76 | public sealed class SQLTypeTimestamp : SQLType 77 | { 78 | } 79 | 80 | [SQLTypeName("TIME"), SQLNetType(null), SQLNoSign] 81 | public sealed class SQLTypeTime : SQLType 82 | { 83 | } 84 | 85 | [SQLTypeName("BOOLEAN"), SQLNetType(typeof(bool)), SQLNoSign] 86 | public sealed class SQLTypeBool : SQLType 87 | { 88 | } 89 | 90 | [SQLTypeName("CHAR"), SQLNetType(typeof(char)), SQLNoSign] 91 | public sealed class SQLTypeChar : SQLType 92 | { 93 | } 94 | 95 | [SQLTypeName("TINYTEXT"), SQLNetType(null), SQLLength(255), SQLNoSign] 96 | public sealed class SQLTypeTinyText : SQLType 97 | { 98 | } 99 | 100 | [SQLTypeName("TEXT"), SQLNetType(typeof(string)), SQLNoSign] 101 | public sealed class SQLTypeText : SQLType 102 | { 103 | } 104 | 105 | [SQLTypeName("BLOB"), SQLNetType(typeof(byte[])), SQLNoSign] 106 | public sealed class SQLTypeBlob : SQLType 107 | { 108 | } 109 | 110 | [SQLTypeName("MEDIUMTEXT"), SQLNetType(null), SQLNoSign] 111 | public sealed class SQLTypeMediumText : SQLType 112 | { 113 | } 114 | 115 | [SQLTypeName("MEDIUMBLOB"), SQLNetType(null), SQLNoSign] 116 | public sealed class SQLTypeMediumBlob : SQLType 117 | { 118 | } 119 | 120 | [SQLTypeName("LONGTEXT"), SQLNetType(null), SQLNoSign] 121 | public sealed class SQLTypeLongText : SQLType 122 | { 123 | } 124 | 125 | [SQLTypeName("LONGBLOB"), SQLNetType(null), SQLNoSign] 126 | public sealed class SQLTyeLongBlob : SQLType 127 | { 128 | } 129 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/Extensions.cs: -------------------------------------------------------------------------------- 1 | using MySql.Data.MySqlClient; 2 | using ShimmyMySherbet.MySQL.EF.Internals; 3 | using System; 4 | using System.Reflection; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models 8 | { 9 | public static class Extensions 10 | { 11 | public static bool IsAttributeSet(MemberInfo member) where T : Attribute 12 | { 13 | return Attribute.IsDefined(member, typeof(T)); 14 | } 15 | 16 | public static T GetAttribute(MemberInfo member) where T : Attribute 17 | { 18 | var obj = Attribute.GetCustomAttribute(member, typeof(T)); 19 | if (obj != null && obj is T t) return t; 20 | return default(T); 21 | } 22 | 23 | public static int EFExecuteNonQuery(this MySqlConnection connection, string command, params object[] parameters) 24 | { 25 | using (var cmd = EntityCommandBuilder.BuildCommand(connection, command, parameters)) 26 | { 27 | return cmd.ExecuteNonQuery(); 28 | } 29 | } 30 | 31 | public static async Task EFExecuteNonQueryAsync(this MySqlConnection connection, string command, params object[] parameters) 32 | { 33 | using (var cmd = EntityCommandBuilder.BuildCommand(connection, command, parameters)) 34 | { 35 | return await cmd.ExecuteNonQueryAsync(); 36 | } 37 | } 38 | 39 | public static async Task EFDeleteAsync(this MySqlConnection connection, T instance, string table) 40 | { 41 | using (var cmd = EntityCommandBuilder.BuildDeleteCommand(instance, table, connection)) 42 | { 43 | return await cmd.ExecuteNonQueryAsync(); 44 | } 45 | } 46 | 47 | public static int EFDelete(this MySqlConnection connection, T instance, string table) 48 | { 49 | using (var cmd = EntityCommandBuilder.BuildDeleteCommand(instance, table, connection)) 50 | { 51 | return cmd.ExecuteNonQuery(); 52 | } 53 | } 54 | 55 | public static int EFInsert(this MySqlConnection connection, T instance, string table) 56 | { 57 | using (var cmd = EntityCommandBuilder.BuildInsertCommand(instance, table, out _, connection)) 58 | { 59 | return cmd.ExecuteNonQuery(); 60 | } 61 | } 62 | 63 | public static async Task EFInsertAsync(this MySqlConnection connection, T instance, string table) 64 | { 65 | using (var cmd = EntityCommandBuilder.BuildInsertCommand(instance, table, out _, connection)) 66 | { 67 | return await cmd.ExecuteNonQueryAsync(); 68 | } 69 | } 70 | 71 | public static async Task EFInsertUpdateAsync(this MySqlConnection connection, T instance, string table) 72 | { 73 | using (var cmd = EntityCommandBuilder.BuildInsertUpdateCommand(instance, table, connection)) 74 | { 75 | return await cmd.ExecuteNonQueryAsync(); 76 | } 77 | } 78 | 79 | public static int EFInsertUpdate(this MySqlConnection connection, T instance, string table) 80 | { 81 | using (var cmd = EntityCommandBuilder.BuildInsertUpdateCommand(instance, table, connection)) 82 | { 83 | return cmd.ExecuteNonQuery(); 84 | } 85 | } 86 | 87 | public static async Task EFUpdateAsync(this MySqlConnection connection, T instance, string table) 88 | { 89 | using (var cmd = EntityCommandBuilder.BuildUpdateCommand(instance, table, connection)) 90 | { 91 | return await cmd.ExecuteNonQueryAsync(); 92 | } 93 | } 94 | 95 | public static int EFUpdate(this MySqlConnection connection, T instance, string table) 96 | { 97 | using (var cmd = EntityCommandBuilder.BuildUpdateCommand(instance, table, connection)) 98 | { 99 | return cmd.ExecuteNonQuery(); 100 | } 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/ConnectionProviders/SingleConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | using MySql.Data.MySqlClient; 2 | using ShimmyMySherbet.MySQL.EF.Models.Interfaces; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace ShimmyMySherbet.MySQL.EF.Models.ConnectionProviders 7 | { 8 | /// 9 | /// Maintains a single database connection. 10 | /// Does not check for connection integrity, and does not do any connection locking. 11 | /// Not recomended for multi-threaded use cases. 12 | /// 13 | /// This version is depricatged, but remains for backwards compatability, see for the revised version with thread locking and connection integrity checking. 14 | /// This class will be marked obsolete in a future version. 15 | /// 16 | public class SingleConnectionProvider : IConnectionProvider 17 | { 18 | private string m_ConnectionString; 19 | public string ConnectionString => m_ConnectionString; 20 | private MySqlConnection m_ConnectionInstance { get; set; } 21 | 22 | public bool Connected => m_ConnectionInstance != null && m_ConnectionInstance.State == System.Data.ConnectionState.Open; 23 | 24 | public SingleConnectionProvider(MySqlConnection connection) 25 | { 26 | m_ConnectionInstance = connection; 27 | m_ConnectionString = connection.ConnectionString; 28 | } 29 | 30 | public SingleConnectionProvider(DatabaseSettings settings) 31 | { 32 | m_ConnectionString = settings.ToString(); 33 | } 34 | 35 | public SingleConnectionProvider(string connectionString) 36 | { 37 | m_ConnectionString = connectionString; 38 | } 39 | 40 | public MySqlConnection GetConnection(bool autoOpen = true, bool forceNew = false) 41 | { 42 | if (forceNew) 43 | { 44 | var conn = new MySqlConnection(m_ConnectionString); 45 | if (autoOpen) 46 | { 47 | conn.Open(); 48 | } 49 | 50 | return conn; 51 | } 52 | else 53 | { 54 | return m_ConnectionInstance; 55 | } 56 | } 57 | 58 | public async Task GetConnectionAsync(bool autoOpen = true, bool forceNew = false) 59 | { 60 | if (forceNew) 61 | { 62 | var conn = new MySqlConnection(m_ConnectionString); 63 | if (autoOpen) 64 | { 65 | await conn.OpenAsync(); 66 | } 67 | 68 | return conn; 69 | } 70 | else 71 | { 72 | return m_ConnectionInstance; 73 | } 74 | } 75 | 76 | public void ReleaseConnection(MySqlConnection connection) 77 | { 78 | if (connection != m_ConnectionInstance) 79 | { 80 | connection.Close(); 81 | } 82 | } 83 | 84 | public async Task ReleaseConnectionAsync(MySqlConnection connection) 85 | { 86 | if (connection != m_ConnectionInstance) 87 | { 88 | await connection.CloseAsync(); 89 | } 90 | } 91 | 92 | public void Open() 93 | { 94 | if (m_ConnectionInstance == null) 95 | { 96 | m_ConnectionInstance = new MySqlConnection(m_ConnectionString); 97 | m_ConnectionInstance.Open(); 98 | } 99 | } 100 | 101 | public async Task OpenAsync() 102 | { 103 | if (m_ConnectionInstance == null) 104 | { 105 | m_ConnectionInstance = new MySqlConnection(m_ConnectionString); 106 | await m_ConnectionInstance.OpenAsync(); 107 | } 108 | } 109 | 110 | public void Disconnect() 111 | { 112 | m_ConnectionInstance?.Close(); 113 | } 114 | 115 | public async Task DisconnectAsync() 116 | { 117 | await m_ConnectionInstance?.CloseAsync(); 118 | } 119 | 120 | public void Dispose() 121 | { 122 | m_ConnectionInstance.Dispose(); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/InternalExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MySql.Data.MySqlClient; 4 | using ShimmyMySherbet.MySQL.EF.Models; 5 | 6 | namespace ShimmyMySherbet.MySQL.EF.Internals 7 | { 8 | internal static class InternalExtensions 9 | { 10 | public static void Add(this MySqlCommand com, IEnumerable parameters) 11 | { 12 | foreach (var p in parameters) 13 | { 14 | com.Parameters.AddWithValue(p.Key, p.Value); 15 | } 16 | } 17 | 18 | public static IClassField DetermineIDField(this List fields) 19 | { 20 | var matches = new List(); 21 | foreach (var f in fields) 22 | { 23 | if (f.AttributeDefined() && f.AttributeDefined() && f.FieldType.IsNumeric()) 24 | { 25 | matches.Add(f); 26 | } 27 | } 28 | 29 | if (matches.Count == 1) 30 | { 31 | return matches[0]; 32 | } 33 | 34 | return null; // No matching field, or ambiguity in multiple numeric composite auto-increment keys 35 | } 36 | 37 | private static readonly HashSet m_NumericMap = new HashSet(new[] { typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) }); 38 | 39 | public static bool IsNumeric(this Type t) => t.IsPrimitive && m_NumericMap.Contains(t); 40 | 41 | public static bool IsZero(this object obj) 42 | { 43 | if (obj is sbyte sb) 44 | return sb == 0; 45 | if (obj is byte b) 46 | return b == 0; 47 | if (obj is short s) 48 | return s == 0; 49 | if (obj is ushort us) 50 | return us == 0; 51 | if (obj is int i) 52 | return i == 0; 53 | if (obj is uint ui) 54 | return ui == 0; 55 | if (obj is long l) 56 | return l == 0; 57 | if (obj is ulong ul) 58 | return ul == 0; 59 | if (obj is float f) 60 | return f == 0; 61 | if (obj is double d) 62 | return d == 0; 63 | if (obj is decimal dc) 64 | return dc == 0; 65 | return false; 66 | } 67 | 68 | public static bool TryConvertNumeric(this long inp, Type target, out object inst) 69 | { 70 | if (target == typeof(sbyte)) 71 | { 72 | inst = (sbyte)inp; 73 | return true; 74 | } 75 | if (target == typeof(byte)) 76 | { 77 | inst = (byte)inp; 78 | return true; 79 | } 80 | if (target == typeof(byte)) 81 | { 82 | inst = (byte)inp; 83 | return true; 84 | } 85 | if (target == typeof(short)) 86 | { 87 | inst = (short)inp; 88 | return true; 89 | } 90 | if (target == typeof(ushort)) 91 | { 92 | inst = (ushort)inp; 93 | return true; 94 | } 95 | if (target == typeof(int)) 96 | { 97 | inst = (int)inp; 98 | return true; 99 | } 100 | if (target == typeof(uint)) 101 | { 102 | inst = (uint)inp; 103 | return true; 104 | } 105 | if (target == typeof(long)) 106 | { 107 | inst = inp; 108 | return true; 109 | } 110 | if (target == typeof(ulong)) 111 | { 112 | inst = (ulong)inp; 113 | return true; 114 | } 115 | if (target == typeof(float)) 116 | { 117 | inst = (float)inp; 118 | return true; 119 | } 120 | if (target == typeof(double)) 121 | { 122 | inst = (double)inp; 123 | return true; 124 | } 125 | inst = null; 126 | return false; 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/TypeModel/SQLTypeHelper.cs: -------------------------------------------------------------------------------- 1 | using ShimmyMySherbet.MySQL.EF.Models.Internals; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models.TypeModel 8 | { 9 | public class SQLTypeHelper 10 | { 11 | private Dictionary NetTypeIndex = new Dictionary(); 12 | 13 | public SQLTypeHelper() 14 | { 15 | foreach (Type Type in Assembly.GetExecutingAssembly().GetTypes().Where(x => typeof(SQLType).IsAssignableFrom(x.BaseType))) 16 | { 17 | if (Attribute.IsDefined(Type, typeof(SQLNetType))) 18 | { 19 | Type NetType = ((SQLNetType)Attribute.GetCustomAttribute(Type, typeof(SQLNetType))).Type; 20 | if (NetType != null && !NetTypeIndex.ContainsKey(NetType)) 21 | { 22 | SQLType SQLType = (SQLType)Activator.CreateInstance(Type); 23 | NetTypeIndex.Add(NetType, SQLType); 24 | } 25 | } 26 | } 27 | } 28 | 29 | public SQLType GetSQLTypeIndexed(Type T) 30 | { 31 | var real = Nullable.GetUnderlyingType(T); 32 | if (real != null) 33 | { 34 | T = real; 35 | } 36 | 37 | if (NetTypeIndex.ContainsKey(T)) 38 | { 39 | return NetTypeIndex[T]; 40 | } 41 | else 42 | { 43 | return null; 44 | } 45 | } 46 | 47 | public static SQLType GetSQLType(Type T) 48 | { 49 | foreach (Type Type in Assembly.GetExecutingAssembly().GetTypes().Where(x => typeof(SQLType).IsAssignableFrom(x.BaseType))) 50 | { 51 | foreach (Attribute attrib in Attribute.GetCustomAttributes(Type)) 52 | { 53 | if (attrib is SQLNetType) 54 | { 55 | SQLNetType NType = (SQLNetType)attrib; 56 | if (NType.Type == null) break; 57 | if (NType.Type == T) 58 | { 59 | return Activator.CreateInstance(Type) as SQLType; 60 | } 61 | } 62 | } 63 | } 64 | return null; 65 | } 66 | 67 | private static Type[] Int16Equivilants = { typeof(byte), typeof(sbyte) }; 68 | private static Type[] UInt16Equivilants = { typeof(byte) }; 69 | private static Type[] Int32Equivilants = { typeof(byte), typeof(sbyte), typeof(Int16), typeof(UInt16) }; 70 | private static Type[] UInt32Equivilants = { typeof(byte), typeof(UInt16) }; 71 | private static Type[] Int64Equivilants = { typeof(byte), typeof(sbyte), typeof(Int16), typeof(UInt16), typeof(UInt32), typeof(UInt32) }; 72 | private static Type[] UInt64Equivilants = { typeof(byte), typeof(UInt16), typeof(UInt32) }; 73 | 74 | /// 75 | /// Checks for Loss-less casts 76 | /// 77 | public static bool CanCastEquivilant(Type BaseType, Type TargetType) 78 | { 79 | if (TargetType == typeof(Int16)) 80 | { 81 | return Int16Equivilants.Contains(BaseType); 82 | } 83 | else if (TargetType == typeof(UInt16)) 84 | { 85 | return UInt16Equivilants.Contains(BaseType); 86 | } 87 | else if (TargetType == typeof(Int32)) 88 | { 89 | return Int32Equivilants.Contains(BaseType); 90 | } 91 | else if (TargetType == typeof(UInt32)) 92 | { 93 | return UInt32Equivilants.Contains(BaseType); 94 | } 95 | else if (TargetType == typeof(Int64)) 96 | { 97 | return Int64Equivilants.Contains(BaseType); 98 | } 99 | else if (TargetType == typeof(UInt64)) 100 | { 101 | return UInt64Equivilants.Contains(BaseType); 102 | } 103 | else 104 | { 105 | return false; 106 | } 107 | } 108 | 109 | public static bool NumericType(Type T) 110 | { 111 | if (T.IsPrimitive) 112 | { 113 | return ((T != typeof(char)) && (T != typeof(string)) && (T != typeof(object))); 114 | } 115 | else return false; 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/BulkExecutor.cs: -------------------------------------------------------------------------------- 1 | using MySql.Data.MySqlClient; 2 | using ShimmyMySherbet.MySQL.EF.Internals; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace ShimmyMySherbet.MySQL.EF.Models 7 | { 8 | /// 9 | /// Used to bulk execute commands against a db. 10 | /// NOTE: if your work is insert-heavy, use BulkInserter instead. 11 | /// 12 | public class BulkExecutor 13 | { 14 | private StringBuilder m_Commands = new StringBuilder(); 15 | private MySqlConnection m_Connection; 16 | private PrefixAssigner m_Assigner = new PrefixAssigner(); 17 | private PropertyList m_MasterPropertiesList = new PropertyList(); 18 | 19 | 20 | public BulkExecutor(MySqlConnection connection) 21 | { 22 | m_Connection = connection; 23 | ExecuteNonQuery("START TRANSACTION"); 24 | } 25 | 26 | public void ExecuteNonQuery(string command, params object[] parameters) 27 | { 28 | if (!command.EndsWith(";")) 29 | { 30 | command += ';'; 31 | } 32 | string value = EntityCommandBuilder.BuildCommandContent(command, m_Assigner.AssignPrefix(), out var properties, parameters); 33 | lock (m_Commands) 34 | { 35 | m_Commands.AppendLine(value); 36 | } 37 | properties.Merge(m_MasterPropertiesList); 38 | } 39 | 40 | /// 41 | /// NOTE: Use BulkInserter instead where possible 42 | /// 43 | /// 44 | public void Insert(T Obj, string Table) 45 | { 46 | string value = EntityCommandBuilder.BuildInsertCommandContent(Obj, Table, m_Assigner.AssignPrefix(), out var properties); 47 | lock (m_Commands) 48 | { 49 | m_Commands.AppendLine(value); 50 | } 51 | properties.Merge(m_MasterPropertiesList); 52 | } 53 | 54 | public void InsertUpdate(T Obj, string Table) 55 | { 56 | string value = EntityCommandBuilder.BuildInsertUpdateCommandContent(Obj, Table, m_Assigner.AssignPrefix(), out var properties); 57 | lock (m_Commands) 58 | { 59 | m_Commands.AppendLine(value); 60 | } 61 | properties.Merge(m_MasterPropertiesList); 62 | } 63 | 64 | public void Update(T Obj, string Table) 65 | { 66 | string value = EntityCommandBuilder.BuildUpdateCommandContent(Obj, Table, m_Assigner.AssignPrefix(), out var properties); 67 | lock (m_Commands) 68 | { 69 | m_Commands.AppendLine(value); 70 | } 71 | properties.Merge(m_MasterPropertiesList); 72 | } 73 | 74 | public void Delete(T Obj, string Table) 75 | { 76 | string value = EntityCommandBuilder.BuildDeleteCommandContent(Obj, Table, m_Assigner.AssignPrefix(), out var properties); 77 | lock (m_Commands) 78 | { 79 | m_Commands.AppendLine(value); 80 | } 81 | properties.Merge(m_MasterPropertiesList); 82 | } 83 | 84 | public int Commit() 85 | { 86 | ExecuteNonQuery("COMMIT"); 87 | lock (m_Commands) 88 | lock (m_Connection) 89 | lock (m_MasterPropertiesList) 90 | { 91 | using (MySqlCommand command = new MySqlCommand(m_Commands.ToString(), m_Connection)) 92 | { 93 | command.CommandTimeout = 2147483; 94 | command.EnableCaching = false; 95 | foreach (var p in m_MasterPropertiesList) 96 | { 97 | command.Parameters.AddWithValue(p.Key, p.Value); 98 | } 99 | 100 | return command.ExecuteNonQuery(); 101 | } 102 | } 103 | } 104 | 105 | public async Task CommitAsync() 106 | { 107 | ExecuteNonQuery("COMMIT"); 108 | string cmdContent; 109 | PropertyList properties = new PropertyList(); 110 | 111 | lock (m_Commands) 112 | { 113 | cmdContent = m_Commands.ToString(); 114 | } 115 | 116 | m_MasterPropertiesList.Merge(properties); 117 | 118 | using (MySqlCommand command = new MySqlCommand(cmdContent, m_Connection)) 119 | { 120 | command.CommandTimeout = 2147483; 121 | foreach (var p in properties) 122 | { 123 | command.Parameters.AddWithValue(p.Key, p.Value); 124 | } 125 | 126 | return await command.ExecuteNonQueryAsync(); 127 | } 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Core/DatabaseClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Threading.Tasks; 5 | using ShimmyMySherbet.MySQL.EF.Models; 6 | using ShimmyMySherbet.MySQL.EF.Models.Interfaces; 7 | 8 | namespace ShimmyMySherbet.MySQL.EF.Core 9 | { 10 | /// 11 | /// Used to create database Managers/Brides 12 | /// To use, inherit this class, and add fields/properties of DatabaseTable, or any class that inherits IDatabaseTable 13 | /// This class will supply each instance with a datasdatabase connection. 14 | /// Connect() will open the databse connection and CheckSchema() will create any missing database tables 15 | /// TIP: To provide table specific operations, in another class inherit DatabaseTable and include it in your database manager 16 | /// 17 | /// 18 | public abstract class DatabaseClient 19 | { 20 | public MySQLEntityClient Client { get; private set; } 21 | public bool SingleConnectionMode { get; private set; } 22 | private bool m_AutoInit = true; 23 | private Type m_Class => GetType(); 24 | 25 | public bool Connected => Client.Connected; 26 | 27 | private bool m_Inited = false; 28 | 29 | /// 30 | /// When enabled, numeric auto-increment primary key fields will be updated with the ID assigned to the new row when inserting. 31 | /// This is still an experimental feature 32 | /// 33 | public bool AutoUpdateInstanceKey 34 | { 35 | get => Client.AutoUpdateInstanceKey; 36 | set => Client.AutoUpdateInstanceKey = value; 37 | } 38 | 39 | private List m_Initializers = new List(); 40 | 41 | public DatabaseClient(DatabaseSettings settings, bool singleConnectionMode = true, bool autoInit = true) 42 | { 43 | Client = new MySQLEntityClient(settings, singleConnectionMode); 44 | m_AutoInit = autoInit; 45 | FinaliseConstructor(); 46 | } 47 | 48 | public DatabaseClient(string connectionString, bool singleConnectionMode = true, bool autoInit = true) 49 | { 50 | Client = new MySQLEntityClient(connectionString, singleConnectionMode); 51 | m_AutoInit = autoInit; 52 | FinaliseConstructor(); 53 | } 54 | 55 | public DatabaseClient(IConnectionProvider connectionProvider, bool autoInit = true) 56 | { 57 | Client = new MySQLEntityClient(connectionProvider); 58 | m_AutoInit = autoInit; 59 | FinaliseConstructor(); 60 | } 61 | 62 | public DatabaseClient(string address, string username, string password, string database, ushort port = 3306, bool singleConnectionMode = true, bool autoInit = true) 63 | { 64 | Client = new MySQLEntityClient(address, username, password, database, port, singleConnectionMode); 65 | m_AutoInit = autoInit; 66 | FinaliseConstructor(); 67 | } 68 | 69 | private void FinaliseConstructor() 70 | { 71 | if (m_AutoInit) 72 | { 73 | Init(); 74 | } 75 | } 76 | 77 | public void CheckSchema() 78 | { 79 | if (!m_Inited) 80 | { 81 | throw new InvalidOperationException("Table instances has not been initialized yet. Call Init() or enable auto init."); 82 | } 83 | 84 | foreach (var init in m_Initializers) 85 | { 86 | init.CheckSchema(); 87 | } 88 | } 89 | 90 | protected void Init() 91 | { 92 | if (m_Inited) 93 | { 94 | throw new InvalidOperationException("Table instances have already been initialized."); 95 | } 96 | m_Inited = true; 97 | foreach (var field in m_Class.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) 98 | { 99 | if (typeof(IDatabaseTableInitializer).IsAssignableFrom(field.FieldType)) 100 | { 101 | var inst = field.GetValue(this); 102 | if (inst != null && inst is IDatabaseTableInitializer init) 103 | { 104 | m_Initializers.Add(init); 105 | } 106 | } 107 | } 108 | 109 | foreach (var property in m_Class.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) 110 | { 111 | if (property.CanRead && typeof(IDatabaseTableInitializer).IsAssignableFrom(property.PropertyType)) 112 | { 113 | var inst = property.GetValue(this); 114 | if (inst != null && inst is IDatabaseTableInitializer init) 115 | { 116 | m_Initializers.Add(init); 117 | } 118 | } 119 | } 120 | 121 | SendClientInstances(); 122 | } 123 | 124 | public bool Connect() 125 | { 126 | return Client.Connect(); 127 | } 128 | 129 | public bool Connect(out string errorMessage) 130 | { 131 | return Client.Connect(out errorMessage); 132 | } 133 | 134 | public async Task ConnectAsync() 135 | { 136 | return await Client.ConnectAsync(); 137 | } 138 | 139 | private void SendClientInstances() 140 | { 141 | foreach (var init in m_Initializers) 142 | { 143 | init.SendClient(Client); 144 | } 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MySQLEntityFramework 2 | A Lightweight MySQL Entity Adapter for .NET 3 | 4 | # Usage 5 | 6 | Basic usage of this library is centered around MySQLEntityClient. This class povides access to most SQL functions. When working with a table, you use a class associated with it. 7 | This class is also what you use to read/write entries from a database table. 8 | 9 | For the following examples, this class is used: 10 | ```cs 11 | public class UserAccount 12 | { 13 | [SQLPrimaryKey, SQLAutoIncrement, SQLOmit] 14 | public int ID; 15 | 16 | [SQLUnique] 17 | public string Username; 18 | 19 | public byte[] HashData; 20 | 21 | [SQLIndex] 22 | public ulong SteamID; 23 | 24 | public string EmailAddress; 25 | 26 | public DateTime Created; 27 | } 28 | ``` 29 | ## Creating the MySQLEntityClient 30 | 31 | The MySQLEntityClient is used for most functions. If you need pure performance (sub 0.2ms for simple queries and inserts) you should use the SQLConverter and EntityCommandBuilder classes more specific methods rather than the Entity Client's generalised methods. 32 | 33 | This Client has two modes, Single Connection mode and Multiple Connection mode. 34 | 35 | In Single Connection Mode, a single SQL connection is made, and maintained. All methods in the client will use this single connection, locking it while it's in use. This means, in this mode, if multiple methods are invoked accross two or more threads, the methods will block until the connection is available. 36 | This mode is ideal for mostly single threaded screnarios that need to transfer alot of data (e.g., 50K queries in 4 sec on a single thread). 37 | 38 | Multiple Connection Mode 39 | 40 | In this mode, a new connection is made each time a connection is needed. This means that the client can be used accross many threads simultaneously without blocking. 41 | 42 | This mode is ideal for database wrappers that will likely be accessed across multiple threads. 43 | 44 | ```cs 45 | bool SingleConnectionMode = true; 46 | MySQLEntityClient EntityClient = new MySQLEntityClient("127.0.0.1", "UserName", "SuperSecretPassword", "Database", 3306, SingleConnectionMode); 47 | 48 | Console.WriteLine($"Connected: {EntityClient.Connected}"); 49 | ``` 50 | *From here on, this MySQLEntityClient will just be referanced as EntityClient in code snippets* 51 | 52 | ## Creating a Database Table 53 | 54 | This uses the model of the supplied class, including any SQL attributes on it's fields, to create an equivilant database table. 55 | 56 | ```cs 57 | EntityClient.CreateTable("Users"); 58 | ``` 59 | 60 | ## Querying 61 | 62 | Selecting multiple entries: 63 | 64 | ```cs 65 | List Accounts = EntityClient.Query("SELECT * FROM Users"); 66 | ``` 67 | 68 | Selecting a single entry: 69 | 70 | This method returns null if there are no results. 71 | 72 | ```cs 73 | UserAccount userAccount1 = EntityClient.QuerySingle("SELECT * FROM Users WHERE ID = 1"); 74 | ``` 75 | 76 | Using Parameters 77 | 78 | Most of MySQLEntityClient's methods provide an easy way to create command parameters. Parameters are a feature of MySQL.Data that allows you to securely represent a variable in a MySQLCommand. 79 | These parameters safely format and escape the variable, to prevent SQL injection attacks and ensure proper query formatting. These should be used when working with strings or class types (e.g., DateTime). 80 | 81 | ```cs 82 | UserAccount BobsAccount = EntityClient.QuerySingle("SELECT * FROM Users WHERE Username = @0 AND EmailAddresss = @1", "Bob", "BobsMail@mail.com"); 83 | ``` 84 | 85 | ## Inserting 86 | 87 | Since the ID field of UserAccount has sQLOmit, it is omitted from the insert. This means that the value will resolve to the default value. In this case, since it is also tagged as AutoIncrement when the table was created, it will resolve to the new auto-increment value. 88 | 89 | ```cs 90 | UserAccount NewAccount = new UserAccount() 91 | { 92 | Username = "NewUser", 93 | EmailAddress = "Email@Address.com", 94 | SteamID = 123456789, 95 | Created = DateTime.Now, 96 | HashData = new byte[] { 10, 21, 21 } 97 | }; 98 | EntityClient.Insert(NewAccount, "Users"); 99 | ``` 100 | 101 | ## Updating 102 | 103 | This method requires that the supplied object's class has a field tagged as SQLPrimaryKey. 104 | 105 | ```cs 106 | BobsAccount.EmailAddress = "BobsNewEmailAddress@email.com"; 107 | EntityClient.Update(BobsAccount, "Users"); 108 | ``` 109 | 110 | ## Deleting 111 | 112 | This method requires that the supplied object's class has a field tagged as SQLPrimaryKey. 113 | 114 | ```cs 115 | EntityClient.Delete(BobsAccount, "Users"); 116 | ``` 117 | ## Checking for a table 118 | 119 | This method allows you to check if a table exists by it's name in the current database. 120 | 121 | ```cs 122 | if (EntityClient.TableExists("Users")) 123 | { 124 | Console.WriteLine("Table Exists."); 125 | } else 126 | { 127 | Console.WriteLine("Tabe does not exist."); 128 | } 129 | ``` 130 | 131 | ## Checking Connection Status 132 | 133 | If ReuseSingleConnection (Single Connection Mode) is enabled, it will return the connection status of the active MySQL connection. If the client is in Multiple Connection mode, this will attempt to create a new connection, and returns if the connection was successful. 134 | 135 | ```cs 136 | if (EntityClient.Connected) 137 | { 138 | Console.WriteLine("Connected!"); 139 | } else 140 | { 141 | Console.WriteLine("Connection Failed."); 142 | } 143 | ``` 144 | ## Deleting a table 145 | 146 | This method will drop a table and all of it's contents. 147 | 148 | ```cs 149 | EntityClient.DeleteTable("Users"); 150 | ``` 151 | 152 | # SQL Attributes 153 | 154 | For a full list of SQL Attribues, see the wiki page https://github.com/ShimmyMySherbet/MySQLEntityFramework/wiki/SQL-Attributes 155 | 156 | # Installation 157 | 158 | Install from Nuget: 159 | ``` 160 | Install-Package ShimmyMySherbet.MySQL.EF 161 | ``` 162 | 163 | 164 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/BulkInserter.cs: -------------------------------------------------------------------------------- 1 | using MySql.Data.MySqlClient; 2 | using ShimmyMySherbet.MySQL.EF.Internals; 3 | using ShimmyMySherbet.MySQL.EF.Models.Internals; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace ShimmyMySherbet.MySQL.EF.Models 12 | { 13 | /// 14 | /// Provides a way to quickly insert large amount of objects into a table. 15 | /// For larger insert (e.g., 10K+, use TransactionalBulkInserter) 16 | /// 17 | /// Database table class type 18 | /// 19 | public class BulkInserter : IBulkInserter 20 | { 21 | public string Table { get; private set; } 22 | 23 | private MySqlConnection m_Connection; 24 | 25 | private PrefixAssigner m_Assigner = new PrefixAssigner(); 26 | 27 | private PropertyList m_BuildProperties = new PropertyList(); 28 | 29 | private StringBuilder m_CommandBuilder { get; set; } = new StringBuilder(); 30 | private List m_SQLMetas = new List(); 31 | private bool m_FirstInsert = true; 32 | private EInsertMode m_Mode; 33 | 34 | 35 | public BulkInserter(MySqlConnection connection, string table, EInsertMode mode = EInsertMode.INSERT) 36 | { 37 | m_Connection = connection; 38 | m_Mode = mode; 39 | Table = table; 40 | 41 | foreach (FieldInfo Field in typeof(T).GetFields()) 42 | { 43 | bool Include = true; 44 | string Name = Field.Name; 45 | foreach (Attribute Attrib in Attribute.GetCustomAttributes(Field)) 46 | { 47 | if (Attrib is SQLOmit || Attrib is SQLIgnore) 48 | { 49 | Include = false; 50 | break; 51 | } 52 | else if (Attrib is SQLPropertyName) 53 | { 54 | Name = ((SQLPropertyName)Attrib).Name; 55 | } 56 | } 57 | if (Include) 58 | { 59 | if (m_SQLMetas.Where(x => string.Equals(x.Name, Name, StringComparison.InvariantCultureIgnoreCase)).Count() != 0) continue; 60 | m_SQLMetas.Add(new SQLMetaField(Name, m_SQLMetas.Count, Field)); 61 | } 62 | } 63 | Reset(); 64 | } 65 | 66 | /// 67 | /// Discard all queued inserts and resets the inserter. 68 | /// 69 | public void Reset() 70 | { 71 | m_Assigner.Reset(); 72 | m_BuildProperties.Reset(); 73 | lock (m_CommandBuilder) 74 | { 75 | m_CommandBuilder = new StringBuilder(); 76 | string Command = $"INSERT{(m_Mode == EInsertMode.INSERT_IGNORE ? " IGNORE" : "")} INTO `{Table}` ({string.Join(", ", m_SQLMetas.CastEnumeration(x => x.Name))}) VALUES"; 77 | m_CommandBuilder.Append(Command); 78 | } 79 | } 80 | 81 | /// 82 | /// Adds an object to the insert list 83 | /// 84 | public void Insert(T instance) 85 | { 86 | int prefix = m_Assigner.AssignPrefix(); 87 | lock (m_SQLMetas) 88 | { 89 | lock (m_CommandBuilder) 90 | { 91 | m_CommandBuilder.Append($"{(m_FirstInsert ? "" : ",")}\n({string.Join(", ", m_SQLMetas.CastEnumeration(x => $"@{prefix}_{x.FieldIndex}"))})"); 92 | } 93 | m_FirstInsert = false; 94 | 95 | lock (m_BuildProperties) 96 | { 97 | foreach (var meta in m_SQLMetas) 98 | { 99 | m_BuildProperties.Add($"@{prefix}_{meta.FieldIndex}", meta.Field.GetValue(instance)); 100 | } 101 | } 102 | } 103 | } 104 | 105 | /// 106 | /// Writes all inserts to the database 107 | /// 108 | /// Rows modified 109 | public int Commit() 110 | { 111 | int a; 112 | lock (m_CommandBuilder) 113 | { 114 | m_CommandBuilder.Append(";"); 115 | lock (m_Connection) 116 | { 117 | using (MySqlCommand command = new MySqlCommand(m_CommandBuilder.ToString(), m_Connection)) 118 | { 119 | command.CommandTimeout = 2147483; 120 | lock (m_BuildProperties) 121 | { 122 | foreach (var p in m_BuildProperties) 123 | { 124 | command.Parameters.AddWithValue(p.Key, p.Value); 125 | } 126 | } 127 | 128 | 129 | a = command.ExecuteNonQuery(); 130 | } 131 | } 132 | } 133 | Reset(); 134 | return a; 135 | } 136 | /// 137 | /// Writes all inserts to the database 138 | /// 139 | /// Rows modified 140 | public async Task CommitAsync() 141 | { 142 | string cmdTxt; 143 | lock (m_CommandBuilder) 144 | { 145 | m_CommandBuilder.Append(";"); 146 | cmdTxt = m_CommandBuilder.ToString(); 147 | } 148 | PropertyList properties = new PropertyList(); 149 | m_BuildProperties.Merge(properties); 150 | 151 | using (MySqlCommand command = new MySqlCommand(cmdTxt, m_Connection)) 152 | { 153 | command.CommandTimeout = 2147483; 154 | 155 | foreach (var p in properties) 156 | { 157 | command.Parameters.AddWithValue(p.Key, p.Value); 158 | } 159 | Reset(); 160 | return await command.ExecuteNonQueryAsync(); 161 | } 162 | } 163 | } 164 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/MySQLEntityReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using MySql.Data.MySqlClient; 9 | using ShimmyMySherbet.MySQL.EF.Internals; 10 | using ShimmyMySherbet.MySQL.EF.Models.TypeModel; 11 | #pragma warning disable CA2100 12 | namespace ShimmyMySherbet.MySQL.EF.Internals 13 | { 14 | public sealed class MySQLEntityReader 15 | { 16 | public SQLTypeHelper IndexedHelper; 17 | public List RetriveFromDatabase(MySqlConnection Connection, string Command, params object[] Arguments) 18 | { 19 | if (Connection.State != System.Data.ConnectionState.Open) Connection.Open(); 20 | using (MySqlCommand Command_ = new MySqlCommand(Command, Connection)) 21 | { 22 | for (int i = 0; i < Arguments.Length; i++) 23 | { 24 | if (Command.Contains($"{{{i}}}")) Command_.Parameters.AddWithValue($"{{{i}}}", Arguments[i]); 25 | if (Command.Contains($"@{i}")) Command_.Parameters.AddWithValue($"@{i}", Arguments[i]); 26 | } 27 | using (DbDataReader Reader = Command_.ExecuteReader()) 28 | { 29 | SQLConverter Converter = new SQLConverter(); 30 | Converter.TypeHelper = IndexedHelper; 31 | return Converter.ReadModelsFromReader(Reader); 32 | } 33 | } 34 | } 35 | 36 | public async Task> RetriveFromDatabaseAsync(MySqlConnection Connection, string Command, params object[] Arguments) 37 | { 38 | if (Connection.State != System.Data.ConnectionState.Open) await Connection.OpenAsync(); 39 | using (MySqlCommand Command_ = new MySqlCommand(Command, Connection)) 40 | { 41 | for (int i = 0; i < Arguments.Length; i++) 42 | { 43 | if (Command.Contains($"{{{i}}}")) Command_.Parameters.AddWithValue($"{{{i}}}", Arguments[i]); 44 | if (Command.Contains($"@{i}")) Command_.Parameters.AddWithValue($"@{i}", Arguments[i]); 45 | } 46 | using (DbDataReader Reader = await Command_.ExecuteReaderAsync()) 47 | { 48 | SQLConverter Converter = new SQLConverter(); 49 | Converter.TypeHelper = IndexedHelper; 50 | return await Converter.ReadModelsFromReaderAsync(Reader); 51 | } 52 | } 53 | } 54 | 55 | 56 | public List RetriveFromDatabaseCapped(MySqlConnection Connection, int limit, string Command, params object[] Arguments) 57 | { 58 | if (Connection.State != System.Data.ConnectionState.Open) Connection.Open(); 59 | using (MySqlCommand Command_ = new MySqlCommand(Command, Connection)) 60 | { 61 | for (int i = 0; i < Arguments.Length; i++) 62 | { 63 | if (Command.Contains($"{{{i}}}")) Command_.Parameters.AddWithValue($"{{{i}}}", Arguments[i]); 64 | if (Command.Contains($"@{i}")) Command_.Parameters.AddWithValue($"@{i}", Arguments[i]); 65 | } 66 | using (DbDataReader Reader = Command_.ExecuteReader()) 67 | { 68 | SQLConverter Converter = new SQLConverter(); 69 | Converter.TypeHelper = IndexedHelper; 70 | return Converter.ReadModelsFromReader(Reader, limit); 71 | } 72 | } 73 | } 74 | 75 | public async Task> RetriveFromDatabaseCappedAsync(MySqlConnection Connection, int limit, string Command, params object[] Arguments) 76 | { 77 | if (Connection.State != System.Data.ConnectionState.Open) await Connection.OpenAsync(); 78 | using (MySqlCommand Command_ = new MySqlCommand(Command, Connection)) 79 | { 80 | for (int i = 0; i < Arguments.Length; i++) 81 | { 82 | if (Command.Contains($"{{{i}}}")) Command_.Parameters.AddWithValue($"{{{i}}}", Arguments[i]); 83 | if (Command.Contains($"@{i}")) Command_.Parameters.AddWithValue($"@{i}", Arguments[i]); 84 | } 85 | using (DbDataReader Reader = await Command_.ExecuteReaderAsync()) 86 | { 87 | SQLConverter Converter = new SQLConverter(); 88 | Converter.TypeHelper = IndexedHelper; 89 | return await Converter.ReadModelsFromReaderAsync(Reader, limit); 90 | } 91 | } 92 | } 93 | 94 | 95 | 96 | public List RetriveClassesFromDatabase(MySqlConnection Connection, string Command, params object[] Arguments) 97 | { 98 | if (Connection.State != System.Data.ConnectionState.Open) Connection.Open(); 99 | using (MySqlCommand Command_ = new MySqlCommand(Command, Connection)) 100 | { 101 | for (int i = 0; i < Arguments.Length; i++) 102 | { 103 | if (Command.Contains($"{{{i}}}")) Command_.Parameters.AddWithValue($"{{{i}}}", Arguments[i]); 104 | if (Command.Contains($"@{i}")) Command_.Parameters.AddWithValue($"@{i}", Arguments[i]); 105 | } 106 | using (MySqlDataReader Reader = Command_.ExecuteReader()) 107 | { 108 | SQLConverter Converter = new SQLConverter(); 109 | return Converter.ReadClasses(Reader); 110 | } 111 | } 112 | } 113 | public List RetriveSQLTypesFromDatabase(MySqlConnection Connection, string Command, params object[] Arguments) 114 | { 115 | if (Connection.State != System.Data.ConnectionState.Open) Connection.Open(); 116 | using (MySqlCommand Command_ = new MySqlCommand(Command, Connection)) 117 | { 118 | for (int i = 0; i < Arguments.Length; i++) 119 | { 120 | if (Command.Contains($"{{{i}}}")) Command_.Parameters.AddWithValue($"{{{i}}}", Arguments[i]); 121 | if (Command.Contains($"@{i}")) Command_.Parameters.AddWithValue($"@{i}", Arguments[i]); 122 | } 123 | using (MySqlDataReader Reader = Command_.ExecuteReader()) 124 | { 125 | SQLConverter Converter = new SQLConverter(); 126 | return Converter.ReadSQLBaseTypes(Reader, IndexedHelper); 127 | } 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/ConnectionProviders/SingletonConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using MySql.Data.MySqlClient; 9 | 10 | namespace ShimmyMySherbet.MySQL.EF.Models.ConnectionProviders 11 | { 12 | /// 13 | /// Maintains a single working connection, and manages connection locking for using the same connection multithreaded. 14 | /// An updated revision of that will create a new connection if the connection instance breaks or disconnects. 15 | /// This provider will also lock the connection and block other threads accessing it while it is in use. You have to call or to release the lock on the connection. 16 | /// 17 | /// Warning: 18 | /// Trying to obtain a connection lock on a thread that already owns the connection lock will not cause a thread lock, 19 | /// but obtaining a lock on 1 thread then running work on another thread that also tries to obtain a lock will cause a connection lock. 20 | /// If you need more reliability over speed, you can use the to create a new connection all the time to avoid any thread locking issues. 21 | /// For backwards compatibility, you can still use that does not do any connection locking, but also does not ensure connection integrity. 22 | /// 23 | public class SingletonConnectionProvider 24 | { 25 | private string m_ConnectionString; 26 | public string ConnectionString => m_ConnectionString; 27 | private MySqlConnection m_ConnectionInstance { get; set; } 28 | private SemaphoreSlim m_Semaphore = new SemaphoreSlim(0); 29 | private int m_LockedBy = -1; 30 | public bool Connected => m_ConnectionInstance != null && m_ConnectionInstance.State == System.Data.ConnectionState.Open; 31 | 32 | public SingletonConnectionProvider(MySqlConnection connection) 33 | { 34 | m_ConnectionInstance = connection; 35 | m_ConnectionString = connection.ConnectionString; 36 | } 37 | 38 | public SingletonConnectionProvider(DatabaseSettings settings) 39 | { 40 | m_ConnectionString = settings.ToString(); 41 | } 42 | 43 | public SingletonConnectionProvider(string connectionString) 44 | { 45 | m_ConnectionString = connectionString; 46 | } 47 | 48 | public MySqlConnection GetConnection(bool autoOpen = true, bool forceNew = false) 49 | { 50 | if (forceNew) 51 | { 52 | var conn = new MySqlConnection(m_ConnectionString); 53 | if (autoOpen) 54 | { 55 | conn.Open(); 56 | } 57 | 58 | return conn; 59 | } 60 | else 61 | { 62 | // avoid thread locking 63 | if (Thread.CurrentThread.ManagedThreadId == m_LockedBy) 64 | { 65 | return m_ConnectionInstance; 66 | } 67 | m_Semaphore.Wait(); 68 | // Ensure connetcion is valid 69 | if (m_ConnectionInstance == null || m_ConnectionInstance.State == ConnectionState.Closed || m_ConnectionInstance.State == ConnectionState.Broken) 70 | { 71 | m_ConnectionInstance?.Dispose(); 72 | m_ConnectionInstance = GetConnection(forceNew: true); 73 | } 74 | m_LockedBy = Thread.CurrentThread.ManagedThreadId; 75 | return m_ConnectionInstance; 76 | } 77 | } 78 | 79 | public async Task GetConnectionAsync(bool autoOpen = true, bool forceNew = false) 80 | { 81 | if (forceNew) 82 | { 83 | var conn = new MySqlConnection(m_ConnectionString); 84 | if (autoOpen) 85 | { 86 | await conn.OpenAsync(); 87 | } 88 | 89 | return conn; 90 | } 91 | else 92 | { 93 | if (Thread.CurrentThread.ManagedThreadId == m_LockedBy) 94 | { 95 | return m_ConnectionInstance; 96 | } 97 | await m_Semaphore.WaitAsync(); 98 | // Ensure connetcion is valid 99 | if (m_ConnectionInstance == null || m_ConnectionInstance.State == ConnectionState.Closed || m_ConnectionInstance.State == ConnectionState.Broken) 100 | { 101 | m_ConnectionInstance?.Dispose(); 102 | m_ConnectionInstance = await GetConnectionAsync(forceNew: true); 103 | } 104 | m_LockedBy = Thread.CurrentThread.ManagedThreadId; 105 | return m_ConnectionInstance; 106 | } 107 | } 108 | 109 | public void ReleaseConnection(MySqlConnection connection) 110 | { 111 | if (connection != m_ConnectionInstance) 112 | { 113 | connection.Close(); 114 | } 115 | else 116 | { 117 | m_LockedBy = -1; 118 | m_Semaphore.Release(); 119 | } 120 | } 121 | 122 | public async Task ReleaseConnectionAsync(MySqlConnection connection) 123 | { 124 | if (connection != m_ConnectionInstance) 125 | { 126 | await connection.CloseAsync(); 127 | } 128 | else 129 | { 130 | m_LockedBy = -1; 131 | m_Semaphore.Release(); 132 | } 133 | } 134 | 135 | public void Open() 136 | { 137 | if (m_ConnectionInstance == null) 138 | { 139 | m_ConnectionInstance = new MySqlConnection(m_ConnectionString); 140 | m_ConnectionInstance.Open(); 141 | m_Semaphore.Release(); 142 | } 143 | } 144 | 145 | public async Task OpenAsync() 146 | { 147 | if (m_ConnectionInstance == null) 148 | { 149 | m_ConnectionInstance = new MySqlConnection(m_ConnectionString); 150 | await m_ConnectionInstance.OpenAsync(); 151 | m_Semaphore.Release(); 152 | } 153 | } 154 | 155 | public void Disconnect() 156 | { 157 | m_ConnectionInstance?.Close(); 158 | } 159 | 160 | public async Task DisconnectAsync() 161 | { 162 | await m_ConnectionInstance?.CloseAsync(); 163 | } 164 | 165 | public void Dispose() 166 | { 167 | m_ConnectionInstance.Dispose(); 168 | m_Semaphore.Dispose(); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Models/ConnectionProviders/ThreadedConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | using MySql.Data.MySqlClient; 2 | using ShimmyMySherbet.MySQL.EF.Models.Interfaces; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace ShimmyMySherbet.MySQL.EF.Models.ConnectionProviders 8 | { 9 | /// 10 | /// Maintains a connection for each thread that calls for one 11 | /// 12 | public class ThreadedConnectionProvider : IConnectionProvider 13 | { 14 | private string m_ConnectionString; 15 | 16 | public bool Connected => true; 17 | 18 | public string ConnectionString => m_ConnectionString; 19 | 20 | private Dictionary m_Connections = new Dictionary(); 21 | 22 | private List m_ConnectionList = new List(); 23 | 24 | private int m_ThreadID => Thread.CurrentThread.ManagedThreadId; 25 | 26 | public ThreadedConnectionProvider(MySqlConnection connection) 27 | { 28 | m_ConnectionString = connection.ConnectionString; 29 | m_Connections[m_ThreadID] = connection; 30 | } 31 | 32 | public ThreadedConnectionProvider(DatabaseSettings settings) 33 | { 34 | m_ConnectionString = settings.ToString(); 35 | } 36 | 37 | public ThreadedConnectionProvider(string connectionString) 38 | { 39 | m_ConnectionString = connectionString; 40 | } 41 | 42 | public void Disconnect() 43 | { 44 | lock (m_Connections) 45 | { 46 | if (m_Connections.ContainsKey(m_ThreadID)) 47 | { 48 | m_Connections[m_ThreadID].Close(); 49 | } 50 | } 51 | } 52 | 53 | public async Task DisconnectAsync() 54 | { 55 | MySqlConnection conn; 56 | lock (m_Connections) 57 | { 58 | if (m_Connections.ContainsKey(m_ThreadID)) 59 | { 60 | conn = m_Connections[m_ThreadID]; 61 | } 62 | else 63 | { 64 | return; 65 | } 66 | } 67 | await conn.CloseAsync(); 68 | } 69 | 70 | public void Dispose() 71 | { 72 | lock (m_ConnectionList) 73 | { 74 | lock (m_Connections) 75 | { 76 | foreach (var conn in m_ConnectionList) 77 | conn.Dispose(); 78 | 79 | m_Connections.Clear(); 80 | } 81 | } 82 | } 83 | 84 | public MySqlConnection GetConnection(bool autoOpen = true, bool forceNew = false) 85 | { 86 | if (!forceNew) 87 | { 88 | lock (m_Connections) 89 | { 90 | if (m_Connections.ContainsKey(m_ThreadID)) 91 | { 92 | var cCon = m_Connections[m_ThreadID]; 93 | if (cCon.State != System.Data.ConnectionState.Broken && cCon.State != System.Data.ConnectionState.Closed) 94 | { 95 | return cCon; 96 | } else 97 | { 98 | cCon?.Dispose(); 99 | } 100 | } 101 | } 102 | } 103 | 104 | var conn = new MySqlConnection(m_ConnectionString); 105 | lock (m_ConnectionList) 106 | { 107 | m_ConnectionList.Add(conn); 108 | } 109 | if (autoOpen) 110 | { 111 | conn.Open(); 112 | } 113 | 114 | lock (m_Connections) 115 | { 116 | m_Connections[m_ThreadID] = conn; 117 | } 118 | return conn; 119 | } 120 | 121 | public async Task GetConnectionAsync(bool autoOpen = true, bool forceNew = false) 122 | { 123 | if (!forceNew) 124 | { 125 | lock (m_Connections) 126 | { 127 | if (m_Connections.ContainsKey(m_ThreadID)) 128 | { 129 | var cCon = m_Connections[m_ThreadID]; 130 | 131 | if (cCon.State != System.Data.ConnectionState.Broken && cCon.State != System.Data.ConnectionState.Closed) 132 | { 133 | return cCon; 134 | } 135 | else 136 | { 137 | cCon?.Dispose(); 138 | } 139 | } 140 | } 141 | } 142 | 143 | var conn = new MySqlConnection(m_ConnectionString); 144 | lock (m_ConnectionList) 145 | { 146 | m_ConnectionList.Add(conn); 147 | } 148 | if (autoOpen) 149 | { 150 | await conn.OpenAsync(); 151 | } 152 | 153 | lock (m_Connections) 154 | { 155 | m_Connections[m_ThreadID] = conn; 156 | } 157 | return conn; 158 | } 159 | 160 | public void Open() 161 | { 162 | lock (m_Connections) 163 | { 164 | if (m_Connections.ContainsKey(m_ThreadID)) 165 | { 166 | m_ConnectionList.Remove(m_Connections[m_ThreadID]); 167 | m_Connections[m_ThreadID].Dispose(); 168 | } 169 | } 170 | 171 | var conn = new MySqlConnection(m_ConnectionString); 172 | lock (m_ConnectionList) 173 | { 174 | m_ConnectionList.Add(conn); 175 | } 176 | conn.Open(); 177 | 178 | lock (m_Connections) 179 | { 180 | m_Connections[m_ThreadID] = conn; 181 | } 182 | } 183 | 184 | public async Task OpenAsync() 185 | { 186 | lock (m_Connections) 187 | { 188 | if (m_Connections.ContainsKey(m_ThreadID)) 189 | { 190 | return; 191 | } 192 | } 193 | 194 | var conn = new MySqlConnection(m_ConnectionString); 195 | lock (m_ConnectionList) 196 | { 197 | m_ConnectionList.Add(conn); 198 | } 199 | await conn.OpenAsync(); 200 | 201 | lock (m_Connections) 202 | { 203 | m_Connections[m_ThreadID] = conn; 204 | } 205 | } 206 | 207 | public void ReleaseConnection(MySqlConnection connection) 208 | { 209 | } 210 | 211 | public Task ReleaseConnectionAsync(MySqlConnection connection) 212 | { 213 | return Task.CompletedTask; 214 | } 215 | } 216 | } -------------------------------------------------------------------------------- /ExampleApp/ExampleApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {91665F0E-10C8-47CD-BCF9-5D079B7ABDBB} 8 | Exe 9 | ExampleApp 10 | ExampleApp 11 | v4.8 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\BouncyCastle.1.8.5\lib\BouncyCastle.Crypto.dll 38 | 39 | 40 | ..\packages\Google.Protobuf.3.19.4\lib\net45\Google.Protobuf.dll 41 | 42 | 43 | ..\packages\K4os.Compression.LZ4.1.2.6\lib\net46\K4os.Compression.LZ4.dll 44 | 45 | 46 | ..\packages\K4os.Compression.LZ4.Streams.1.2.6\lib\net46\K4os.Compression.LZ4.Streams.dll 47 | 48 | 49 | ..\packages\K4os.Hash.xxHash.1.0.6\lib\net46\K4os.Hash.xxHash.dll 50 | 51 | 52 | ..\packages\MySql.Data.8.0.29\lib\net48\MySql.Data.dll 53 | 54 | 55 | ..\packages\SSH.NET.2016.1.0\lib\net40\Renci.SshNet.dll 56 | 57 | 58 | 59 | ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll 71 | 72 | 73 | 74 | ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll 75 | 76 | 77 | ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | ..\packages\MySql.Data.8.0.29\lib\net48\Ubiety.Dns.Core.dll 88 | 89 | 90 | ..\packages\MySql.Data.8.0.29\lib\net48\ZstdNet.dll 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | {71ae8410-534c-4f13-8af1-7dd7c6f8d7b2} 114 | ShimmyMySherbet.MySQL.EF 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | *- Backup*.rdl 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush personal settings 299 | .cr/personal 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | 336 | # Local History for Visual Studio 337 | .localhistory/ 338 | 339 | # BeatPulse healthcheck temp database 340 | healthchecksdb -------------------------------------------------------------------------------- /ExampleApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Threading.Tasks; 7 | using ExampleApp.Database; 8 | using ExampleApp.Database.Models; 9 | using Org.BouncyCastle.Bcpg; 10 | using ShimmyMySherbet.MySQL.EF.Models; 11 | using ShimmyMySherbet.MySQL.EF.Models.ConnectionProviders; 12 | 13 | namespace ExampleApp 14 | { 15 | public class Program 16 | { 17 | public static DatabaseManager Database; 18 | 19 | private static void Main(string[] args) 20 | { 21 | // Can be used in serialized configs, e.g., rocket plugin config 22 | var settings = new DatabaseSettings("127.0.0.1", "TestDatabase", "kn8hSzrg2OVhTWHN", "test"); 23 | // Different providers change how connections are managed. 24 | var provider = new SingleConnectionProvider(settings); // singleton provider 25 | // Database manager class that contains tables 26 | Database = new DatabaseManager(provider); 27 | 28 | Console.WriteLine($"Connected: {Database.Connect(out string fail)}"); // try to connect to database 29 | Console.WriteLine($"Status Message: {fail}"); // print user friendly error if failed 30 | Database.CheckSchema(); // Check Schema (ensure tables exist, if not, create them) 31 | Database.AutoUpdateInstanceKey = true; // Auto update class auto increment primary key on insert 32 | 33 | RunConsole().GetAwaiter().GetResult(); 34 | } 35 | 36 | private static void Help() 37 | { 38 | var ms = typeof(Program).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); 39 | foreach (var m in ms) 40 | { 41 | Console.WriteLine($"{m.Name}({string.Join(", ", m.GetParameters().Select(x => $"{x.ParameterType.Name} {x.Name}"))})"); 42 | } 43 | } 44 | 45 | private static async Task CreateUser(string username, string email) 46 | { 47 | var newUser = new UserAccount() 48 | { 49 | EmailAddress = email, 50 | Username = username 51 | }; 52 | await Database.Users.InsertAsync(newUser); 53 | // return user ID as allocated by the database 54 | return newUser.ID; 55 | } 56 | 57 | private static void ShowSchema(string table) 58 | { 59 | Console.WriteLine(Database.Client.QuerySingle($"SHOW CREATE TABLE `{table}`")); 60 | } 61 | 62 | private static async Task GrantPermission(ulong userID, string permission, ulong granter) 63 | { 64 | // gets user perms from table, or creates new one if user does not have any perms 65 | var perms = Database.Permissions.QuerySingle("SELECT * FROM @TABLE WHERE UserID=@0;", userID) 66 | ?? new UserPermissions() { UserID = userID }; 67 | 68 | if (!perms.Permissions.Any(x => x.PermissionID == permission)) 69 | { 70 | perms.Permissions.Add(new Permission() { PermissionID = permission, GrantedBy = granter }); 71 | 72 | await Database.Permissions.InsertUpdateAsync(perms); // Update existing or Insert new row 73 | } 74 | } 75 | 76 | private static async Task ShowBalances() 77 | { 78 | var balances = await Database.Balances.QueryAsync("SELECT * FROM @TABLE;"); 79 | foreach (var bal in balances) 80 | { 81 | Console.WriteLine($"[{bal.UserID}] ${bal.Balance}"); 82 | } 83 | } 84 | 85 | private static void ShowBalancesSync() 86 | { 87 | var balances = Database.Balances.Query("SELECT * FROM @TABLE;"); 88 | foreach (var bal in balances) 89 | { 90 | Console.WriteLine($"[{bal.UserID}] ${bal.Balance}"); 91 | } 92 | } 93 | 94 | private static ulong GetUserCountSync() 95 | { 96 | return Database.Users.GetRowCount(); 97 | } 98 | 99 | private static async Task GetUserCount() 100 | { 101 | return await Database.Users.GetRowCountAsync(); 102 | } 103 | 104 | private static async Task PostComment(ulong uid, ulong postid, string value) 105 | { 106 | var ent = new UserComment() 107 | { 108 | PostID = postid, 109 | UserID = uid, 110 | Content = value, 111 | Posted = DateTime.Now, 112 | Updated = DateTime.Now 113 | }; 114 | await Database.Comments.InsertUpdateAsync(ent); 115 | } 116 | 117 | 118 | private static async Task ShowComments(ulong postID) 119 | { 120 | var comments = await Database.Comments.QueryAsync("SELECT * FROM @TABLE WHERE PostID=@0;", postID); 121 | foreach (var c in comments) 122 | { 123 | Console.WriteLine($"PostID: {c.PostID}"); 124 | Console.WriteLine($"UserID: {c.UserID}"); 125 | Console.WriteLine($"Posted: {c.Posted.ToShortDateString()}"); 126 | if (c.Updated != null) 127 | { 128 | Console.WriteLine($"Updated: {c.Updated.Value.ToShortDateString()}"); 129 | } 130 | Console.WriteLine(); 131 | Console.WriteLine(c.Content); 132 | Console.WriteLine(); 133 | } 134 | 135 | } 136 | 137 | private static async Task ModifyBalance(ulong uid, double amount) 138 | { 139 | var bal = await Database.Balances.QuerySingleAsync($"SELECT * FROM @TABLE WHERE UserID=@0;", uid) 140 | ?? new UserBalance() { UserID = uid }; 141 | bal.Balance += amount; 142 | await Database.Balances.InsertUpdateAsync(bal); 143 | } 144 | 145 | #region "Test console code" 146 | 147 | private static async Task RunConsole() 148 | { 149 | var converter = new StringConverter(); 150 | var sw = new Stopwatch(); 151 | while (true) 152 | { 153 | Console.ForegroundColor = ConsoleColor.Green; // fancy 154 | Console.Write(" > "); 155 | Console.ForegroundColor = ConsoleColor.Cyan; 156 | var command = Console.ReadLine(); 157 | var targetMethod = command.Split(' ')[0]; 158 | var arguments = targetMethod.Length != command.Length ? command.Substring(targetMethod.Length + 1).Split(' ').ToArray() : new string[0]; 159 | Console.ForegroundColor = ConsoleColor.Red; 160 | var mInfo = typeof(Program).GetMethod(targetMethod, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.IgnoreCase); 161 | 162 | if (mInfo == null) 163 | { 164 | Console.WriteLine("Command not found."); 165 | continue; 166 | } 167 | 168 | var mParam = mInfo.GetParameters(); 169 | 170 | var param = new object[mParam.Length]; 171 | 172 | if (arguments.Length < mParam.Length) 173 | { 174 | Console.WriteLine($"Not enough arguments"); 175 | continue; 176 | } 177 | 178 | for (int i = 0; i < param.Length; i++) 179 | { 180 | if (mParam[i].ParameterType == typeof(ulong)) 181 | { 182 | param[i] = ulong.Parse(arguments[i]); 183 | } 184 | else if (mParam[i].ParameterType == typeof(double)) 185 | { 186 | param[i] = double.Parse(arguments[i]); 187 | } 188 | else if (mParam[i].ParameterType == typeof(decimal)) 189 | { 190 | param[i] = decimal.Parse(arguments[i]); 191 | } 192 | else 193 | { 194 | if (!converter.CanConvertTo(mParam[i].ParameterType) && mParam[i].ParameterType != typeof(ulong)) 195 | { 196 | Console.WriteLine($"Cant convert to {mParam[i].ParameterType.Name}"); 197 | continue; 198 | } 199 | param[i] = converter.ConvertTo(arguments[i], mParam[i].ParameterType); 200 | } 201 | } 202 | Console.ForegroundColor = ConsoleColor.Yellow; 203 | sw.Restart(); 204 | var ret = mInfo.Invoke(null, param); 205 | if (ret is Task tsk) 206 | { 207 | await tsk; 208 | sw.Stop(); 209 | Console.ForegroundColor = ConsoleColor.Green; 210 | Console.WriteLine($"Finished Async in {Math.Round(sw.ElapsedTicks / 10000f, 4)}ms"); 211 | 212 | // check if it's Type with result 213 | var f_result = ret.GetType().GetProperty("Result", BindingFlags.Public | BindingFlags.Instance); 214 | if (f_result != null) 215 | { 216 | var rValue = f_result.GetValue(ret); 217 | Console.WriteLine($"Async Retun Value: {(rValue == null ? "null" : rValue)}"); 218 | } 219 | else 220 | { 221 | Console.WriteLine($"No return value."); 222 | } 223 | } 224 | else 225 | { 226 | sw.Stop(); 227 | Console.ForegroundColor = ConsoleColor.Green; 228 | Console.WriteLine($"Finished in {Math.Round(sw.ElapsedTicks / 10000f, 4)}ms"); 229 | if (mInfo.ReturnType != typeof(void)) 230 | { 231 | Console.WriteLine($"Retun Value: {(ret == null ? "null" : ret)}"); 232 | } 233 | } 234 | } 235 | } 236 | 237 | #endregion "Test console code" 238 | 239 | 240 | private static async Task TestTypeConversion() 241 | { 242 | try 243 | { 244 | var r = await Database.Balances.QuerySingleAsync("SELECT COUNT(*) FROM @TABLE;"); 245 | Console.WriteLine($"BIGINT -> {typeof(T).Name} pass [{r}]"); 246 | } 247 | catch (Exception ex) 248 | { 249 | Console.WriteLine($"BIGINT -> {typeof(T).Name} fail: {ex.Message}"); 250 | } 251 | } 252 | 253 | private static async Task TestConversions() 254 | { 255 | Console.WriteLine("testing reads from BIGINT"); 256 | await TestTypeConversion(); 257 | await TestTypeConversion(); 258 | await TestTypeConversion(); 259 | await TestTypeConversion(); 260 | await TestTypeConversion(); 261 | await TestTypeConversion(); 262 | await TestTypeConversion(); 263 | await TestTypeConversion(); 264 | await TestTypeConversion(); 265 | await TestTypeConversion(); 266 | await TestTypeConversion(); 267 | Console.WriteLine("done."); 268 | } 269 | } 270 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Core/DatabaseTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using MySql.Data.MySqlClient; 5 | using ShimmyMySherbet.MySQL.EF.Models; 6 | using ShimmyMySherbet.MySQL.EF.Models.Exceptions; 7 | using ShimmyMySherbet.MySQL.EF.Models.Interfaces; 8 | 9 | namespace ShimmyMySherbet.MySQL.EF.Core 10 | { 11 | /// 12 | /// Provides methods to interact with a specific database table. 13 | /// Designed to be used in a class that inheits though can also work alone 14 | /// 15 | /// Table type model 16 | public class DatabaseTable : IDatabaseTable where T : class 17 | { 18 | public string TableName { get; protected set; } 19 | 20 | protected string RealTableName => $"`{TableName.Trim('`')}`"; 21 | 22 | protected MySQLEntityClient Client { get; set; } 23 | 24 | public DatabaseTable(string tableName) 25 | { 26 | if (tableName == null) 27 | { 28 | throw new ArgumentNullException("tableName"); 29 | } 30 | TableName = tableName; 31 | } 32 | 33 | public DatabaseTable(string tableName, MySQLEntityClient client) 34 | { 35 | if (tableName == null) 36 | { 37 | throw new ArgumentNullException("tableName"); 38 | } 39 | TableName = tableName; 40 | Client = client; 41 | } 42 | 43 | public DatabaseTable(string tableName, MySqlConnection connection) 44 | { 45 | if (tableName == null) 46 | { 47 | throw new ArgumentNullException("tableName"); 48 | } 49 | TableName = tableName; 50 | Client = new MySQLEntityClient(connection, true); 51 | } 52 | 53 | /// 54 | /// Recives the database client instance 55 | /// 56 | public virtual void SendClient(MySQLEntityClient client) 57 | { 58 | Client = client; 59 | } 60 | 61 | /// 62 | /// Creates a Bulk Inserter client. 63 | /// Use this when inserting large amounts of objects into the table, and where performance is critical. 64 | /// 65 | /// Max insert operations per transaction. If you get Max Packet Size errors, reduce this number. 66 | /// Bulk Inserter client for this table 67 | public IBulkInserter CreateInserter(int maxInsertsPerTransaction = 5000) 68 | { 69 | return new TransactionalBulkInserter(Client.ConnectionProvider.GetConnection(), TableName, maxInsertsPerTransaction); 70 | } 71 | 72 | /// 73 | /// Creates a Bulk Inserter client. 74 | /// Use this when inserting large amounts of objects into the table, and where performance is critical. 75 | /// 76 | /// Max insert operations per transaction. If you get Max Packet Size errors, reduce this number. 77 | /// Bulk Inserter client for this table 78 | public async Task> CreateInserterAsync(int maxInsertsPerTransaction = 5000) 79 | { 80 | return new TransactionalBulkInserter(await Client.ConnectionProvider.GetConnectionAsync(), TableName, maxInsertsPerTransaction); 81 | } 82 | 83 | /// 84 | /// Gets the number of rows in the table 85 | /// 86 | /// Row count 87 | public async Task GetRowCountAsync() 88 | { 89 | return await QuerySingleAsync("SELECT COUNT(*) FROM @TABLE;"); 90 | } 91 | 92 | /// 93 | /// Gets the number of rows in the table 94 | /// 95 | /// Row count 96 | public ulong GetRowCount() 97 | { 98 | return QuerySingle("SELECT COUNT(*) FROM @TABLE;"); 99 | } 100 | 101 | /// 102 | /// Checks if the table exists, and if it doesn't, creates the table. 103 | /// 104 | public virtual void CheckSchema() 105 | { 106 | if (Client == null) 107 | { 108 | throw new NoConnectionException("No database connection available. Use SendClient() or provide a connection at construction."); 109 | } 110 | 111 | if (!Client.TableExists(TableName)) 112 | { 113 | Client.CreateTable(TableName); 114 | } 115 | } 116 | 117 | /// 118 | /// Creates the database table if it doesn't exist. 119 | /// NOTE: CheckSchema() also does this, and if you are using a database manager using a call CheckSchema on that instead. 120 | /// 121 | public void CreateTableIfNotExists() => Client.CreateTableIfNotExists(TableName); 122 | 123 | /// 124 | /// Deletes the specified instance from the table. 125 | /// Requires the type to have at least one 126 | /// 127 | public void Delete(T obj) => Client.Delete(obj, TableName); 128 | 129 | /// 130 | /// Deletes the specified instance from the table. 131 | /// Requires the type to have at least one 132 | /// 133 | public async Task DeleteAsync(T obj) => await Client.DeleteAsync(obj, TableName); 134 | 135 | /// 136 | /// Inserts an object instance into the table 137 | /// 138 | public void Insert(T obj) => Client.Insert(obj, TableName); 139 | 140 | /// 141 | /// Inserts an object instance into the table 142 | /// 143 | public async Task InsertAsync(T obj) => await Client.InsertAsync(obj, TableName); 144 | 145 | /// 146 | /// If a row with the same key exists, this updates that row, if not, this will insert the object instead 147 | /// 148 | public void InsertUpdate(T obj) => Client.InsertUpdate(obj, TableName); 149 | 150 | /// 151 | /// If a row with the same key exists, this updates that row, if not, this will insert the object instead 152 | /// 153 | public async Task InsertUpdateAsync(T obj) => await Client.InsertUpdateAsync(obj, TableName); 154 | 155 | /// 156 | /// Selects a list of instances from the table 157 | /// 158 | /// The MySQL query command. Use @TABLE to refer to the table, and @0 - @99 as arguments. Arguments ar esecurly escaped and formatted. 159 | /// Arguments to escape and format into the command. 160 | public List Query(string command, params object[] args) => Client.Query(InsertTableName(command), args); 161 | 162 | /// 163 | /// Selects a list of instances from the table 164 | /// 165 | /// The MySQL query command. Use @TABLE to refer to the table, and @0 - @99 as arguments. Arguments ar esecurly escaped and formatted. 166 | /// Arguments to escape and format into the command. 167 | public List Query(string command, params object[] args) => Client.Query(InsertTableName(command), args); 168 | 169 | /// 170 | /// Selects a list of instances from the table 171 | /// 172 | /// The MySQL query command. Use @TABLE to refer to the table, and @0 - @99 as arguments. Arguments ar esecurly escaped and formatted. 173 | /// Arguments to escape and format into the command. 174 | public async Task> QueryAsync(string command, params object[] args) => await Client.QueryAsync(InsertTableName(command), args); 175 | 176 | /// 177 | /// Selects a list of instances from the table 178 | /// 179 | /// The MySQL query command. Use @TABLE to refer to the table, and @0 - @99 as arguments. Arguments ar esecurly escaped and formatted. 180 | /// Arguments to escape and format into the command. 181 | public async Task> QueryAsync(string command, params object[] args) => await Client.QueryAsync(InsertTableName(command), args); 182 | 183 | /// 184 | /// Selects a single object from the table 185 | /// 186 | /// The MySQL query command. Use @TABLE to refer to the table, and @0 - @99 as arguments. Arguments ar esecurly escaped and formatted. 187 | /// Arguments to escape and format into the command. 188 | public T QuerySingle(string command, params object[] args) => Client.QuerySingle(InsertTableName(command), args); 189 | 190 | /// 191 | /// Selects a single object from the table 192 | /// 193 | /// The MySQL query command. Use @TABLE to refer to the table, and @0 - @99 as arguments. Arguments ar esecurly escaped and formatted. 194 | /// Arguments to escape and format into the command. 195 | public O QuerySingle(string command, params object[] args) => Client.QuerySingle(InsertTableName(command), args); 196 | 197 | /// 198 | /// Selects a single object from the table 199 | /// 200 | /// The MySQL query command. Use @TABLE to refer to the table, and @0 - @99 as arguments. Arguments ar esecurly escaped and formatted. 201 | /// Arguments to escape and format into the command. 202 | public async Task QuerySingleAsync(string command, params object[] args) => await Client.QuerySingleAsync(InsertTableName(command), args); 203 | 204 | /// 205 | /// Selects a single object from the table 206 | /// 207 | /// The MySQL query command. Use @TABLE to refer to the table, and @0 - @99 as arguments. Arguments ar esecurly escaped and formatted. 208 | /// Arguments to escape and format into the command. 209 | public async Task QuerySingleAsync(string command, params object[] args) => await Client.QuerySingleAsync(InsertTableName(command), args); 210 | 211 | /// 212 | /// Executes a non query against the table 213 | /// 214 | /// The MySQL query command. Use @TABLE to refer to the table, and @0 - @99 as arguments. Arguments ar esecurly escaped and formatted. 215 | /// Arguments to escape and format into the command. 216 | /// Rows Modified 217 | public int ExecuteNonQuery(string command, params object[] args) => Client.ExecuteNonQuery(InsertTableName(command), args); 218 | 219 | /// 220 | /// Executes a non query against the table 221 | /// 222 | /// The MySQL query command. Use @TABLE to refer to the table, and @0 - @99 as arguments. Arguments ar esecurly escaped and formatted. 223 | /// Arguments to escape and format into the command. 224 | /// Rows Modified 225 | public async Task ExecuteNonQueryAsync(string command, params object[] args) => await Client.ExecuteNonQueryAsync(InsertTableName(command), args); 226 | 227 | /// 228 | /// Securely replaces @TABLE with the table name 229 | /// 230 | protected string InsertTableName(string command) 231 | { 232 | int i = 0; 233 | while (true) 234 | { 235 | var index = command.IndexOf("@table", 0, command.Length, StringComparison.InvariantCultureIgnoreCase); 236 | if (index == -1) 237 | { 238 | break; 239 | } 240 | command = command.Remove(index, 6).Insert(index, RealTableName); 241 | i++; 242 | if (i > 20) break; 243 | } 244 | return command; 245 | } 246 | } 247 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/EntityCommandBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using MySql.Data.MySqlClient; 9 | using ShimmyMySherbet.MySQL.EF.Models; 10 | using ShimmyMySherbet.MySQL.EF.Models.Exceptions; 11 | using ShimmyMySherbet.MySQL.EF.Models.Internals; 12 | using ShimmyMySherbet.MySQL.EF.Models.TypeModel; 13 | 14 | #pragma warning disable CA2100 15 | 16 | namespace ShimmyMySherbet.MySQL.EF.Internals 17 | { 18 | public class EntityCommandBuilder 19 | { 20 | private SQLTypeHelper m_TypeHelper = new SQLTypeHelper(); 21 | 22 | private static IDictionary> m_FieldCache = new ConcurrentDictionary>(); 23 | 24 | private static bool m_CacheEnabled { get; set; } = true; 25 | 26 | /// 27 | /// Toggles the use of cached class models that represent a type's fields and properties for SQL methods. 28 | /// 29 | public static void SetCacheEnabled(bool enabled) 30 | { 31 | m_CacheEnabled = enabled; 32 | } 33 | 34 | internal static List GetClassFields(Type model) 35 | { 36 | if (model == null) 37 | { 38 | throw new ArgumentNullException("model"); 39 | } 40 | 41 | if (m_CacheEnabled) 42 | { 43 | if (m_FieldCache == null) 44 | { 45 | m_FieldCache = new ConcurrentDictionary>(); 46 | } 47 | else if (m_FieldCache.ContainsKey(model)) 48 | { 49 | return m_FieldCache[model]; 50 | } 51 | } 52 | 53 | var fields = new List(); 54 | var index = 0; 55 | 56 | foreach (var field in model.GetFields()) 57 | { 58 | var f = new ClassField(field, index); 59 | 60 | if (!f.Meta.Ignore) 61 | { 62 | index++; 63 | fields.Add(f); 64 | } 65 | } 66 | 67 | if (model.GetCustomAttribute() == null) 68 | { 69 | foreach (var property in model.GetProperties()) 70 | { 71 | var p = new ClassProperty(property, index); 72 | 73 | if (!p.Meta.Ignore) 74 | { 75 | index++; 76 | fields.Add(p); 77 | } 78 | } 79 | } 80 | 81 | if (m_CacheEnabled) 82 | { 83 | m_FieldCache[model] = fields; 84 | } 85 | 86 | return fields; 87 | } 88 | 89 | internal static List GetClassFields() => GetClassFields(typeof(T)); 90 | 91 | internal static List GetClassFields(Func selector) => GetClassFields(typeof(T)).Where(selector).ToList(); 92 | 93 | public static MySqlCommand BuildCommand(string command, params object[] args) 94 | { 95 | MySqlCommand sqlCommand = new MySqlCommand(command); 96 | for (int i = 0; i < args.Length; i++) 97 | { 98 | if (command.Contains($"{{{i}}}")) sqlCommand.Parameters.AddWithValue($"{{{i}}}", args[i]); 99 | if (command.Contains($"@{i}")) sqlCommand.Parameters.AddWithValue($"@{i}", args[i]); 100 | } 101 | return sqlCommand; 102 | } 103 | 104 | public static MySqlCommand BuildCommand(MySqlConnection connection, string command, params object[] arguments) 105 | { 106 | MySqlCommand sqlCommand = new MySqlCommand(command, connection); 107 | for (int i = 0; i < arguments.Length; i++) 108 | { 109 | if (command.Contains($"{{{i}}}")) sqlCommand.Parameters.AddWithValue($"{{{i}}}", arguments[i]); 110 | if (command.Contains($"@{i}")) sqlCommand.Parameters.AddWithValue($"@{i}", arguments[i]); 111 | } 112 | return sqlCommand; 113 | } 114 | 115 | public static string BuildCommandContent(string command, int prefix, out PropertyList properties, params object[] args) 116 | { 117 | properties = new PropertyList(); 118 | for (int i = args.Length - 1; i > 0; i--) 119 | { 120 | if (command.Contains($"{{{i}}}")) 121 | { 122 | command = command.Replace($"{{{i}}}", $"@_{prefix}_{i}"); 123 | properties.Add($"@_{prefix}_{i}", args[i]); 124 | } 125 | if (command.Contains($"@{i}")) 126 | { 127 | command = command.Replace($"@{i}", $"@_{prefix}_{i}"); 128 | properties.Add($"@_{prefix}_{i}", args[i]); 129 | } 130 | } 131 | return command; 132 | } 133 | 134 | public static MySqlCommand BuildInsertCommand(T obj, string table, out List fields, MySqlConnection connection = null) 135 | { 136 | fields = GetClassFields(x => !x.ShouldOmit(obj) && !x.Meta.OmitOnInsert); 137 | string command = $"INSERT INTO `{table}` ({string.Join(", ", fields.CastEnumeration(x => x.SQLName))}) VALUES ({string.Join(", ", fields.CastEnumeration(x => $"@{x.FieldIndex}"))});"; 138 | MySqlCommand sqlCommand = (connection != null ? new MySqlCommand(command, connection) : new MySqlCommand(command)); 139 | foreach (var field in fields) 140 | sqlCommand.Parameters.AddWithValue($"@{field.FieldIndex}", field.GetValue(obj)); 141 | return sqlCommand; 142 | } 143 | 144 | public static string BuildInsertCommandContent(T obj, string table, int prefix, out PropertyList properties) 145 | { 146 | var fields = GetClassFields(x => !x.ShouldOmit(obj) && !x.Meta.OmitOnInsert); 147 | 148 | string command = $"INSERT INTO `{table}` ({string.Join(", ", fields.CastEnumeration(x => x.SQLName))}) VALUES ({string.Join(", ", fields.CastEnumeration(x => $"@{prefix}_{x.FieldIndex}"))});"; 149 | 150 | properties = new PropertyList(); 151 | foreach (var meta in fields) 152 | properties.Add($"@{prefix}_{meta.FieldIndex}", meta.GetValue(obj)); 153 | return command; 154 | } 155 | 156 | public static MySqlCommand BuildInsertUpdateCommand(T obj, string table, MySqlConnection Connection = null) 157 | { 158 | var fields = GetClassFields(x => !x.ShouldOmit(obj)); 159 | 160 | bool hasUpdatable = fields.Any(x => !x.Meta.OmitOnUpdate); 161 | 162 | string command = $"INSERT INTO `{table}` ({string.Join(", ", fields.Where(x => !x.Meta.OmitOnInsert).CastEnumeration(x => x.SQLName))}) VALUES ({string.Join(", ", fields.Where(x => !x.Meta.OmitOnInsert).CastEnumeration(x => $"@{x.FieldIndex}"))}) {(!hasUpdatable ? "" : $"ON DUPLICATE KEY UPDATE {string.Join(", ", fields.Where(x => !x.Meta.OmitOnUpdate).Select(x => $"`{x.Name}`=@{x.FieldIndex}"))};")};"; 163 | MySqlCommand sqlCommand = (Connection != null ? new MySqlCommand(command, Connection) : new MySqlCommand(command)); 164 | foreach (var meta in fields) 165 | sqlCommand.Parameters.AddWithValue($"@{meta.FieldIndex}", meta.GetValue(obj)); 166 | return sqlCommand; 167 | } 168 | 169 | public static string BuildInsertUpdateCommandContent(T obj, string table, int prefix, out PropertyList properties) 170 | { 171 | var fields = GetClassFields(x => !x.ShouldOmit(obj)); 172 | bool hasUpdatable = fields.Any(x => !x.Meta.OmitOnUpdate); 173 | 174 | string command = $"INSERT INTO `{table}` ({string.Join(", ", fields.Where(x=> !x.Meta.OmitOnInsert).CastEnumeration(x => x.SQLName))}) VALUES ({string.Join(", ", fields.Where(x => !x.Meta.OmitOnInsert).CastEnumeration(x => $"@{x.FieldIndex}"))}) {(!hasUpdatable ? "" : $"ON DUPLICATE KEY UPDATE {string.Join(", ", fields.Where(x => !x.Meta.OmitOnUpdate).Select(x => $"`{x.Name}`=@{x.FieldIndex}"))};")};"; 175 | properties = new PropertyList(); 176 | foreach (var meta in fields) 177 | properties.Add($"@{prefix}_{meta.FieldIndex}", meta.GetValue(obj)); 178 | return command; 179 | } 180 | 181 | internal static string BuildSelector(IEnumerable primaryKeys, object instance, out ParamObject[] parameters, int? prefix = null) 182 | { 183 | var count = primaryKeys.Count(); 184 | parameters = new ParamObject[count]; 185 | var selections = new string[count]; 186 | for (int i = 0; i < count; i++) 187 | { 188 | var key = primaryKeys.ElementAt(i); 189 | selections[i] = $"{key.SQLName}=@{(prefix != null ? $"{prefix}_" : "")}KEY{i}"; 190 | parameters[i] = new ParamObject($"@{(prefix != null ? $"{prefix}_" : "")}KEY{i}", key.GetValue(instance)); 191 | } 192 | 193 | return string.Join(" AND ", selections); 194 | } 195 | 196 | public static MySqlCommand BuildUpdateCommand(T obj, string table, MySqlConnection connection = null) 197 | { 198 | var fields = GetClassFields(x => !x.ShouldOmit(obj) && !x.Meta.OmitOnUpdate); 199 | 200 | var primaryKeys = fields.Where(x => x.Meta.IsPrimaryKey); 201 | 202 | if (!primaryKeys.Any()) 203 | { 204 | throw new NoPrimaryKeyException(); 205 | } 206 | 207 | string Command = $"UPDATE `{table}` SET {string.Join(", ", fields.CastEnumeration(x => $"{x.SQLName}=@{x.FieldIndex}"))} WHERE {BuildSelector(primaryKeys, obj, out var prm)};"; 208 | MySqlCommand sqlCommand = (connection != null ? new MySqlCommand(Command, connection) : new MySqlCommand(Command)); 209 | sqlCommand.Add(prm); 210 | foreach (var meta in fields) 211 | sqlCommand.Parameters.AddWithValue($"@{meta.FieldIndex}", meta.GetValue(obj)); 212 | return sqlCommand; 213 | } 214 | 215 | public static string BuildUpdateCommandContent(T obj, string table, int prefix, out PropertyList properties) 216 | { 217 | var fields = GetClassFields(x => !x.ShouldOmit(obj) && !x.Meta.OmitOnUpdate); 218 | 219 | var primaryKeys = fields.Where(x => x.Meta.IsPrimaryKey); 220 | 221 | if (!primaryKeys.Any()) 222 | { 223 | throw new NoPrimaryKeyException(); 224 | } 225 | 226 | string command = $"UPDATE `{table}` SET {string.Join(", ", fields.CastEnumeration(x => $"{x.SQLName}=@{prefix}_{x.FieldIndex}"))} WHERE {BuildSelector(primaryKeys, obj, out var prop, prefix)};"; 227 | properties = new PropertyList(); 228 | properties.Add(prop); 229 | 230 | foreach (var meta in fields) 231 | properties.Add($"@{prefix}_{meta.FieldIndex}", meta.GetValue(obj)); 232 | 233 | return command; 234 | } 235 | 236 | public static MySqlCommand BuildDeleteCommand(T obj, string table, MySqlConnection sqlConnection = null) 237 | { 238 | var fields = GetClassFields(x => !x.ShouldOmit(obj)); 239 | 240 | var primaryKeys = fields.Where(x => x.Meta.IsPrimaryKey); 241 | 242 | if (!primaryKeys.Any()) 243 | { 244 | throw new NoPrimaryKeyException(); 245 | } 246 | string command = $"DELETE FROM `{table}` WHERE {BuildSelector(primaryKeys, obj, out var prms)};"; 247 | MySqlCommand sqlCommand = (sqlConnection != null ? new MySqlCommand(command, sqlConnection) : new MySqlCommand(command)); 248 | sqlCommand.Add(prms); 249 | return sqlCommand; 250 | } 251 | 252 | public static string BuildDeleteCommandContent(T obj, string table, int prefix, out PropertyList properties) 253 | { 254 | var fields = GetClassFields(x => !x.ShouldOmit(obj)); 255 | 256 | var primaryKeys = fields.Where(x => x.Meta.IsPrimaryKey); 257 | 258 | if (!primaryKeys.Any()) 259 | { 260 | throw new NoPrimaryKeyException(); 261 | } 262 | string command = $"DELETE FROM `{table}` WHERE {BuildSelector(primaryKeys, obj, out var prms, prefix)};"; 263 | 264 | properties = new PropertyList(); 265 | properties.Add(prms); 266 | 267 | return command; 268 | } 269 | 270 | public MySqlCommand BuildCreateTableCommand(string TableName, MySqlConnection Connection = null) 271 | { 272 | List fields = new List(); 273 | string dbEngine = "InnoDB"; 274 | if (Attribute.IsDefined(typeof(T), typeof(SQLDatabaseEngine))) 275 | { 276 | Attribute attrib = Attribute.GetCustomAttribute(typeof(T), typeof(SQLDatabaseEngine)); 277 | dbEngine = ((SQLDatabaseEngine)attrib).DatabaseEngine; 278 | } 279 | 280 | foreach (var field in GetClassFields()) 281 | { 282 | if (fields.Where(x => string.Equals(x.Name, field.SQLName, StringComparison.InvariantCultureIgnoreCase)).Count() == 0) 283 | { 284 | var bField = new SQLBuildField() 285 | { 286 | AutoIncrement = field.Meta.AutoIncrement, 287 | Indexed = field.AttributeDefined(), 288 | Name = field.SQLName, 289 | Null = field.Meta.DBNull, 290 | PrimaryKey = field.Meta.IsPrimaryKey, 291 | Unique = field.Meta.Unique, 292 | OverrideType = field.OverrideType 293 | }; 294 | 295 | if (field.OverrideType != null) 296 | { 297 | bField.Type = field.OverrideType; 298 | } else 299 | { 300 | bField.Type = m_TypeHelper.GetSQLTypeIndexed(field.FieldType); 301 | } 302 | 303 | 304 | var def = field.GetAttribute(); 305 | if (def != null) 306 | { 307 | bField.Default = def.DefaultValue; 308 | } 309 | 310 | if (bField.Type == null) 311 | { 312 | throw new SQLIncompatableTypeException(bField.Name); 313 | } 314 | 315 | fields.Add(bField); 316 | } 317 | } 318 | 319 | StringBuilder commandBuilder = new StringBuilder(); 320 | commandBuilder.AppendLine($"CREATE TABLE `{MySqlHelper.EscapeString(TableName)}` ("); 321 | List bodyParams = new List(); 322 | List defaults = new List(); 323 | var primaryKeys = new List(); 324 | 325 | foreach (SQLBuildField field in fields) 326 | { 327 | bodyParams.Add($" `{field.Name}` {field.SQLRepresentation} {(field.Null ? "NULL" : "NOT NULL")}{(field.Default != null ? $" DEFAULT @DEF{defaults.Count}" : "")}{(field.AutoIncrement ? " AUTO_INCREMENT" : "")}"); 328 | 329 | if (field.Default != null) defaults.Add(field.Default); 330 | //if (field.PrimaryKey) 331 | // bodyParams.Add($" PRIMARY KEY (`{field.Name}`)"); 332 | 333 | if (field.PrimaryKey) 334 | primaryKeys.Add(field); 335 | 336 | if (field.Unique) 337 | bodyParams.Add($" UNIQUE `{field.Name}_Unique` (`{field.Name}`)"); 338 | if (field.Indexed) 339 | bodyParams.Add($" INDEX `{field.Name}_INDEX` (`{field.Name}`)"); 340 | } 341 | 342 | if (primaryKeys.Any()) 343 | { 344 | bodyParams.Add($" PRIMARY KEY ({string.Join(", ", primaryKeys.Select(x => $"`{x.Name}`"))})"); 345 | } 346 | 347 | commandBuilder.AppendLine(string.Join(",\n", bodyParams)); 348 | commandBuilder.Append($") ENGINE = {dbEngine}"); 349 | 350 | var charset = typeof(T).GetCustomAttribute(); 351 | if (charset == null || charset.CharSet == SQLCharSet.ServerDefault) 352 | { 353 | commandBuilder.Append(';'); 354 | } else 355 | { 356 | commandBuilder.Append($" DEFAULT CHARSET={charset.CharSetName};"); 357 | } 358 | 359 | MySqlCommand sqlCommand = (Connection != null ? new MySqlCommand(commandBuilder.ToString(), Connection) : new MySqlCommand(commandBuilder.ToString())); 360 | 361 | for(int i = 0; i < defaults.Count; i++) 362 | { 363 | var defaultValue = defaults[i]; 364 | sqlCommand.Parameters.AddWithValue($"@DEF{i}", defaultValue); 365 | } 366 | 367 | return sqlCommand; 368 | } 369 | } 370 | } -------------------------------------------------------------------------------- /ShimmyMySherbet.MySQLEntityFramework/Internals/SQLConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Threading.Tasks; 6 | using MySql.Data.MySqlClient; 7 | using ShimmyMySherbet.MySQL.EF.Models.Exceptions; 8 | using ShimmyMySherbet.MySQL.EF.Models.Internals; 9 | using ShimmyMySherbet.MySQL.EF.Models.TypeModel; 10 | 11 | namespace ShimmyMySherbet.MySQL.EF.Internals 12 | { 13 | public class SQLConverter 14 | { 15 | public SQLTypeHelper TypeHelper; 16 | 17 | public List ReadModelsFromReader(DbDataReader Reader, int limit = -1) 18 | { 19 | if (typeof(T).IsPrimitive || typeof(T) == typeof(string) || TypeHelper != null && TypeHelper.GetSQLTypeIndexed(typeof(T)) != null) 20 | { 21 | return ReadSQLBaseTypesUnsafe(Reader, limit); 22 | } 23 | else 24 | { 25 | return ReadClasses(Reader, limit); 26 | } 27 | } 28 | 29 | public async Task> ReadModelsFromReaderAsync(DbDataReader Reader, int limit = -1) 30 | { 31 | if (typeof(T).IsPrimitive || typeof(T) == typeof(string) || TypeHelper != null && TypeHelper.GetSQLTypeIndexed(typeof(T)) != null) 32 | { 33 | return await ReadSQLBaseTypesUnsafeAsync(Reader, limit); 34 | } 35 | else 36 | { 37 | return await ReadClassesAsync(Reader, limit); 38 | } 39 | } 40 | 41 | public List ReadSQLBaseTypes(MySqlDataReader Reader, SQLTypeHelper Helper, int limit = -1) 42 | { 43 | SQLType Type = Helper.GetSQLTypeIndexed(typeof(T)); 44 | if (Type == null) throw new SQLIncompatableTypeException(); 45 | return ReadSQLBaseTypesUnsafe(Reader, limit); 46 | } 47 | 48 | public List ReadSQLBaseTypesUnsafe(IDataReader Reader, int limit = -1) 49 | { 50 | List Entries = new List(); 51 | int CompatableColumn = -1; 52 | bool IsNumeric = SQLTypeHelper.NumericType(typeof(T)); 53 | for (int i = 0; i < Reader.FieldCount; i++) 54 | { 55 | Type SQLType = Reader.GetFieldType(i); 56 | if (typeof(T).IsAssignableFrom(SQLType)) 57 | { 58 | CompatableColumn = i; 59 | } 60 | else if (SQLTypeHelper.CanCastEquivilant(SQLType, typeof(T)) || (IsNumeric && SQLTypeHelper.NumericType(SQLType))) 61 | { 62 | CompatableColumn = i; 63 | } 64 | } 65 | if (CompatableColumn == -1) 66 | { 67 | if (Reader.FieldCount == 0) 68 | { 69 | CompatableColumn = 0; 70 | } 71 | else 72 | { 73 | var names = new List(); 74 | for (int i = 0; i < Reader.FieldCount; i++) 75 | names.Add(Reader.GetName(i)); 76 | throw new SQLIncompatableTypeException(string.Join(", ", names), typeof(T).Name); 77 | } 78 | } 79 | 80 | var typeReader = GetTypeReader(typeof(T)); 81 | 82 | bool checkLimit = limit != -1; 83 | int count = 0; 84 | 85 | while (Reader.Read()) 86 | { 87 | count++; 88 | 89 | try 90 | { 91 | Entries.Add((T)typeReader(Reader, CompatableColumn)); 92 | } 93 | catch (SQLConversionFailedException) 94 | { 95 | throw new SQLInvalidCastException(Reader.GetName(CompatableColumn), Reader.GetDataTypeName(CompatableColumn), typeof(T).Name); 96 | } 97 | catch (InvalidCastException) 98 | { 99 | throw new SQLInvalidCastException(Reader.GetName(CompatableColumn), Reader.GetDataTypeName(CompatableColumn), typeof(T).Name); 100 | } 101 | if (checkLimit && count >= limit) break; 102 | } 103 | return Entries; 104 | } 105 | 106 | public async Task> ReadSQLBaseTypesUnsafeAsync(DbDataReader Reader, int limit = -1) 107 | { 108 | List Entries = new List(); 109 | int CompatableColumn = -1; 110 | bool IsNumeric = SQLTypeHelper.NumericType(typeof(T)); 111 | for (int i = 0; i < Reader.FieldCount; i++) 112 | { 113 | Type SQLType = Reader.GetFieldType(i); 114 | if (typeof(T).IsAssignableFrom(SQLType)) 115 | { 116 | CompatableColumn = i; 117 | } 118 | else if (SQLTypeHelper.CanCastEquivilant(SQLType, typeof(T)) || (IsNumeric && SQLTypeHelper.NumericType(SQLType))) 119 | { 120 | CompatableColumn = i; 121 | } 122 | } 123 | if (CompatableColumn == -1) 124 | { 125 | if (Reader.FieldCount == 0) 126 | { 127 | CompatableColumn = 0; 128 | } 129 | else 130 | { 131 | var names = new List(); 132 | for (int i = 0; i < Reader.FieldCount; i++) 133 | names.Add(Reader.GetName(i)); 134 | throw new SQLIncompatableTypeException(string.Join(", ", names), typeof(T).Name); 135 | } 136 | } 137 | 138 | var typeReader = GetTypeReader(typeof(T)); 139 | bool checkLimit = limit != -1; 140 | int count = 0; 141 | 142 | while (await Reader.ReadAsync()) 143 | { 144 | count++; 145 | 146 | try 147 | { 148 | Entries.Add((T)typeReader(Reader, CompatableColumn)); 149 | } 150 | catch (SQLConversionFailedException) 151 | { 152 | throw new SQLInvalidCastException(Reader.GetName(CompatableColumn), Reader.GetDataTypeName(CompatableColumn), typeof(T).Name); 153 | } 154 | catch (InvalidCastException) 155 | { 156 | throw new SQLInvalidCastException(Reader.GetName(CompatableColumn), Reader.GetDataTypeName(CompatableColumn), typeof(T).Name); 157 | } 158 | if (checkLimit && count >= limit) break; 159 | } 160 | return Entries; 161 | } 162 | 163 | public List ReadClasses(IDataReader Reader, int limit = -1) 164 | { 165 | var BaseFields = new Dictionary(StringComparer.InvariantCultureIgnoreCase); 166 | 167 | foreach (var field in EntityCommandBuilder.GetClassFields()) 168 | { 169 | if (!BaseFields.ContainsKey(field.SQLName)) 170 | { 171 | BaseFields[field.SQLName] = field; 172 | } 173 | } 174 | 175 | var SQLFields = new Dictionary(StringComparer.InvariantCultureIgnoreCase); 176 | for (int i = 0; i < Reader.FieldCount; i++) 177 | { 178 | Type SQLType = Reader.GetFieldType(i); 179 | string Name = Reader.GetName(i); 180 | SQLFields.Add(Name, new SQLFieldReferance(i, Name, SQLType)); 181 | } 182 | 183 | List Result = new List(); 184 | bool checkLimit = limit != -1; 185 | int count = 0; 186 | 187 | while (Reader.Read()) 188 | { 189 | count++; 190 | T NewObject = Activator.CreateInstance(); 191 | foreach (var referance in SQLFields.Values) 192 | { 193 | if (BaseFields.ContainsKey(referance.Name)) 194 | { 195 | var field = BaseFields[referance.Name]; 196 | 197 | var value = field.Reader(Reader, referance.Index); 198 | 199 | field.SetValue(NewObject, value); 200 | } 201 | } 202 | Result.Add(NewObject); 203 | if (checkLimit && count >= limit) break; 204 | } 205 | return Result; 206 | } 207 | 208 | private static object GetDefault(Type type) 209 | { 210 | if (type.IsValueType) 211 | { 212 | return Activator.CreateInstance(type); 213 | } 214 | return null; 215 | } 216 | 217 | public static TypeReader GetTypeReader(Type type) 218 | { 219 | var rawType = type; 220 | var nullableUnderlying = Nullable.GetUnderlyingType(type); 221 | var isNullable = nullableUnderlying != null; 222 | 223 | if (isNullable) 224 | { 225 | type = nullableUnderlying; 226 | } 227 | 228 | if (type == typeof(string)) 229 | { 230 | return (reader, index) => 231 | { 232 | if (reader.IsDBNull(index)) 233 | { 234 | return GetDefault(rawType); 235 | } 236 | return reader.GetString(index); 237 | }; 238 | } 239 | else if (type == typeof(bool)) 240 | { 241 | return (reader, index) => 242 | { 243 | if (reader.IsDBNull(index)) 244 | { 245 | return GetDefault(rawType); 246 | } 247 | return reader.GetBoolean(index); 248 | }; 249 | } 250 | else if (type == typeof(byte)) 251 | { 252 | return (reader, index) => 253 | { 254 | if (reader.IsDBNull(index)) 255 | { 256 | return GetDefault(rawType); 257 | } 258 | return reader.GetByte(index); 259 | }; 260 | } 261 | else if (type == typeof(char)) 262 | { 263 | return (reader, index) => 264 | { 265 | if (reader.IsDBNull(index)) 266 | { 267 | return GetDefault(rawType); 268 | } 269 | return reader.GetChar(index); 270 | }; 271 | } 272 | else if (type == typeof(DateTime)) 273 | { 274 | return (reader, index) => 275 | { 276 | if (reader.IsDBNull(index)) 277 | { 278 | return GetDefault(rawType); 279 | } 280 | return reader.GetDateTime(index); 281 | }; 282 | } 283 | else if (type == typeof(decimal)) 284 | { 285 | return (reader, index) => 286 | { 287 | if (reader.IsDBNull(index)) 288 | { 289 | return GetDefault(rawType); 290 | } 291 | return reader.GetDecimal(index); 292 | }; 293 | } 294 | else if (type == typeof(double)) 295 | { 296 | return (reader, index) => 297 | { 298 | if (reader.IsDBNull(index)) 299 | { 300 | return GetDefault(rawType); 301 | } 302 | return reader.GetDouble(index); 303 | }; 304 | } 305 | else if (type == typeof(float)) 306 | { 307 | return (reader, index) => 308 | { 309 | if (reader.IsDBNull(index)) 310 | { 311 | return GetDefault(rawType); 312 | } 313 | return reader.GetFloat(index); 314 | }; 315 | } 316 | else if (type == typeof(Guid)) 317 | { 318 | return (reader, index) => 319 | { 320 | if (reader.IsDBNull(index)) 321 | { 322 | return GetDefault(rawType); 323 | } 324 | return reader.GetGuid(index); 325 | }; 326 | } 327 | else if (type == typeof(short)) 328 | { 329 | return (reader, index) => 330 | { 331 | if (reader.IsDBNull(index)) 332 | { 333 | return GetDefault(rawType); 334 | } 335 | return reader.GetInt16(index); 336 | }; 337 | } 338 | else if (type == typeof(int)) 339 | { 340 | return (reader, index) => 341 | { 342 | if (reader.IsDBNull(index)) 343 | { 344 | return GetDefault(rawType); 345 | } 346 | return reader.GetInt32(index); 347 | }; 348 | } 349 | else if (type == typeof(long)) 350 | { 351 | return (reader, index) => 352 | { 353 | if (reader.IsDBNull(index)) 354 | { 355 | return GetDefault(rawType); 356 | } 357 | return reader.GetInt64(index); 358 | }; 359 | } 360 | 361 | return (reader, index) => 362 | { 363 | var obj = reader.GetValue(index); 364 | 365 | if (obj == null) 366 | { 367 | return GetDefault(rawType); 368 | } 369 | 370 | if (type.IsAssignableFrom(obj.GetType())) 371 | { 372 | return obj; 373 | } 374 | 375 | try 376 | { 377 | var ob = Convert.ChangeType(obj, type); 378 | return ob; 379 | } 380 | catch (InvalidCastException) 381 | { 382 | throw new SQLConversionFailedException(index, type); 383 | } 384 | }; 385 | } 386 | 387 | /// 388 | /// Attempts to read a type from the reader using it's implemented conversion methods. 389 | /// If the requested type isn't implemented for the reader, an IConversion cast is attempted instead 390 | /// 391 | /// True if the item was supported and read 392 | /// Instance or null 393 | [Obsolete("Use GetTypeReader instead")] 394 | private static object TryRead(IDataReader reader, Type type, int index, out bool read) 395 | { 396 | if (type == typeof(string)) 397 | { 398 | read = true; 399 | return reader.GetString(index); 400 | } 401 | else if (type == typeof(bool)) 402 | { 403 | read = true; 404 | return reader.GetBoolean(index); 405 | } 406 | else if (type == typeof(byte)) 407 | { 408 | read = true; 409 | return reader.GetByte(index); 410 | } 411 | else if (type == typeof(char)) 412 | { 413 | read = true; 414 | return reader.GetChar(index); 415 | } 416 | else if (type == typeof(DateTime)) 417 | { 418 | read = true; 419 | return reader.GetDateTime(index); 420 | } 421 | else if (type == typeof(decimal)) 422 | { 423 | read = true; 424 | return reader.GetDecimal(index); 425 | } 426 | else if (type == typeof(double)) 427 | { 428 | read = true; 429 | return reader.GetDouble(index); 430 | } 431 | else if (type == typeof(float)) 432 | { 433 | read = true; 434 | return reader.GetFloat(index); 435 | } 436 | else if (type == typeof(Guid)) 437 | { 438 | read = true; 439 | return reader.GetGuid(index); 440 | } 441 | else if (type == typeof(short)) 442 | { 443 | read = true; 444 | return reader.GetInt16(index); 445 | } 446 | else if (type == typeof(int)) 447 | { 448 | read = true; 449 | return reader.GetInt32(index); 450 | } 451 | else if (type == typeof(long)) 452 | { 453 | read = true; 454 | return reader.GetInt64(index); 455 | } 456 | 457 | var obj = reader.GetValue(index); 458 | 459 | try 460 | { 461 | var ob = Convert.ChangeType(obj, type); 462 | read = true; 463 | return ob; 464 | } 465 | catch (InvalidCastException) 466 | { 467 | read = false; 468 | } 469 | 470 | return null; 471 | } 472 | 473 | public async Task> ReadClassesAsync(DbDataReader Reader, int limit = -1) 474 | { 475 | var BaseFields = new Dictionary(StringComparer.InvariantCultureIgnoreCase); 476 | foreach (var field in EntityCommandBuilder.GetClassFields()) 477 | { 478 | if (!BaseFields.ContainsKey(field.SQLName)) 479 | { 480 | BaseFields[field.SQLName] = field; 481 | } 482 | } 483 | 484 | Dictionary SQLFields = new Dictionary(StringComparer.InvariantCultureIgnoreCase); 485 | for (int i = 0; i < Reader.FieldCount; i++) 486 | { 487 | Type SQLType = Reader.GetFieldType(i); 488 | string Name = Reader.GetName(i); 489 | SQLFields.Add(Name, new SQLFieldReferance(i, Name, SQLType)); 490 | } 491 | bool checkLimit = limit != -1; 492 | int count = 0; 493 | 494 | 495 | 496 | List Result = new List(); 497 | while (await Reader.ReadAsync()) 498 | { 499 | count++; 500 | T NewObject = Activator.CreateInstance(); 501 | foreach (SQLFieldReferance Referance in SQLFields.Values) 502 | { 503 | if (BaseFields.ContainsKey(Referance.Name)) 504 | { 505 | var field = BaseFields[Referance.Name]; 506 | var obj = field.Reader(Reader, Referance.Index); 507 | 508 | field.SetValue(NewObject, obj); 509 | } 510 | } 511 | Result.Add(NewObject); 512 | if (checkLimit && count >= limit) break; 513 | } 514 | return Result; 515 | } 516 | } 517 | } --------------------------------------------------------------------------------