Skip to content

Commit

Permalink
Support the TableAndViewAttribute #348
Browse files Browse the repository at this point in the history
  • Loading branch information
Grauenwolf committed Feb 4, 2020
1 parent 532358f commit a41ea1f
Show file tree
Hide file tree
Showing 35 changed files with 759 additions and 646 deletions.
35 changes: 30 additions & 5 deletions Tortuga.Chain/Shared/DataSource/DataSourceBase.Class1DataSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,64 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Tortuga.Chain.CommandBuilders;
using Tortuga.Chain.Metadata;
using System;

#if SQL_SERVER_SDS

using AbstractCommand = System.Data.SqlClient.SqlCommand;
using AbstractDbType = System.Data.SqlDbType;
using AbstractParameter = System.Data.SqlClient.SqlParameter;
using AbstractObjectName = Tortuga.Chain.SqlServer.SqlServerObjectName;
using AbstractLimitOption = Tortuga.Chain.SqlServerLimitOption;

#elif SQL_SERVER_MDS

using AbstractCommand = Microsoft.Data.SqlClient.SqlCommand;
using AbstractDbType = System.Data.SqlDbType;
using AbstractParameter = Microsoft.Data.SqlClient.SqlParameter;
using AbstractObjectName = Tortuga.Chain.SqlServer.SqlServerObjectName;
using AbstractLimitOption = Tortuga.Chain.SqlServerLimitOption;

#elif SQL_SERVER_OLEDB

using AbstractCommand = System.Data.OleDb.OleDbCommand;
using AbstractDbType = System.Data.OleDb.OleDbType;
using AbstractLimitOption = Tortuga.Chain.SqlServerLimitOption;
using AbstractObjectName = Tortuga.Chain.SqlServer.SqlServerObjectName;
using AbstractParameter = System.Data.OleDb.OleDbParameter;

#elif SQLITE

using AbstractCommand = System.Data.SQLite.SQLiteCommand;
using AbstractDbType = System.Data.DbType;
using AbstractParameter = System.Data.SQLite.SQLiteParameter;
using AbstractObjectName = Tortuga.Chain.SQLite.SQLiteObjectName;
using AbstractLimitOption = Tortuga.Chain.SQLiteLimitOption;

#elif MYSQL

using AbstractCommand = MySql.Data.MySqlClient.MySqlCommand;
using AbstractDbType = MySql.Data.MySqlClient.MySqlDbType;
using AbstractParameter = MySql.Data.MySqlClient.MySqlParameter;
using AbstractObjectName = Tortuga.Chain.MySql.MySqlObjectName;
using AbstractLimitOption = Tortuga.Chain.MySqlLimitOption;

#elif POSTGRESQL

using AbstractCommand = Npgsql.NpgsqlCommand;
using AbstractDbType = NpgsqlTypes.NpgsqlDbType;
using AbstractParameter = Npgsql.NpgsqlParameter;
using AbstractObjectName = Tortuga.Chain.PostgreSql.PostgreSqlObjectName;
using AbstractLimitOption = Tortuga.Chain.PostgreSqlLimitOption;

#elif ACCESS

using AbstractCommand = System.Data.OleDb.OleDbCommand;
using AbstractDbType = System.Data.OleDb.OleDbType;
using AbstractParameter = System.Data.OleDb.OleDbParameter;
using AbstractLimitOption = Tortuga.Chain.AccessLimitOption;
using AbstractObjectName = Tortuga.Chain.Access.AccessObjectName;
using AbstractParameter = System.Data.OleDb.OleDbParameter;

#endif

Expand Down Expand Up @@ -255,7 +264,7 @@ public TableDbCommandBuilder<AbstractCommand, AbstractParameter, AbstractLimitOp
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
public TableDbCommandBuilder<AbstractCommand, AbstractParameter, AbstractLimitOption> From<TObject>() where TObject : class
{
return From(DatabaseMetadata.GetTableOrViewFromClass<TObject>().Name);
return From(DatabaseObjectAsTableOrView<TObject>(OperationType.Select).Name);
}

/// <summary>
Expand All @@ -267,7 +276,7 @@ public TableDbCommandBuilder<AbstractCommand, AbstractParameter, AbstractLimitOp
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
public TableDbCommandBuilder<AbstractCommand, AbstractParameter, AbstractLimitOption> From<TObject>(string whereClause) where TObject : class
{
return From(DatabaseMetadata.GetTableOrViewFromClass<TObject>().Name, whereClause);
return From(DatabaseObjectAsTableOrView<TObject>(OperationType.Select).Name, whereClause);
}

