diff --git a/Dapper.SimpleCRUD/SimpleCRUD.cs b/Dapper.SimpleCRUD/SimpleCRUD.cs
index 691075c..054b379 100644
--- a/Dapper.SimpleCRUD/SimpleCRUD.cs
+++ b/Dapper.SimpleCRUD/SimpleCRUD.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
@@ -55,7 +55,7 @@ private static void StringBuilderCache(StringBuilder sb, string cacheKey, Action
StringBuilderCacheDict.AddOrUpdate(cacheKey, value, (t, v) => value);
sb.Append(value);
}
-
+
///
/// Returns the current dialect name
///
@@ -857,43 +857,38 @@ private static IEnumerable GetScaffoldableProperties()
props = props.Where(p => p.GetCustomAttributes(true).Any(attr => attr.GetType().Name == typeof(EditableAttribute).Name && !IsEditable(p)) == false);
-
- return props.Where(p => p.PropertyType.IsSimpleType() || IsEditable(p));
+ return props.Where(p =>
+ p.PropertyType.IsSimpleType()
+ || IsEditable(p)
+ || IsConvertible(p)
+ || IsDefinedColumn(p));
}
//Determine if the Attribute has an AllowEdit key and return its boolean state
- //fake the funk and try to mimic EditableAttribute in System.ComponentModel.DataAnnotations
- //This allows use of the DataAnnotations property in the model and have the SimpleCRUD engine just figure it out without a reference
private static bool IsEditable(PropertyInfo pi)
{
- var attributes = pi.GetCustomAttributes(false);
- if (attributes.Length > 0)
- {
- dynamic write = attributes.FirstOrDefault(x => x.GetType().Name == typeof(EditableAttribute).Name);
- if (write != null)
- {
- return write.AllowEdit;
- }
- }
- return false;
+ return pi.GetAttributeNamed(typeof(Dapper.EditableAttribute).Name)?.AllowEdit ?? false;
}
+ // a property that is `IConvertible` can be in the SELECT and the value then converted appropriately
+ private static bool IsConvertible(PropertyInfo pi)
+ {
+ var propertyType = Nullable.GetUnderlyingType(pi.PropertyType) ?? pi.PropertyType;
+ return _convertible.IsAssignableFrom(propertyType);
+ }
+
+ private static readonly Type _convertible = typeof(IConvertible);
+
+ private static bool IsDefinedColumn(PropertyInfo pi)
+ {
+ return pi.GetAttributeNamed(typeof(Dapper.ColumnAttribute).Name) != null
+ || pi.GetAttributeNamed(typeof(Dapper.KeyAttribute).Name) != null;
+ }
//Determine if the Attribute has an IsReadOnly key and return its boolean state
- //fake the funk and try to mimic ReadOnlyAttribute in System.ComponentModel
- //This allows use of the DataAnnotations property in the model and have the SimpleCRUD engine just figure it out without a reference
private static bool IsReadOnly(PropertyInfo pi)
{
- var attributes = pi.GetCustomAttributes(false);
- if (attributes.Length > 0)
- {
- dynamic write = attributes.FirstOrDefault(x => x.GetType().Name == typeof(ReadOnlyAttribute).Name);
- if (write != null)
- {
- return write.IsReadOnly;
- }
- }
- return false;
+ return pi.GetAttributeNamed(typeof(Dapper.ReadOnlyAttribute).Name)?.IsReadOnly ?? false;
}
//Get all properties that are:
@@ -1262,6 +1257,14 @@ public static bool IsSimpleType(this Type type)
return simpleTypes.Contains(type) || type.IsEnum;
}
+ //fake the funk and try to mimic EditableAttribute in System.ComponentModel.DataAnnotations
+ //This allows use of the DataAnnotations property in the model and have the SimpleCRUD engine just figure it out without a reference
+ public static dynamic GetAttributeNamed(this PropertyInfo pi, string typeName)
+ {
+ return pi.GetCustomAttributes(false)?
+ .FirstOrDefault(x => x.GetType().Name == typeName);
+ }
+
public static string CacheKey(this IEnumerable props)
{
return string.Join(",",props.Select(p=> p.DeclaringType.FullName + "." + p.Name).ToArray());
diff --git a/Dapper.SimpleCRUDTests/Models/OfficeId.cs b/Dapper.SimpleCRUDTests/Models/OfficeId.cs
new file mode 100644
index 0000000..248b9aa
--- /dev/null
+++ b/Dapper.SimpleCRUDTests/Models/OfficeId.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Data;
+
+namespace Dapper.SimpleCRUDTests;
+
+public readonly record struct OfficeId(int Id) : IConvertible
+{
+ TypeCode IConvertible.GetTypeCode() => TypeCode.Int32;
+ bool IConvertible.ToBoolean(IFormatProvider provider) => throw new InvalidCastException();
+ byte IConvertible.ToByte(IFormatProvider provider) => throw new InvalidCastException();
+ char IConvertible.ToChar(IFormatProvider provider) => throw new InvalidCastException();
+ DateTime IConvertible.ToDateTime(IFormatProvider provider) => throw new InvalidCastException();
+ decimal IConvertible.ToDecimal(IFormatProvider provider) => Id;
+ double IConvertible.ToDouble(IFormatProvider provider) => Id;
+ short IConvertible.ToInt16(IFormatProvider provider) => throw new InvalidCastException();
+ int IConvertible.ToInt32(IFormatProvider provider) => Id;
+ long IConvertible.ToInt64(IFormatProvider provider) => Id;
+ sbyte IConvertible.ToSByte(IFormatProvider provider) => throw new InvalidCastException();
+ float IConvertible.ToSingle(IFormatProvider provider) => Id;
+ string IConvertible.ToString(IFormatProvider provider) => Id.ToString();
+ object IConvertible.ToType(Type conversionType, IFormatProvider provider) => throw new NotImplementedException();
+ ushort IConvertible.ToUInt16(IFormatProvider provider) => throw new InvalidCastException();
+ uint IConvertible.ToUInt32(IFormatProvider provider) => (uint)Id;
+ ulong IConvertible.ToUInt64(IFormatProvider provider) => (ulong)Id;
+
+ public class MapperTypeHandler : Dapper.SqlMapper.TypeHandler
+ {
+ public override OfficeId Parse(object value)
+ {
+ if (value is null || value is DBNull) throw new InvalidOperationException($"Cannot parse a null value as OfficeId");
+ return new OfficeId(Convert.ToInt32(value));
+ }
+
+ public override void SetValue(IDbDataParameter parameter, OfficeId entityId)
+ {
+ parameter.DbType = DbType.Int32;
+ parameter.Value = entityId.Id;
+ }
+ }
+}
diff --git a/Dapper.SimpleCRUDTests/Program.cs b/Dapper.SimpleCRUDTests/Program.cs
index e4d15ee..286e8f8 100644
--- a/Dapper.SimpleCRUDTests/Program.cs
+++ b/Dapper.SimpleCRUDTests/Program.cs
@@ -57,6 +57,7 @@ private static void Setup()
{
connection.Open();
connection.Execute(@" create table Users (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null, Age int not null, ScheduledDayOff int null, CreatedDate datetime DEFAULT(getdate())) ");
+ connection.Execute(@" create table UsersWithOffice (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null, Age int not null, Office int null) ");
connection.Execute(@" create table Car (CarId int IDENTITY(1,1) not null, Id int null, Make nvarchar(100) not null, Model nvarchar(100) not null) ");
connection.Execute(@" create table BigCar (CarId bigint IDENTITY(2147483650,1) not null, Make nvarchar(100) not null, Model nvarchar(100) not null) ");
connection.Execute(@" create table City (Name nvarchar(100) not null, Population int not null) ");
@@ -72,6 +73,9 @@ private static void Setup()
}
Console.WriteLine("Created database");
+
+ Dapper.SqlMapper.AddTypeHandler(new OfficeId.MapperTypeHandler());
+
}
private static void SetupPg()
@@ -112,6 +116,7 @@ private static void SetupSqLite()
{
connection.Open();
connection.Execute(@" create table Users (Id INTEGER PRIMARY KEY AUTOINCREMENT, Name nvarchar(100) not null, Age int not null, ScheduledDayOff int null, CreatedDate datetime default current_timestamp ) ");
+ connection.Execute(@" create table UsersWithOffice (Id INTEGER PRIMARY KEY AUTOINCREMENT, Name nvarchar(100) not null, Age int not null, Office int null) ");
connection.Execute(@" create table Car (CarId INTEGER PRIMARY KEY AUTOINCREMENT, Id INTEGER null, Make nvarchar(100) not null, Model nvarchar(100) not null) ");
connection.Execute(@" create table BigCar (CarId INTEGER PRIMARY KEY AUTOINCREMENT, Make nvarchar(100) not null, Model nvarchar(100) not null) ");
connection.Execute(@" insert into BigCar (CarId,Make,Model) Values (2147483649,'car','car') ");
diff --git a/Dapper.SimpleCRUDTests/Tests.cs b/Dapper.SimpleCRUDTests/Tests.cs
index e01d033..434f579 100644
--- a/Dapper.SimpleCRUDTests/Tests.cs
+++ b/Dapper.SimpleCRUDTests/Tests.cs
@@ -1,14 +1,12 @@
-using System.ComponentModel;
+using System;
+using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
-using System.Diagnostics;
-using System.Linq;
-using System.Collections.Generic;
-using System;
using System.Data.SQLite;
+using System.Linq;
+using IBM.Data.DB2.Core;
using MySql.Data.MySqlClient;
using Npgsql;
-using IBM.Data.DB2.Core;
namespace Dapper.SimpleCRUDTests
{
@@ -171,6 +169,17 @@ public class UserWithoutAutoIdentity
public string Name { get; set; }
public int Age { get; set; }
}
+
+ [Table("UsersWithOffice")]
+ public class UserWithOffice
+ {
+ [Key]
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public int Age { get; set; }
+ public OfficeId? Office { get; set; }
+ }
+
public class KeyMaster
{
[Key, Required]
@@ -423,6 +432,58 @@ public void TestsGetListWithParameters()
connection.Execute("Delete from Users");
}
}
+
+ public void Tests_Insert_GetList_WithOfficeId()
+ {
+ using (var connection = GetOpenConnection())
+ {
+ connection.Insert(new UserWithOffice { Name = "TestsGetListWithOfficeId1", Office = null });
+ connection.Insert(new UserWithOffice { Name = "TestsGetListWithOfficeId2", Office = new OfficeId(123) });
+ connection.Insert(new UserWithOffice { Name = "TestsGetListWithOfficeId3", Office = new OfficeId(123) });
+ connection.Insert(new UserWithOffice { Name = "TestsGetListWithOfficeId4", Office = new OfficeId(456) });
+
+ var users = connection.GetList(new { Office = new OfficeId(123) }).ToList();
+ users.Count.IsEqualTo(2);
+
+ users[0].Office.HasValue.IsTrue();
+ users[0].Office.Value.Id.IsEqualTo(123);
+
+ connection.Execute("Delete from UsersWithOffice");
+ }
+ }
+
+ public void Tests_Update_Get_WithOfficeId()
+ {
+ int user1Id = 0;
+ using (var connection = GetOpenConnection())
+ {
+ connection.Insert(new UserWithOffice { Name = "TestsGetListWithOfficeId1", Office = null });
+ connection.Insert(new UserWithOffice { Name = "TestsGetListWithOfficeId2", Office = new OfficeId(123) });
+ connection.Insert(new UserWithOffice { Name = "TestsGetListWithOfficeId3", Office = new OfficeId(123) });
+ connection.Insert(new UserWithOffice { Name = "TestsGetListWithOfficeId4", Office = new OfficeId(456) });
+
+ var userCount = connection.RecordCount();
+ userCount.IsEqualTo(4);
+
+ var user1 = connection.GetList(new { Name = "TestsGetListWithOfficeId1" }).FirstOrDefault();
+ user1.Name.IsEqualTo("TestsGetListWithOfficeId1");
+ user1.Office.IsNull();
+ user1Id = user1.Id;
+
+ user1.Office = new OfficeId(999);
+ connection.Update(user1);
+ }
+
+ using (var connection = GetOpenConnection())
+ {
+ var user1again = connection.Get(user1Id);
+ user1again.Name.IsEqualTo("TestsGetListWithOfficeId1");
+ user1again.Office.HasValue.IsTrue();
+ user1again.Office.Value.Id.IsEqualTo(999);
+
+ connection.Execute("Delete from UsersWithOffice");
+ }
+ }
public void TestGetWithReadonlyProperty()
{