How to use Mapster to do type level mapping, with special mapping logic? - mapster

public static void Initialize()
{
TypeAdapterConfig<string?, DateTime?>.NewConfig()
.MapWith(s => Convert(s));
}
private static DateTime? Convert(string? s) => DateTime.TryParseExact(
s, "dd/MM/yyyy", null, DateTimeStyles.None, out DateTime date) ? date : null;
Here's my workaround. If I didn't use the separate private method, the overloads of MapWith won't allow the ? operator as the expression argument. Is there any other built-in way to achieve the same result?

Related

Date type for a certain property with Pure Code First Hot Chocolate GraphQL

I am using pure code first with Hot Chocolate but I have dateTime C# type and I am having problems with the timezone getting converted incorrectly in javascript. So I would like it to output a Date instead of a DateTime object. For now I am using
SchemaBuilder.New()
.BindClrType<DateTime, DateType>()
but this is a very brute force approach as now I can never output a DateTime type in graphQL.
Is there way to put an attribute on the property or set it up somewhere so that a specific property on a class is output as a Date instead of DateTime?
You can also use Pure Code First approach by attributing target field with GraphQLType attribute:
using HotChocolate;
public class User
{
public int Id { get; set; }
[GraphQLType(typeof(NonNullType<DateType>))]
public DateTime BirthDate { get; set; }
}
Yes, there's a pretty straightforward way of reaching that. Suppose you have some User class:
public class User
{
public int Id { get; set; }
public DateTime BirthDate { get; set; }
}
To be able to specify that BirthDate is just a date but not datetime define the User "type metadata" and specify the GraphQL type of BirthDate as "DateType":
public class UserType : ObjectType<User>
{
protected override void Configure(IObjectTypeDescriptor<User> descriptor)
{
descriptor.Field(t => t.BirthDate).Type<DateType>();
}
}
and register that type on schema building:
public void ConfigureServices(IServiceCollection services)
{
services.AddGraphQL(sp =>
SchemaBuilder.New()
.AddQueryType<Query>()
.AddType<UserType>()
.Create());
}

Does Dapper support c# 6 read-only properties in POCOs?

Given the following:
public class SomePoco {
public int IntValue { get; }
}
and
CREATE TABLE SomePocoStorage (IntValue INT NOT NULL)
and
INSERT SomePocoStorage VALUES (1), (274)
If I call
connection.Query<SomePoco>("SELECT * FROM SomePocoStorage")
does Dapper handle populating the IntValue field on the returned SomePoco instances?
Good question! It isn't a scenario I've targeted, but I'd be more than happy to take a look at what would be involved. Since we already do a lot of nasty reflection, this could still be viable. Probably better as a github issue, but I'll have a look.
Update - it does now (at the current time, via repo only - not deployed):
[Fact] // passes
public void GetOnlyProperties()
{
var obj = connection.QuerySingle<HazGetOnly>(
"select 42 as [Id], 'def' as [Name];");
obj.Id.IsEqualTo(42);
obj.Name.IsEqualTo("def");
}
class HazGetOnly
{
public int Id { get; }
public string Name { get; } = "abc";
}
No because there's no way for Dapper to set the value of the property if that property only has a getter.

How can I make Dapper.NET throw when result set has unmapped columns?