/// <summary>
Expand All @@ -280,7 +289,7 @@ public TableDbCommandBuilder<AbstractCommand, AbstractParameter, AbstractLimitOp
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
public TableDbCommandBuilder<AbstractCommand, AbstractParameter, AbstractLimitOption> From<TObject>(string whereClause, object argumentValue) where TObject : class
{
return From(DatabaseMetadata.GetTableOrViewFromClass<TObject>().Name, whereClause, argumentValue);
return From(DatabaseObjectAsTableOrView<TObject>(OperationType.Select).Name, whereClause, argumentValue);
}

/// <summary>
Expand All @@ -292,7 +301,7 @@ public TableDbCommandBuilder<AbstractCommand, AbstractParameter, AbstractLimitOp
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
public TableDbCommandBuilder<AbstractCommand, AbstractParameter, AbstractLimitOption> From<TObject>(object filterValue) where TObject : class
{
return From(DatabaseMetadata.GetTableOrViewFromClass<TObject>().Name, filterValue);
return From(DatabaseObjectAsTableOrView<TObject>(OperationType.Select).Name, filterValue);
}

/// <summary>
Expand Down Expand Up @@ -503,5 +512,21 @@ public ObjectDbCommandBuilder<AbstractCommand, AbstractParameter, TArgument> Ups
}

#endif

TableOrViewMetadata<AbstractObjectName, AbstractDbType> DatabaseObjectAsTableOrView<TObject>(OperationType operationType)
{
var databaseObject = DatabaseMetadata.GetDatabaseObjectFromClass<TObject>(OperationType.Select);

if (databaseObject is TableOrViewMetadata<AbstractObjectName, AbstractDbType> table)
return table;
else if (databaseObject is StoredProcedureMetadata<AbstractObjectName, AbstractDbType>)
throw new ArgumentException($"Table or view expected. Use `Procedure<TObject>(value, OperationType.{operationType})` instead.");
else if (databaseObject is TableFunctionMetadata<AbstractObjectName, AbstractDbType>)
throw new ArgumentException($"Table or view expected. Use `TableFunction<TObject>(value, OperationType.{operationType})` instead.");
else if (databaseObject is ScalarFunctionMetadata<AbstractObjectName, AbstractDbType>)
throw new ArgumentException($"Table or view expected. Use `ScalarFunction<TObject>(value, OperationType.{operationType})` instead.");
else
throw new NotSupportedException($"Unexpected type {databaseObject.GetType()}");
}
}
}
31 changes: 31 additions & 0 deletions Tortuga.Chain/Shared/Tests/CommandBuilders/FromTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,37 @@ namespace Tests.CommandBuilders
[TestClass]
public class FromTests : TestBase
{
[DataTestMethod, BasicData(DataSourceGroup.Primary)]
public void WriteToTableReadFromView(string dataSourceName, DataSourceType mode)
{
var dataSource = DataSource(dataSourceName, mode);
try
{
//Get a random manager key
var manager = dataSource.From<Employee>().WithLimits(1).ToObject<Employee>().Execute();

var lookupKey = Guid.NewGuid().ToString();
for (var i = 0; i < 10; i++)
dataSource.Insert(new EmployeeWithManager() { FirstName = i.ToString("0000"), LastName = "Z" + (int.MaxValue - i), Title = lookupKey, MiddleName = i % 2 == 0 ? "A" + i : null, ManagerKey = manager.EmployeeKey, EmployeeId = Guid.NewGuid().ToString() })
.AsNonQuery().SetStrictMode(false).Execute();

var values = dataSource.From<EmployeeWithManager>(new { Title = lookupKey }).ToCollection<EmployeeWithManager>().Execute();
Assert.AreEqual(10, values.Count);

foreach (var echo in values)
{
Assert.AreEqual(manager.EmployeeKey, echo.ManagerKey);
Assert.AreEqual(manager.EmployeeKey, echo.Manager.EmployeeKey);
Assert.AreEqual(manager.FirstName, echo.Manager.FirstName);
Assert.AreEqual(manager.LastName, echo.Manager.LastName);
}
}
finally
{
Release(dataSource);
}
}

[DataTestMethod, TablesAndViewData(DataSourceGroup.All)]
public void ToDynamicCollection(string dataSourceName, DataSourceType mode, string tableName)
{
Expand Down
5 changes: 3 additions & 2 deletions Tortuga.Chain/Shared/Tests/Models/EmployeeWithManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Tests.Models
{
[TableAndView(TestBase.EmployeeTableName, ViewName = TestBase.EmployeeViewName)]
public class EmployeeWithManager
{
public int? EmployeeKey { get; set; }
Expand All @@ -10,13 +11,15 @@ public class EmployeeWithManager
public string LastName { get; set; }
public string Title { get; set; }

public int? ManagerKey { get; set; }

[Decompose("Manager")]
public Manager Manager { get; set; }

[Decompose]
public AuditInfo AuditInfo { get; set; }

public string EmployeeId { get; set; }
}

public class Manager
Expand All @@ -27,9 +30,7 @@ public class Manager
public string LastName { get; set; }
public string Title { get; set; }


[Decompose]
public AuditInfo AuditInfo { get; set; }

}
}
33 changes: 32 additions & 1 deletion Tortuga.Chain/Tortuga.Chain.Access.Tests/Setup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ MiddleName TEXT(30) NULL,
LastName TEXT(30) NOT NULL,
EmployeeId TEXT(50) NOT NULL,
Title TEXT(100) null,
OfficePhone TEXT(15) NULL,
CellPhone TEXT(15) NULL,
ManagerKey LONG NULL REFERENCES Employee(EmployeeKey),
CreatedDate DateTime NOT NULL DEFAULT NOW(),
UpdatedDate DateTime NULL
Expand All @@ -78,7 +80,33 @@ CreatedDate DATETIME NULL DEFAULT NOW(),
DeletedByKey INTEGER NULL
)";

