Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/custom table name mapping #12

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Dapper.GraphQL.Test/DeleteTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Dapper.GraphQL.Test.Models;
using Xunit;

namespace Dapper.GraphQL.Test
{
public class DeleteTests : IClassFixture<TestFixture>
{
private readonly TestFixture fixture;

public DeleteTests(TestFixture fixture)
{
this.fixture = fixture;
}

[Fact(DisplayName = "DELETE query uses custom table name")]
public void DeleteWithCustomTableName()
{
// Check generic Delete uses custom table name for Contact as configured in TestFixture
var contact = new Contact();

var query = SqlBuilder.Delete<Contact>(contact);
Assert.Equal("DELETE FROM Contacts WHERE ", query.ToString());
}
}
}
17 changes: 13 additions & 4 deletions Dapper.GraphQL.Test/InsertTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ public void InsertPerson()
};

// Add email and phone number to the person
int insertedCount;
insertedCount = SqlBuilder
var insertedCount = SqlBuilder
.Insert(email)
.Insert(phone)
.Execute(db);
Expand Down Expand Up @@ -153,8 +152,7 @@ public async Task InsertPersonAsync()
};

// Add email and phone number to the person
int insertedCount;
insertedCount = await SqlBuilder
var insertedCount = await SqlBuilder
.Insert(email)
.Insert(phone)
.ExecuteAsync(db);
Expand Down Expand Up @@ -210,5 +208,16 @@ public async Task InsertPersonAsync()
Assert.Equal("8011115555", person.Phones[0].Number);
Assert.Equal(personId, person.Phones[0].PersonId);
}

[Fact(DisplayName = "INSERT query uses custom table name")]
public void InsertWithCustomTableName()
{
// Check generic Insert uses custom table name for Contact as configured in TestFixture
var contact = new Contact();
var query = SqlBuilder.Insert<Contact>(contact);

Assert.Equal("Contacts", query.Table);
Assert.Equal("INSERT INTO Contacts () VALUES ();", query.ToString());
}
}
}
11 changes: 11 additions & 0 deletions Dapper.GraphQL.Test/Models/Contact.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Dapper.GraphQL.Test.Models
{
public class Contact
{

}
}
13 changes: 13 additions & 0 deletions Dapper.GraphQL.Test/QueryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,18 @@ public void SelectWithoutMatchingAliasShouldThrow()
}
});
}

[Fact(DisplayName = "SELECT query uses custom table name")]
public void SelectWithCustomTableName()
{
// Check generic Select uses custom table name for Contact as configured in TestFixture
var contact = new Contact();

var query = SqlBuilder.From<Contact>();
Assert.Equal("SELECT\r\n\r\nFROM Contacts\r\n", query.ToString());

var queryWithAlias = SqlBuilder.From<Contact>("contacts");
Assert.Equal("SELECT\r\n\r\nFROM Contacts contacts\r\n", queryWithAlias.ToString());
}
}
}
3 changes: 3 additions & 0 deletions Dapper.GraphQL.Test/TestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ private void SetupDapperGraphQL(IServiceCollection serviceCollection)

// Add entity mappers
options.AddEntityMapper<Person, PersonEntityMapper>();

// Configure custom table names
options.AddCustomTableNameMapping<Contact>("Contacts");
});
}

Expand Down
10 changes: 10 additions & 0 deletions Dapper.GraphQL.Test/UpdateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,15 @@ await SqlBuilder
}
}
}