Using the example code below as context... When I run this query I get the 'Id' field coming back as default value (which is 0 for an int). I would like to tell dapper to run in a manner where it would throw an exception if there is a column in the result set that does not get mapped to a property on my result object. (I understand that the issue is just that I need to remove the extra 'd' in the SQL query but I'm interested in having this expose itself more explicitly)
I've been unable to find anything on this topic. Please let me know if this is even possible with Dapper.
Thanks in advance (besides this issue, and for anyone who hasn't taken the plunge, Dapper really is the greatest thing since sliced bread!).
class CustomerRecord
{
public int Id { get; set; }
public string Name { get; set; }
}
CustomerRecord[] GetCustomerRecords()
{
CustomerRecord[] ret;
var sql = #"SELECT
CustomerRecordId AS Idd,
CustomerName as Name
FROM CustomerRecord";
using (var connection = new SqlConnection(this.connectionString))
{
ret = connection.Query<CustomerRecord>(sql).ToArray();
}
return ret;
}
You could create your own type map where you use Dapper's DefaultTypeMap and throw an exception when it cannot find the member:
public class ThrowWhenNullTypeMap<T> : SqlMapper.ITypeMap
{
private readonly SqlMapper.ITypeMap _defaultTypeMap = new DefaultTypeMap(typeof(T));
public ConstructorInfo FindConstructor(string[] names, Type[] types)
{
return _defaultTypeMap.FindConstructor(names, types);
}
public ConstructorInfo FindExplicitConstructor()
{
return _defaultTypeMap.FindExplicitConstructor();
}
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
return _defaultTypeMap.GetConstructorParameter(constructor, columnName);
}
public SqlMapper.IMemberMap GetMember(string columnName)
{
var member = _defaultTypeMap.GetMember(columnName);
if (member == null)
{
throw new Exception();
}
return member;
}
}
Downside of this, is that you have to configure all the type maps for every entity:
SqlMapper.SetTypeMap(typeof(CustomerRecord), typeof(ThrowWhenNullTypeMap<CustomerRecord>));
This could be configured using reflection, however.
I came here after I solved this same problem for the IEnumerable<dynamic> methods in Dapper. Then I found the proposal to solve the issue for Query<T>; but that doesn't seem to be going anywhere.
My answer builds on the answer proposed by #HenkMollema, and uses his class in the solution, so credit to him for that...
To solve the IEnumerable<dynamic> scenario, I had created a "SafeDynamic" class (follow the link above to see that). I refactored the static "Create" method into an extension method:
public static class EnumerableDynamicExtensions
{
public static IEnumerable<dynamic> Safe(this IEnumerable<dynamic> rows)
{
return rows.Select(x => new SafeDynamic(x));
}
}
and then I created a DapperExtensions class to provide 'Safe' versions of Query and Read (Read is used after QueryMultiple), to give me...
internal static class DapperExtensions
{
public static IEnumerable<dynamic> SafeQuery(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = default(int?), CommandType? commandType = default(CommandType?))
{
return cnn.Query(sql, param, transaction, buffered, commandTimeout, commandType).Safe();
}
public static IEnumerable<dynamic> SafeRead(this SqlMapper.GridReader gridReader, bool buffered = true)
{
return gridReader.Read(buffered).Safe();
}
}
So to solve this issue I added a "SafeQuery<T>" method to DapperExtensions, which takes care of setting up that type mapping for you:
private static readonly IDictionary<Type, object> TypesThatHaveMapper = new Dictionary<Type, object>();
public static IEnumerable<T> SafeQuery<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = default(int?), CommandType? commandType = default(CommandType?))
{
if (TypesThatHaveMapper.ContainsKey(typeof(T)) == false)
{
SqlMapper.SetTypeMap(typeof(T), new ThrowWhenNullTypeMap<T>());
TypesThatHaveMapper.Add(typeof(T), null);
}
return cnn.Query<T>(sql, param, transaction, buffered, commandTimeout, commandType);
}
So if the original poster changes the call to Query to become SafeQuery, it should do what he requested
Edit 25/1/17
Improvements to avoid threading issues on the static dictionary:
private static readonly ConcurrentDictionary<Type, object> TypesThatHaveMapper = new ConcurrentDictionary<Type, object>();
public static IEnumerable<T> SafeQuery<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = default(int?), CommandType? commandType = default(CommandType?))
{
TypesThatHaveMapper.AddOrUpdate(typeof(T), AddValue, UpdateValue);
return cnn.Query<T>(sql, param, transaction, buffered, commandTimeout, commandType);
}
private static object AddValue(Type type)
{
SqlMapper.SetTypeMap(type, XXX); // Apologies... XXX is left to the reader, as my implementation has moved on significantly.
return null;
}
private static object UpdateValue(Type type, object existingValue)
{
return null;
}
I'd like to expand on #Richardissimo 's answer by providing a visual studio project that includes his "SafeQuery" extention to Dapper, wrapped up nice and neat and tested.
https://github.com/LarrySmith-1437/SafeDapper
I use this in all my projects now to help keep the DAL clean of mismapped data, and felt the need to share. I would have posted up a Nuget, but the dependency on Dapper itself makes it much easier to post the project where consumers can update the reference to the Dapper version they want. Consume in good health, all.
Based on this thread and some other resources on SO, I've created an extension method without any custom mapper. What I needed was to throw when some property of my DTO was not set because for example SQL query has some column missing in SELECT statement.
This way my DTO would be set with default property silently and that's kinda dangerous.
The code can be simplified a little by not checking firstly for all properties being present in result, but throwing exception in the last Select call where we could iterate through properties of our type and check if query result has this property as well.
public static class Extensions
{
public static async Task<IEnumerable<T>> SafeQueryAsync<T>(
this IDbConnection cnn,
string sql,
object param = null,
IDbTransaction transaction = null,
int? commandTimeout = default(int?),
CommandType? commandType = default(CommandType?))
where T : new()
{
Dictionary<string, PropertyInfo> propertySetters = typeof(T)
.GetProperties().Where(p => p.CanRead && p.CanWrite)
.ToDictionary(p => p.Name.ToLowerInvariant(), p => p);
HashSet<string> typeProperties = propertySetters
.Select(p => p.Key)
.ToHashSet();
var rows = (await cnn.QueryAsync(sql, param, transaction, commandTimeout, commandType)).ToArray();
if (!rows.Any())
{
return Enumerable.Empty<T>();
}
var firstRow = rows.First();
HashSet<string> rowColumns = ((IDictionary<string, object>) firstRow)
.Select(kvp=>kvp.Key.ToLowerInvariant()).ToHashSet();
var notMappedColumns = typeProperties.Except(rowColumns).ToArray();
if (notMappedColumns.Any())
{
throw new InvalidOperationException(
$"Not all type properties had corresponding columns in SQL query. Query result lacks [{string.Join(", ", notMappedColumns)}]");
}
return rows.Select(row =>
{
IDictionary<string, object> rowDict = (IDictionary<string, object>) row;
T instance = new T();
rowDict.Where(o => propertySetters.ContainsKey(o.Key.ToLowerInvariant()))
.ToList().ForEach(o => propertySetters[o.Key.ToLowerInvariant()].SetValue(instance, o.Value));
return instance;
}).AsEnumerable();
}
}