const string sql3 = "CREATE VIEW EmployeeLookup AS SELECT FirstName, LastName, EmployeeKey FROM Employee";
const string sql3 = "CREATE VIEW EmployeeLookup AS SELECT FirstName, LastName, EmployeeKey, EmployeeId FROM Employee";

const string sql4 = @"CREATE VIEW EmployeeWithManager
AS
SELECT Employee_1.EmployeeKey ,
Employee_1.FirstName ,
Employee_1.MiddleName ,
Employee_1.LastName ,
Employee_1.Title ,
Employee_1.ManagerKey ,
Employee_1.OfficePhone ,
Employee_1.CellPhone ,
Employee_1.CreatedDate ,
Employee_1.UpdatedDate ,
Employee_1.EmployeeId,
Employee_2.EmployeeKey AS ManagerEmployeeKey ,
Employee_2.FirstName AS ManagerFirstName ,
Employee_2.MiddleName AS ManagerMiddleName ,
Employee_2.LastName AS ManagerLastName ,
Employee_2.Title AS ManagerTitle ,
Employee_2.ManagerKey AS ManagerManagerKey ,
Employee_2.OfficePhone AS ManagerOfficePhone ,
Employee_2.CellPhone AS ManagerCellPhone ,
Employee_2.CreatedDate AS ManagerCreatedDate ,
Employee_2.UpdatedDate AS ManagerUpdatedDate
FROM Employee AS Employee_1
LEFT JOIN Employee AS Employee_2 ON Employee_2.EmployeeKey = Employee_1.ManagerKey";

using (var command = new OleDbCommand(sql, dbConnection))
command.ExecuteNonQuery();
Expand All @@ -89,6 +117,9 @@ DeletedByKey INTEGER NULL
using (var command = new OleDbCommand(sql3, dbConnection))
command.ExecuteNonQuery();

using (var command = new OleDbCommand(sql4, dbConnection))
command.ExecuteNonQuery();

sql = "INSERT INTO Employee ([FirstName], [MiddleName], [LastName], [Title], [ManagerKey], [EmployeeId]) VALUES (@FirstName, @MiddleName, @LastName, @Title, @ManagerKey, @EmployeeId)";

