Skip to content
Open
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
59 changes: 31 additions & 28 deletions Dapper.SimpleCRUD/SimpleCRUD.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
Expand Down Expand Up @@ -55,7 +55,7 @@ private static void StringBuilderCache(StringBuilder sb, string cacheKey, Action
StringBuilderCacheDict.AddOrUpdate(cacheKey, value, (t, v) => value);
sb.Append(value);
}

/// <summary>
/// Returns the current dialect name
/// </summary>
Expand Down Expand Up @@ -857,43 +857,38 @@ private static IEnumerable<PropertyInfo> GetScaffoldableProperties<T>()

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:
Expand Down Expand Up @@ -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<PropertyInfo> props)
{
return string.Join(",",props.Select(p=> p.DeclaringType.FullName + "." + p.Name).ToArray());
Expand Down
40 changes: 40 additions & 0 deletions Dapper.SimpleCRUDTests/Models/OfficeId.cs
Original file line number Diff line number Diff line change
@@ -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<OfficeId>
{
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;
}
}
}
5 changes: 5 additions & 0 deletions Dapper.SimpleCRUDTests/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) ");
Expand All @@ -72,6 +73,9 @@ private static void Setup()

}
Console.WriteLine("Created database");

Dapper.SqlMapper.AddTypeHandler(new OfficeId.MapperTypeHandler());

}

private static void SetupPg()
Expand Down Expand Up @@ -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') ");
Expand Down
73 changes: 67 additions & 6 deletions Dapper.SimpleCRUDTests/Tests.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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<UserWithOffice>(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<UserWithOffice>();
userCount.IsEqualTo(4);

var user1 = connection.GetList<UserWithOffice>(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<UserWithOffice>(user1Id);
user1again.Name.IsEqualTo("TestsGetListWithOfficeId1");
user1again.Office.HasValue.IsTrue();
user1again.Office.Value.Id.IsEqualTo(999);

connection.Execute("Delete from UsersWithOffice");
}
}

public void TestGetWithReadonlyProperty()
{
Expand Down