[Fact(DisplayName = "UPDATE query uses custom table name")]
public void UpdateWithCustomTableName()
{
// Check generic Update uses custom table name for Contact as configured in TestFixture
var contact = new Contact();

var query = SqlBuilder.Update<Contact>(contact);
Assert.Equal("UPDATE Contacts SET \r\n", query.ToString());
}
}
}
2 changes: 1 addition & 1 deletion Dapper.GraphQL/Contexts/SqlDeleteContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public SqlDeleteContext(

public static SqlDeleteContext Delete<TEntityType>(dynamic parameters = null)
{
return new SqlDeleteContext(typeof(TEntityType).Name, parameters);
return new SqlDeleteContext(TableHelper.GetTableName<TEntityType>(), parameters);
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions Dapper.GraphQL/Contexts/SqlQueryContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ namespace Dapper.GraphQL
{
public class SqlQueryContext<TEntityType> : SqlQueryContext
{
public SqlQueryContext(string alias = null, dynamic parameters = null)
: base(alias == null ? typeof(TEntityType).Name : $"{typeof(TEntityType).Name} {alias}")
public SqlQueryContext(string alias = null) :
base(alias == null ? TableHelper.GetTableName<TEntityType>() : $"{TableHelper.GetTableName<TEntityType>()} {alias}")
{
_types.Add(typeof(TEntityType));
}
Expand Down
13 changes: 13 additions & 0 deletions Dapper.GraphQL/DapperGraphQLOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,18 @@ public DapperGraphQLOptions AddType<TGraphType>() where TGraphType : class, IGra
serviceCollection.AddSingleton<TGraphType>();
return this;
}

/// <summary>
/// Configures a custom table name to use for <typeparamref name="TEntity"/>
/// when auto-mapping to a SQL table name.
/// </summary>
/// <typeparam name="TEntity">The model type to be mapped.</typeparam>
/// <param name="tableName">The custom table name to set.</param>
/// <returns></returns>
public DapperGraphQLOptions AddCustomTableNameMapping<TEntity>(string tableName) where TEntity : class
{
TableHelper.AddCustomTableNameMapping<TEntity>(tableName);
return this;
}
}
}
6 changes: 3 additions & 3 deletions Dapper.GraphQL/SqlBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static SqlDeleteContext Delete(string from, dynamic parameters = null)

public static SqlDeleteContext Delete<TEntityType>(dynamic parameters = null)
{
return new SqlDeleteContext(typeof(TEntityType).Name, parameters);
return new SqlDeleteContext(TableHelper.GetTableName<TEntityType>(), parameters);
}

public static SqlQueryContext From(string from, dynamic parameters = null)
Expand All @@ -35,7 +35,7 @@ public static SqlQueryContext From<TEntityType>(string alias = null)

public static SqlInsertContext<TEntityType> Insert<TEntityType>(TEntityType obj)
{
return new SqlInsertContext<TEntityType>(typeof(TEntityType).Name, obj);
return new SqlInsertContext(TableHelper.GetTableName<TEntityType>(), obj);
}

public static SqlInsertContext Insert(string table, dynamic parameters = null)
Expand All @@ -45,7 +45,7 @@ public static SqlInsertContext Insert(string table, dynamic parameters = null)

public static SqlUpdateContext Update<TEntityType>(TEntityType obj)
{
return new SqlUpdateContext(typeof(TEntityType).Name, obj);
return new SqlUpdateContext(TableHelper.GetTableName<TEntityType>(), obj);
}

public static SqlUpdateContext Update(string table, dynamic parameters = null)
Expand Down
56 changes: 56 additions & 0 deletions Dapper.GraphQL/TableHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;

namespace Dapper.GraphQL
{
/// <summary>
/// Helper for table related operations.
/// </summary>
public static class TableHelper
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class should probably be a service that's dependency injected with the rest of the dependencies. Something like:

public class NameService : INameService
{
    public string GetTableName<TEntity>()
    {
    }
}

Then, this class would be dependency injected in the AddDapperGraphQL() extension method.

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally up to you. I just followed the pattern you'd used for the PropertyHelper, but I agree it'd be nice to have the flexibility to let others supply their own implementation if need be.

Should we leave the caching up to their implementation or have a base class that can/must be used that does it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change it to a service that's dependency injected. The ParameterHelper is different because it's more a caching interface between .NET and GraphQL -- it doesn't matter who uses it to benefit from the cache, whereas it may matter what database you're connecting to for the TableHelper to perform properly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started playing around with this and the problem is all the static methods that create Insert/Query/Delete/Update contexts would need to accept an INameService because it can't be automatically resolved. Potentially this needs a bigger shift to use DI completely to new up these contexts. Have stopped for now as I'd like to hear your thoughts.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll see if I can take a look over the weekend. Thanks Ben!

Copy link
Contributor Author

@benmccallum benmccallum Apr 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like they have a default implementation and then a delegate you can supply to override the default if needed. We could look at an approach like that instead of dependency injection potentially.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello,

Why is it still not merged?
I also want to have an ability to specify table name because the default table name is not always suitable.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to change code to use Dapper.Contrib functionality?

{
private static Dictionary<Type, string> TableNameCache = new Dictionary<Type, string>();

/// <summary>
/// Gets a table name for a given type, as configured with a default.
/// </summary>
/// <typeparam name="TEntity">The type to get a table name for.</typeparam>
/// <returns>The configured table name or a generated default.</returns>
public static string GetTableName<TEntity>()
{
var type = typeof(TEntity);
if (!TableNameCache.ContainsKey(type))
{
lock (TableNameCache)
{
if (!TableNameCache.ContainsKey(type))
{
// Generate a default and cache it
TableNameCache[type] = type.Name;
}
else
{
return TableNameCache[type];
}
}
}

return TableNameCache[type];
}

public static void AddCustomTableNameMapping<TEntity>(string name)
{
var type = typeof(TEntity);
if (TableNameCache.TryGetValue(type, out string tableName) && tableName != name)
{
throw new InvalidOperationException($"Dapper.GraphQL - Cannot add multiple custom table names for type '${type.Name}'");
}
else
{
lock (TableNameCache)
{
TableNameCache[type] = name;
}
}
}
}
}