sql2 = "INSERT INTO Employee ([FirstName], [MiddleName], [LastName], [Title], [ManagerKey], [CreatedDate], [EmployeeId]) VALUES (@FirstName, @MiddleName, @LastName, @Title, @ManagerKey, @CreatedDate, @EmployeeId)";
Expand Down
43 changes: 21 additions & 22 deletions Tortuga.Chain/Tortuga.Chain.Access.Tests/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,13 @@ namespace Tests
{
public abstract partial class TestBase
{
public const string EmployeeTableName = "Employee";
public const string EmployeeViewName = "EmployeeWithManager";
internal static readonly Dictionary<string, AccessDataSource> s_DataSources = new Dictionary<string, AccessDataSource>();
internal static AccessDataSource s_PrimaryDataSource;

internal static void SetupTestBase()
{
if (s_PrimaryDataSource != null)
return; //run once check

Setup.CreateDatabase();

var configuration = new ConfigurationBuilder().SetBasePath(AppContext.BaseDirectory).AddJsonFile("appsettings.json").Build();

foreach (var con in configuration.GetSection("ConnectionStrings").GetChildren())
{
var ds = new AccessDataSource(con.Key, con.Value);
s_DataSources.Add(con.Key, ds);
if (s_PrimaryDataSource == null) s_PrimaryDataSource = ds;
}

BuildEmployeeSearchKey1000(s_PrimaryDataSource);
}

public static string CustomerTableName { get { return "Customer"; } }

public static string EmployeeTableName { get { return "Employee"; } }
public static string EmployeeViewName { get { return "EmployeeLookup"; } }

public AccessDataSource AttachRules(AccessDataSource source)
{
return source.WithRules(
Expand Down Expand Up @@ -103,6 +83,25 @@ public async Task<AccessDataSourceBase> DataSourceAsync(string name, DataSourceT
throw new ArgumentException($"Unknown mode {mode}");
}

internal static void SetupTestBase()
{
if (s_PrimaryDataSource != null)
return; //run once check

Setup.CreateDatabase();

var configuration = new ConfigurationBuilder().SetBasePath(AppContext.BaseDirectory).AddJsonFile("appsettings.json").Build();

foreach (var con in configuration.GetSection("ConnectionStrings").GetChildren())
{
var ds = new AccessDataSource(con.Key, con.Value);
s_DataSources.Add(con.Key, ds);
if (s_PrimaryDataSource == null) s_PrimaryDataSource = ds;
}

BuildEmployeeSearchKey1000(s_PrimaryDataSource);
}

void WriteDetails(ExecutionEventArgs e)
{
var token = e.ExecutionDetails as AccessCommandExecutionToken;
Expand Down
33 changes: 4 additions & 29 deletions Tortuga.Chain/Tortuga.Chain.Access/Access/AccessMetadataCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,32 +88,6 @@ public override TableOrViewMetadata<AccessObjectName, OleDbType> GetTableOrView(
throw new MissingObjectException($"Could not find table or view {tableName}");
}

/// <summary>
/// Returns the table or view derived from the class's name and/or Table attribute.
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <returns>TableOrViewMetadata&lt;AccessObjectName, OleDbType&gt;.</returns>
public override TableOrViewMetadata<AccessObjectName, OleDbType> GetTableOrViewFromClass<TObject>()
{
var type = typeof(TObject);
TableOrViewMetadata<AccessObjectName, OleDbType> result;
if (m_TypeTableMap.TryGetValue(type, out result))
return result;

var typeInfo = MetadataCache.GetMetadata(type);
if (!typeInfo.MappedTableName.IsNullOrEmpty())
{
result = GetTableOrView(typeInfo.MappedTableName);
m_TypeTableMap[type] = result;
return result;
}

//infer table from class name
result = GetTableOrView(type.Name);
m_TypeTableMap[type] = result;
return result;
}

/// <summary>
/// Gets the tables and views that were loaded by this cache.
/// </summary>
Expand Down Expand Up @@ -161,11 +135,12 @@ public override void Reset()
}

/// <summary>
/// Parses the name of the database object.
/// Parse a string and return the database specific representation of the object name.
/// </summary>
/// <param name="schema">The schema.</param>
/// <param name="name">The name.</param>
/// <returns>System.String.</returns>
protected override AccessObjectName ParseObjectName(string name) => name;
/// <returns>AccessObjectName.</returns>
protected override AccessObjectName ParseObjectName(string? schema, string name) => name;

/// <summary>
/// Determines the database column type from the column type name.
Expand Down
14 changes: 4 additions & 10 deletions Tortuga.Chain/Tortuga.Chain.Access/Tortuga.Chain.Access.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Tortuga.Chain.AuditRules
/// <summary>
/// Indicates the type of operation being performed.
/// </summary>
/// <remarks>Keep numbers in sync with Metadata.OperationType.</remarks>
[Flags]
public enum OperationTypes
{
Expand Down
Loading

0 comments on commit a41ea1f

Please sign in to comment.