Improved MySqlManagedConnection retry logic. Added new MySqlRowIndex attribute to specify the indexes for a given row. Improved documentation and the example program. Bumped version number to 1.0.5

This commit is contained in:
MattMo 2023-01-31 16:58:19 -08:00
parent 74ffdebee3
commit c4c39bfc46
6 changed files with 224 additions and 71 deletions

View File

@ -6,6 +6,8 @@ namespace MontoyaTech.MySqlPlus.Example
public class Program public class Program
{ {
[MySqlRow("cars")] [MySqlRow("cars")]
[MySqlRowIndex("make_model", "model", "year")]
[MySqlRowIndex("year", "year")]
public class Car public class Car
{ {
[MySqlColumn(Id = true, Name = "id", PrimaryKey = true, AutoIncrement = true, Nullable = false)] [MySqlColumn(Id = true, Name = "id", PrimaryKey = true, AutoIncrement = true, Nullable = false)]

View File

@ -23,13 +23,13 @@ namespace MontoyaTech.MySqlPlus
public static void Insert<T>(this MySqlCommand command, T row) public static void Insert<T>(this MySqlCommand command, T row)
{ {
//Get the type of T //Get the type of T
var type = typeof(T); var rowType = typeof(T);
//Get the row information. //Get the row information.
var rowAttribute = type.GetCustomAttribute<MySqlRow>(); var rowAttribute = rowType.GetCustomAttribute<MySqlRow>();
//Get all the fields. //Get all the fields.
var fields = type.GetFields(); var fields = rowType.GetFields();
if (fields == null || fields.Length == 0) if (fields == null || fields.Length == 0)
throw new Exception("Found no public fields on given row."); throw new Exception("Found no public fields on given row.");
@ -38,7 +38,7 @@ namespace MontoyaTech.MySqlPlus
var builder = new StringBuilder(); var builder = new StringBuilder();
//Write the insert section. //Write the insert section.
builder.Append($"INSERT INTO `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? type.Name : rowAttribute.Name)}` "); builder.Append($"INSERT INTO `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? rowType.Name : rowAttribute.Name)}` ");
//Write the set values section. //Write the set values section.
builder.Append("SET "); builder.Append("SET ");
@ -79,13 +79,13 @@ namespace MontoyaTech.MySqlPlus
public static void Update<T>(this MySqlCommand command, T row) public static void Update<T>(this MySqlCommand command, T row)
{ {
//Get the type of T //Get the type of T
var type = typeof(T); var rowType = typeof(T);
//Get the row information. //Get the row information.
var rowAttribute = type.GetCustomAttribute<MySqlRow>(); var rowAttribute = rowType.GetCustomAttribute<MySqlRow>();
//Get all the fields. //Get all the fields.
var fields = type.GetFields(); var fields = rowType.GetFields();
if (fields == null || fields.Length == 0) if (fields == null || fields.Length == 0)
throw new Exception("Found no public fields on given row."); throw new Exception("Found no public fields on given row.");
@ -94,7 +94,7 @@ namespace MontoyaTech.MySqlPlus
var builder = new StringBuilder(); var builder = new StringBuilder();
//Write the update section //Write the update section
builder.Append($"UPDATE `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? type.Name : rowAttribute.Name)}` "); builder.Append($"UPDATE `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? rowType.Name : rowAttribute.Name)}` ");
//Write the set values section //Write the set values section
builder.Append("SET "); builder.Append("SET ");
@ -150,17 +150,17 @@ namespace MontoyaTech.MySqlPlus
public static void Get<T>(this MySqlCommand command, ulong id) public static void Get<T>(this MySqlCommand command, ulong id)
{ {
//Get the type of T //Get the type of T
var type = typeof(T); var rowType = typeof(T);
//Get the row information. //Get the row information.
var rowAttribute = type.GetCustomAttribute<MySqlRow>(); var rowAttribute = rowType.GetCustomAttribute<MySqlRow>();
//Get the id field //Get the id field
if (!type.GetMySqlId(out FieldInfo idField, out MySqlColumn idColumn)) if (!rowType.GetMySqlId(out FieldInfo idField, out MySqlColumn idColumn))
throw new Exception("Failed to find the id column on the given row."); throw new Exception("Failed to find the id column on the given row.");
//Set the command text. //Set the command text.
command.CommandText = $"SELECT * FROM `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? type.Name : rowAttribute.Name)}` WHERE `{(string.IsNullOrWhiteSpace(idColumn.Name) ? idField.Name : idColumn.Name)}`=@id LIMIT 1"; command.CommandText = $"SELECT * FROM `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? rowType.Name : rowAttribute.Name)}` WHERE `{(string.IsNullOrWhiteSpace(idColumn.Name) ? idField.Name : idColumn.Name)}`=@id LIMIT 1";
//Add the id parameter. //Add the id parameter.
command.Parameters.AddWithValue("@id", id); command.Parameters.AddWithValue("@id", id);
@ -174,13 +174,13 @@ namespace MontoyaTech.MySqlPlus
public static void GetAll<T>(this MySqlCommand command) public static void GetAll<T>(this MySqlCommand command)
{ {
//Get the type of T //Get the type of T
var type = typeof(T); var rowType = typeof(T);
//Get the row information. //Get the row information.
var rowAttribute = type.GetCustomAttribute<MySqlRow>(); var rowAttribute = rowType.GetCustomAttribute<MySqlRow>();
//Set the command text. //Set the command text.
command.CommandText = $"SELECT * FROM `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? type.Name : rowAttribute.Name)}`"; command.CommandText = $"SELECT * FROM `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? rowType.Name : rowAttribute.Name)}`";
} }
/// <summary> /// <summary>
@ -193,16 +193,16 @@ namespace MontoyaTech.MySqlPlus
public static void Delete<T>(this MySqlCommand command, T row) public static void Delete<T>(this MySqlCommand command, T row)
{ {
//Get the type of T //Get the type of T
var type = typeof(T); var rowType = typeof(T);
//Get the row information. //Get the row information.
var rowAttribute = type.GetCustomAttribute<MySqlRow>(); var rowAttribute = rowType.GetCustomAttribute<MySqlRow>();
//Start building the query. //Start building the query.
var builder = new StringBuilder(); var builder = new StringBuilder();
//Write the delete from section //Write the delete from section
builder.Append($"DELETE FROM `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? type.Name : rowAttribute.Name)}` "); builder.Append($"DELETE FROM `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? rowType.Name : rowAttribute.Name)}` ");
//Get the id the column and field info //Get the id the column and field info
if (!row.GetMySqlId(out FieldInfo idField, out MySqlColumn idColumn)) if (!row.GetMySqlId(out FieldInfo idField, out MySqlColumn idColumn))
@ -227,19 +227,19 @@ namespace MontoyaTech.MySqlPlus
public static void Delete<T>(this MySqlCommand command, ulong id) public static void Delete<T>(this MySqlCommand command, ulong id)
{ {
//Get the type of T //Get the type of T
var type = typeof(T); var rowType = typeof(T);
//Get the row information. //Get the row information.
var rowAttribute = type.GetCustomAttribute<MySqlRow>(); var rowAttribute = rowType.GetCustomAttribute<MySqlRow>();
//Start building the query. //Start building the query.
var builder = new StringBuilder(); var builder = new StringBuilder();
//Write the delete from section //Write the delete from section
builder.Append($"DELETE FROM `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? type.Name : rowAttribute.Name)}` "); builder.Append($"DELETE FROM `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? rowType.Name : rowAttribute.Name)}` ");
//Get the id the column and field info //Get the id the column and field info
if (!type.GetMySqlId(out FieldInfo idField, out MySqlColumn idColumn)) if (!rowType.GetMySqlId(out FieldInfo idField, out MySqlColumn idColumn))
throw new Exception("Failed to find Id column on row."); throw new Exception("Failed to find Id column on row.");
//Write the where clause //Write the where clause
@ -260,16 +260,16 @@ namespace MontoyaTech.MySqlPlus
public static void DeleteAll<T>(this MySqlCommand command) public static void DeleteAll<T>(this MySqlCommand command)
{ {
//Get the type of T //Get the type of T
var type = typeof(T); var rowType = typeof(T);
//Get the row information. //Get the row information.
var rowAttribute = type.GetCustomAttribute<MySqlRow>(); var rowAttribute = rowType.GetCustomAttribute<MySqlRow>();
//Start building the query. //Start building the query.
var builder = new StringBuilder(); var builder = new StringBuilder();
//Write the delete from section //Write the delete from section
builder.Append($"DELETE FROM `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? type.Name : rowAttribute.Name)}`"); builder.Append($"DELETE FROM `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? rowType.Name : rowAttribute.Name)}`");
//Set the command text. //Set the command text.
command.CommandText = builder.ToString(); command.CommandText = builder.ToString();
@ -283,26 +283,27 @@ namespace MontoyaTech.MySqlPlus
public static void CreateTable<T>(this MySqlCommand command) public static void CreateTable<T>(this MySqlCommand command)
{ {
//Get the type of T //Get the type of T
var type = typeof(T); var rowType = typeof(T);
//Create a new instance of the row //Create a new instance of the row
var row = type.CreateInstance<T>(); var row = rowType.CreateInstance<T>();
//Get the row information. //Get the row information.
var rowAttribute = type.GetCustomAttribute<MySqlRow>(); var rowAttribute = rowType.GetCustomAttribute<MySqlRow>();
//Start building the query. //Start building the query.
var builder = new StringBuilder(); var builder = new StringBuilder();
//Write the delete from section //Write the delete from section
builder.Append($"CREATE TABLE `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? type.Name : rowAttribute.Name)}` ("); builder.Append($"CREATE TABLE `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? rowType.Name : rowAttribute.Name)}` (");
//Write all the columns //Write all the columns
var fields = type.GetFields(); var fields = rowType.GetFields();
bool separate = false;
if (fields != null) if (fields != null)
{ {
bool separate = false;
for (int i = 0; i < fields.Length; i++) for (int i = 0; i < fields.Length; i++)
{ {
var field = fields[i]; var field = fields[i];
@ -355,6 +356,26 @@ namespace MontoyaTech.MySqlPlus
} }
} }
//Add any indexes
var indexes = rowType.GetCustomAttributes<MySqlRowIndex>().ToList();
if (indexes != null)
{
for (int i = 0; i < indexes.Count; i++)
{
var index = indexes[i];
if (index.Columns == null || index.Columns.Length == 0)
continue;
if (separate)
builder.Append(", ");
builder.Append($"INDEX {index.Name} ({index.Columns.Aggregate((curr, next) => curr == null ? next : curr + ", " + next)})");
separate = true;
}
}
builder.Append(")"); builder.Append(")");
//Set the command text. //Set the command text.
@ -369,16 +390,16 @@ namespace MontoyaTech.MySqlPlus
public static void TableExists<T>(this MySqlCommand command) public static void TableExists<T>(this MySqlCommand command)
{ {
//Get the type of T //Get the type of T
var type = typeof(T); var rowType = typeof(T);
//Get the row information. //Get the row information.
var rowAttribute = type.GetCustomAttribute<MySqlRow>(); var rowAttribute = rowType.GetCustomAttribute<MySqlRow>();
//Start building the query. //Start building the query.
var builder = new StringBuilder(); var builder = new StringBuilder();
//Write the delete from section //Write the delete from section
builder.Append($"SHOW TABLES LIKE '{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? type.Name : rowAttribute.Name)}'"); builder.Append($"SHOW TABLES LIKE '{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? rowType.Name : rowAttribute.Name)}'");
//Set the command text //Set the command text
command.CommandText = builder.ToString(); command.CommandText = builder.ToString();
@ -392,16 +413,16 @@ namespace MontoyaTech.MySqlPlus
public static void EmptyTable<T>(this MySqlCommand command) public static void EmptyTable<T>(this MySqlCommand command)
{ {
//Get the type of T //Get the type of T
var type = typeof(T); var rowType = typeof(T);
//Get the row information. //Get the row information.
var rowAttribute = type.GetCustomAttribute<MySqlRow>(); var rowAttribute = rowType.GetCustomAttribute<MySqlRow>();
//Start building the query. //Start building the query.
var builder = new StringBuilder(); var builder = new StringBuilder();
//Write the delete from section //Write the delete from section
builder.Append($"TRUNCATE TABLE `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? type.Name : rowAttribute.Name)}`"); builder.Append($"TRUNCATE TABLE `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? rowType.Name : rowAttribute.Name)}`");
//Set the command text //Set the command text
command.CommandText = builder.ToString(); command.CommandText = builder.ToString();
@ -415,16 +436,16 @@ namespace MontoyaTech.MySqlPlus
public static void DeleteTable<T>(this MySqlCommand command) public static void DeleteTable<T>(this MySqlCommand command)
{ {
//Get the type of T //Get the type of T
var type = typeof(T); var rowType = typeof(T);
//Get the row information. //Get the row information.
var rowAttribute = type.GetCustomAttribute<MySqlRow>(); var rowAttribute = rowType.GetCustomAttribute<MySqlRow>();
//Start building the query. //Start building the query.
var builder = new StringBuilder(); var builder = new StringBuilder();
//Write the delete from section //Write the delete from section
builder.Append($"DROP TABLE `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? type.Name : rowAttribute.Name)}`"); builder.Append($"DROP TABLE `{(rowAttribute == null || string.IsNullOrWhiteSpace(rowAttribute.Name) ? rowType.Name : rowAttribute.Name)}`");
//Set the command text //Set the command text
command.CommandText = builder.ToString(); command.CommandText = builder.ToString();

View File

@ -45,9 +45,9 @@ namespace MontoyaTech.MySqlPlus
if (!reader.Read()) if (!reader.Read())
return false; return false;
var type = typeof(T); var rowType = typeof(T);
var fields = type.GetFields(); var fields = rowType.GetFields();
if (fields == null || fields.Length == 0) if (fields == null || fields.Length == 0)
throw new Exception("Found no public fields on given row."); throw new Exception("Found no public fields on given row.");
@ -79,14 +79,14 @@ namespace MontoyaTech.MySqlPlus
if (!reader.Read()) if (!reader.Read())
return false; return false;
var type = typeof(T); var rowType = typeof(T);
var fields = type.GetFields(); var fields = rowType.GetFields();
if (fields == null || fields.Length == 0) if (fields == null || fields.Length == 0)
throw new Exception("Found no public fields on given row."); throw new Exception("Found no public fields on given row.");
row = type.CreateInstance<T>(); row = rowType.CreateInstance<T>();
for (int i = 0; i < fields.Length; i++) for (int i = 0; i < fields.Length; i++)
{ {
@ -124,16 +124,16 @@ namespace MontoyaTech.MySqlPlus
/// <param name="rows"></param> /// <param name="rows"></param>
public static void ReadAll<T>(this MySqlDataReader reader, List<T> rows) public static void ReadAll<T>(this MySqlDataReader reader, List<T> rows)
{ {
var type = typeof(T); var rowType = typeof(T);
var fields = type.GetFields(); var fields = rowType.GetFields();
if (fields == null || fields.Length == 0) if (fields == null || fields.Length == 0)
throw new Exception("Found no public fields on given row."); throw new Exception("Found no public fields on given row.");
while (reader.Read()) while (reader.Read())
{ {
var row = type.CreateInstance<T>(); var row = rowType.CreateInstance<T>();
for (int i = 0; i < fields.Length; i++) for (int i = 0; i < fields.Length; i++)
{ {

View File

@ -73,9 +73,10 @@ namespace MontoyaTech.MySqlPlus
} }
/// <summary> /// <summary>
/// Attempts to reconnect to the server and retrys if needed. /// Attempts to reconnect to the server and retrys if needed, returns whether or not a connection
/// was successfully reastablished.
/// </summary> /// </summary>
public void Reconnect(int maxRetrys = 5, bool exponentialBackoff = true, bool retry = true) public bool Reconnect(int maxRetrys = 5, bool exponentialBackoff = true, bool retry = true)
{ {
int backoffSleep = 2000; int backoffSleep = 2000;
for (int i = 0; i < maxRetrys; i++) for (int i = 0; i < maxRetrys; i++)
@ -99,7 +100,7 @@ namespace MontoyaTech.MySqlPlus
//If we are now connected then stop trying. //If we are now connected then stop trying.
if (this.IsConnected) if (this.IsConnected)
break; return true;
else else
throw new Exception("Failed to open a valid MySqlConnection."); throw new Exception("Failed to open a valid MySqlConnection.");
} }
@ -117,6 +118,8 @@ namespace MontoyaTech.MySqlPlus
} }
} }
} }
return false;
} }
/// <summary> /// <summary>
@ -263,18 +266,23 @@ namespace MontoyaTech.MySqlPlus
ex.Message.ToLower().Contains("a connection attempt failed because the connected party did not properly respond after a period of time") || ex.Message.ToLower().Contains("a connection attempt failed because the connected party did not properly respond after a period of time") ||
ex.Message.ToLower().Contains("an existing connection was forcibly closed by the remote host")) ex.Message.ToLower().Contains("an existing connection was forcibly closed by the remote host"))
{ {
this.Reconnect(maxRetrys, exponentialBackoff); //Attempt to reconnect, but if this fails, it means we can't try any more since the reconnect retrys more than once.
if (!this.Reconnect(maxRetrys, exponentialBackoff))
throw;
} }
//See if we should retry or just throw. //See if we should retry or just throw.
if (!ShouldRetryBasedOnMySqlErrorNum(code)) else if (!ShouldRetryBasedOnMySqlErrorNum(code))
throw;
//If the operation took less than 5 seconds, then sleep. Otherwise continue right away.
if (GlobalTimeStamp.GetDifference(startTimestamp) <= 5000 && exponentialBackoff)
{ {
Thread.Sleep(backoffSleep); throw;
backoffSleep *= 2; }
else
{
//If the operation took less than 5 seconds, then sleep. Otherwise continue right away.
if (GlobalTimeStamp.GetDifference(startTimestamp) <= 5000 && exponentialBackoff)
{
Thread.Sleep(backoffSleep);
backoffSleep *= 2;
}
} }
} }
} }
@ -290,16 +298,106 @@ namespace MontoyaTech.MySqlPlus
private static bool ShouldRetryBasedOnMySqlErrorNum(int number) private static bool ShouldRetryBasedOnMySqlErrorNum(int number)
{ {
//List of codes here: https://www.briandunning.com/error-codes/?source=MySQL //List of codes here: https://www.briandunning.com/error-codes/?source=MySQL
if (number >= 1044 && number <= 1052) switch (number)
return false; {
else if (number >= 1054 && number <= 1075) case 1021:
return false; case 1023:
else if (number == 1265) case 1027:
return false; //Data truncation (Don't try again) case 1030:
else if (number == 1264) case 1037:
return false; //Value out of range (Don't try again) case 1038:
case 1040:
return true; case 1041:
case 1043:
case 1076:
case 1078:
case 1079:
case 1080:
case 1081:
case 1094:
case 1099:
case 1105:
case 1119:
case 1129:
case 1130:
case 1135:
case 1150:
case 1151:
case 1152:
case 1154:
case 1155:
case 1157:
case 1158:
case 1160:
case 1161:
case 1180:
case 1181:
case 1182:
case 1183:
case 1184:
case 1186:
case 1189:
case 1190:
case 1192:
case 1194:
case 1195:
case 1196:
case 1199:
case 1200:
case 1202:
case 1203:
case 1205:
case 1206:
case 1207:
case 1208:
case 1213:
case 1218:
case 1219:
case 1220:
case 1236:
case 1244:
case 1257:
case 1258:
case 1259:
case 1297:
case 1316:
case 1341:
case 2000:
case 2001:
case 2002:
case 2003:
case 2004:
case 2005:
case 2006:
case 2007:
case 2008:
case 2009:
case 2010:
case 2011:
case 2012:
case 2013:
case 2014:
case 2024:
case 2025:
case 2026:
case 2027:
case 2037:
case 2038:
case 2039:
case 2040:
case 2041:
case 2042:
case 2043:
case 2044:
case 2045:
case 2046:
case 2048:
case 2050:
case 2051:
return true;
default:
return false;
}
} }
/// <summary> /// <summary>

View File

@ -7,7 +7,7 @@
<AssemblyName>MontoyaTech.MySqlPlus</AssemblyName> <AssemblyName>MontoyaTech.MySqlPlus</AssemblyName>
<RootNamespace>MontoyaTech.MySqlPlus</RootNamespace> <RootNamespace>MontoyaTech.MySqlPlus</RootNamespace>
<Title>MontoyaTech.MySqlPlus</Title> <Title>MontoyaTech.MySqlPlus</Title>
<Version>1.0.4</Version> <Version>1.0.5</Version>
<Company>MontoyaTech</Company> <Company>MontoyaTech</Company>
<Description>A simple C# library to help work with MySql.</Description> <Description>A simple C# library to help work with MySql.</Description>
<Copyright>MontoyaTech 2023</Copyright> <Copyright>MontoyaTech 2023</Copyright>

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MontoyaTech.MySqlPlus
{
/// <summary>
/// The outline of a MySqlRowIndex attribute which allows the specification
/// of an index for a row.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = true)]
public class MySqlRowIndex : Attribute
{
public string Name;
public string[] Columns;
public MySqlRowIndex() { }
public MySqlRowIndex(string name, params string[] columns)
{
if (columns == null || columns.Length == 0)
throw new ArgumentException("Columns must not be null or 0.");
this.Name = name;
this.Columns = columns;
}
}
}