Autofixture test for invalid constructor parameter

I have the following class and test. I want to test passing a null value as a parameter to the constructor and are expecting an ArgumentNullException. But since I use the Autofixture's CreateAnonymous method I get a TargetInvocationException instead.
What is the correct way to write those kinds of tests?
public sealed class CreateObject : Command {
// Properties
public ObjectId[] Ids { get; private set; }
public ObjectTypeId ObjectType { get; private set; }
public UserId CreatedBy { get; private set; }
// Constructor
public CreateObject(ObjectId[] ids, ObjectTypeId objectType, UserId createdBy) {
Guard.NotNull(ids, "ids");
Guard.NotNull(objectType, "objectType");
Guard.NotNull(createdBy, "createdBy");
Ids = ids;
ObjectType = objectType;
CreatedBy = createdBy;
}
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void constructor_with_null_ids_throw() {
fixture.Register<ObjectId[]>(() => null);
fixture.CreateAnonymous<CreateObject>();
}
IMO, Ruben Bartelink's comment is the best answer.
With AutoFixture.Idioms, you can do this instead:
var fixture = new Fixture();
var assertion = new GuardClauseAssertion(fixture);
assertion.Verify(typeof(CreateObject).GetConstructors());
The Verify method will provide you with a quite detailed exception message if any constructor argument in any constructor is lacking a Guard Clause.
FWIW, AutoFixture extensively uses Reflection, so I don't consider it a bug that it throws a TargetInvocationException. While it could unwrap all TargetInvocationException instances and rethrow their InnerException properties, that would also mean disposing of (potentially) valuable information (such as the AutoFixture stack trace). I've considered this, but don't want to take AutoFixture in that direction, for exactly that reason. A client can always filter out information, but if information is removed prematurely, no client can get it back.
If you prefer the other approach, it's not too hard to write a helper method that unwraps the exception - perhaps something like this:
public Exception Unwrap(this Exception e)
{
var tie = e as TargetInvocationException;
if (tie != null)
return tie.InnerException;
return e;
}
I came across this while I was searching for something similar. I would like to add that, combined with automoqcustomization and xunit, below code also works and its much cleaner.
[Theory, AutoMoqData]
public void Constructor_GuardClausesArePresent(GuardClauseAssertion assertion)
{
assertion.Verify(typeof(foo).GetConstructors());
}
You just need to create the AutoMoqData attribute as follows.
public class AutoMoqDataAttribute : AutoDataAttribute
{
public AutoMoqDataAttribute() : base(() => new Fixture().Customize(new AutoMoqCustomization()))
{
}
}

How does one make NHibernate stop using nvarchar(4000) for insert parameter strings?

I need to optimize a query that is being produced by a save (insert query) on a domain entity. I've configured NHibernate using Fluent NHibernate.
Here's the query generated by NHibernate during the insertion of a user's response to a poll:
exec sp_executesql N'INSERT INTO dbo.Response (ModifiedDate, IpAddress, CountryCode,
IsRemoteAddr, PollId) VALUES (#p0, #p1, #p2, #p3, #p4); select SCOPE_IDENTITY()',N'#p0
datetime,#p1 nvarchar(4000),#p2 nvarchar(4000),#p3 bit,#p4 int',
#p0='2001-07-08 03:59:05',#p1=N'127.0.0.1',#p2=N'US',#p3=1,#p4=2
If one looks at the input parameters for IpAddress and CountryCode, one will notice that NHibernate is using nvarchar(4000). The problem is that nvarchar(4000) is far larger than I need for either IpAddress or CountryCode and due to high traffic and hosting requirements I need to optimize the database for memory usage.
Here's the Fluent NHibernate auto-mapping overrides for those columns:
mapping.Map(x => x.IpAddress).CustomSqlType("varchar(15)");
mapping.Map(x => x.CountryCode).CustomSqlType("varchar(6)");
This isn't the only place that I see unnecessary nvarchar(4000)'s popping up.
How do I control NHibernate's usage of nvarchar(4000) for string representation?
How do I change this insert statement to use the proper sized input parameters?
Specify the Type as NHibernateUtil.AnsiString with a Length instead of using a CustomSqlType.
This issue can cause a huge performance problem in queries if it forces SQL Server to perform a table scan instead of using an index. We use varchar throughout our database so I created a convention to set the type globally:
/// <summary>
/// Convert all string properties to AnsiString (varchar). This does not work with SQL CE.
/// </summary>
public class AnsiStringConvention : IPropertyConventionAcceptance, IPropertyConvention
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Property.PropertyType.Equals(typeof(string)));
}
public void Apply(IPropertyInstance instance)
{
instance.CustomType("AnsiString");
}
}
Okay this is what we have to do, the SQLClientDriver ignores the length property of the SqlType. So we created a our own driverclass inheriting from SQLClientDriver and override the method GenerateCommand...Something like this:
public override IDbCommand GenerateCommand(CommandType type, NHibernate.SqlCommand.SqlString sqlString, SqlType[] parameterTypes)
{
var dbCommand = base.GenerateCommand(type, sqlString, parameterTypes);
SetParameterSizes(dbCommand.Parameters, parameterTypes);
return dbCommand;
}
private static void SetParameterSizes(IDataParameterCollection parameters, SqlType[] parameterTypes)
{
for (int index = 0; index < parameters.Count; ++index)
SetVariableLengthParameterSize((IDbDataParameter)parameters[index], parameterTypes[index]);
}
private static void SetVariableLengthParameterSize(IDbDataParameter dbParam, SqlType sqlType)
{
SetDefaultParameterSize(dbParam, sqlType);
if (sqlType.LengthDefined && !IsText(dbParam, sqlType) && !IsBlob(dbParam, sqlType))
dbParam.Size = sqlType.Length;
if (!sqlType.PrecisionDefined)
return;
dbParam.Precision = sqlType.Precision;
dbParam.Scale = sqlType.Scale;
}
Here is a work around, if you want to replace all nvarchar with varchar
public class Sql2008NoNVarCharDriver : Sql2008ClientDriver
{
public override void AdjustCommand(IDbCommand command)
{
foreach (System.Data.SqlClient.SqlParameter x in command.Parameters)
{
if (x.SqlDbType == SqlDbType.NVarChar)
{
x.SqlDbType = SqlDbType.VarChar;
}
}
base.AdjustCommand(command);
}
}
Then plug it into your config
var cfg = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString)
.Driver<Sql2008NoNVarCharDriver>())
...

Resources