Skip to content

Allowing custom attributes to determine Primary Key, Auto Increment, etc. #298

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

Merged
merged 5 commits into from
May 22, 2016
Merged
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
88 changes: 88 additions & 0 deletions src/SQLite.Net/Attributes/DefaultColumnInformationProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using System.Linq;
using System.Reflection;
using SQLite.Net.Attributes;
using System.Collections.Generic;

namespace SQLite.Net
{
public class DefaultColumnInformationProvider : IColumnInformationProvider
{
#region IColumnInformationProvider implementation

public string GetColumnName(PropertyInfo p)
{
var colAttr = p.GetCustomAttributes<ColumnAttribute>(true).FirstOrDefault();
return colAttr == null ? p.Name : colAttr.Name;
}

public bool IsIgnored(PropertyInfo p)
{
return p.IsDefined(typeof (IgnoreAttribute), true);
}

public IEnumerable<IndexedAttribute> GetIndices(MemberInfo p)
{
return p.GetCustomAttributes<IndexedAttribute>();
}

public bool IsPK(MemberInfo m)
{
return m.GetCustomAttributes<PrimaryKeyAttribute>().Any();
}

public string Collation(MemberInfo m)
{
foreach (var attribute in m.GetCustomAttributes<CollationAttribute>())
{
return attribute.Value;
}
return string.Empty;
}

public bool IsAutoInc(MemberInfo m)
{
return m.GetCustomAttributes<AutoIncrementAttribute>().Any();
}

public int? MaxStringLength(PropertyInfo p)
{
foreach (var attribute in p.GetCustomAttributes<MaxLengthAttribute>())
{
return attribute.Value;
}
return null;
}

public object GetDefaultValue(PropertyInfo p)
{
foreach (var attribute in p.GetCustomAttributes<DefaultAttribute>())
{
try
{
if (!attribute.UseProperty)
{
return Convert.ChangeType(attribute.Value, p.PropertyType);
}

var obj = Activator.CreateInstance(p.DeclaringType);
return p.GetValue(obj);
}
catch (Exception exception)
{
throw new Exception("Unable to convert " + attribute.Value + " to type " + p.PropertyType, exception);
}
}
return null;
}

public bool IsMarkedNotNull(MemberInfo p)
{
var attrs = p.GetCustomAttributes<NotNullAttribute>(true);
return attrs.Any();
}

#endregion
}
}

20 changes: 20 additions & 0 deletions src/SQLite.Net/Attributes/IColumnInformationProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Reflection;
using System.Collections.Generic;
using SQLite.Net.Attributes;

namespace SQLite.Net
{
public interface IColumnInformationProvider
{
bool IsPK(MemberInfo m);
string Collation(MemberInfo m);
bool IsAutoInc(MemberInfo m);
int? MaxStringLength(PropertyInfo p);
IEnumerable<IndexedAttribute> GetIndices(MemberInfo p);
object GetDefaultValue(PropertyInfo p);
bool IsMarkedNotNull(MemberInfo p);
bool IsIgnored(PropertyInfo p);
string GetColumnName(PropertyInfo p);
}
}

51 changes: 16 additions & 35 deletions src/SQLite.Net/Orm.cs
Original file line number Diff line number Diff line change
@@ -36,8 +36,15 @@ internal static class Orm
public const string ImplicitPkName = "Id";
public const string ImplicitIndexSuffix = "Id";

internal static string SqlDecl(TableMapping.Column p, bool storeDateTimeAsTicks, IBlobSerializer serializer,
IDictionary<Type, string> extraTypeMappings)
private static IColumnInformationProvider _columnInformationProvider = new DefaultColumnInformationProvider();
public static IColumnInformationProvider ColumnInformationProvider
{
get { return _columnInformationProvider; }
set { _columnInformationProvider = value; }
}

internal static string SqlDecl(TableMapping.Column p, bool storeDateTimeAsTicks, IBlobSerializer serializer,
IDictionary<Type, string> extraTypeMappings)
{
var decl = "\"" + p.Name + "\" " + SqlType(p, storeDateTimeAsTicks, serializer, extraTypeMappings) + " ";

@@ -144,65 +151,39 @@ private static string SqlType(TableMapping.Column p, bool storeDateTimeAsTicks,

internal static bool IsPK(MemberInfo p)
{
return p.GetCustomAttributes<PrimaryKeyAttribute>().Any();
return ColumnInformationProvider.IsPK (p);
}

internal static string Collation(MemberInfo p)
{
foreach (var attribute in p.GetCustomAttributes<CollationAttribute>())
{
return attribute.Value;
}
return string.Empty;
return ColumnInformationProvider.Collation (p);
}

internal static bool IsAutoInc(MemberInfo p)
{
return p.GetCustomAttributes<AutoIncrementAttribute>().Any();
return ColumnInformationProvider.IsAutoInc (p);
}

internal static IEnumerable<IndexedAttribute> GetIndices(MemberInfo p)
{
return p.GetCustomAttributes<IndexedAttribute>();
return ColumnInformationProvider.GetIndices (p);
}

[CanBeNull]
internal static int? MaxStringLength(PropertyInfo p)
{
foreach (var attribute in p.GetCustomAttributes<MaxLengthAttribute>())
{
return attribute.Value;
}
return null;
return ColumnInformationProvider.MaxStringLength (p);
}

[CanBeNull]
internal static object GetDefaultValue(PropertyInfo p)
{
foreach (var attribute in p.GetCustomAttributes<DefaultAttribute>())
{
try
{
if (!attribute.UseProperty)
{
return Convert.ChangeType(attribute.Value, p.PropertyType);
}

var obj = Activator.CreateInstance(p.DeclaringType);
return p.GetValue(obj);
}
catch (Exception exception)
{
throw new Exception("Unable to convert " + attribute.Value + " to type " + p.PropertyType, exception);
}
}
return null;
return ColumnInformationProvider.GetDefaultValue (p);
}

internal static bool IsMarkedNotNull(MemberInfo p)
{
var attrs = p.GetCustomAttributes<NotNullAttribute>(true);
return attrs.Any();
return ColumnInformationProvider.IsMarkedNotNull (p);
}
}
}
2 changes: 2 additions & 0 deletions src/SQLite.Net/SQLite.Net.csproj
Original file line number Diff line number Diff line change
@@ -116,6 +116,8 @@
<Compile Include="DebugTraceListener.cs" />
<Compile Include="ISerializable.cs" />
<Compile Include="Interop\IDbBackupHandle.cs" />
<Compile Include="Attributes\IColumnInformationProvider.cs" />
<Compile Include="Attributes\DefaultColumnInformationProvider.cs" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
18 changes: 15 additions & 3 deletions src/SQLite.Net/SQLiteConnection.cs
Original file line number Diff line number Diff line change
@@ -186,6 +186,18 @@ public SQLiteConnection([JetBrains.Annotations.NotNull] ISQLitePlatform sqlitePl
BusyTimeout = TimeSpan.FromSeconds(0.1);
}

private IColumnInformationProvider _columnInformationProvider;
[CanBeNull, PublicAPI]
public IColumnInformationProvider ColumnInformationProvider
{
get { return _columnInformationProvider; }
set
{
_columnInformationProvider = value;
Orm.ColumnInformationProvider = _columnInformationProvider ?? new DefaultColumnInformationProvider ();
}
}

[CanBeNull, PublicAPI]
public IBlobSerializer Serializer { get; private set; }

@@ -311,9 +323,9 @@ public TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags.
private TableMapping CreateAndSetMapping(Type type, CreateFlags createFlags, IDictionary<string, TableMapping> mapTable)
{
var props = Platform.ReflectionService.GetPublicInstanceProperties(type);
var map = new TableMapping(type, props, createFlags);
mapTable[type.FullName] = map;
return map;
var map = new TableMapping(type, props, createFlags, _columnInformationProvider);
mapTable[type.FullName] = map;
return map;
}

/// <summary>
19 changes: 13 additions & 6 deletions src/SQLite.Net/TableMapping.cs
Original file line number Diff line number Diff line change
@@ -36,8 +36,13 @@ public class TableMapping
private Column[] _insertColumns;

[PublicAPI]
public TableMapping(Type type, IEnumerable<PropertyInfo> properties, CreateFlags createFlags = CreateFlags.None)
public TableMapping(Type type, IEnumerable<PropertyInfo> properties, CreateFlags createFlags = CreateFlags.None, IColumnInformationProvider infoProvider = null)
{
if (infoProvider == null)
{
infoProvider = new DefaultColumnInformationProvider ();
}

MappedType = type;

var tableAttr = type.GetTypeInfo().GetCustomAttributes<TableAttribute>().FirstOrDefault();
@@ -49,7 +54,7 @@ public TableMapping(Type type, IEnumerable<PropertyInfo> properties, CreateFlags
var cols = new List<Column>();
foreach (var p in props)
{
var ignore = p.IsDefined(typeof (IgnoreAttribute), true);
var ignore = infoProvider.IsIgnored (p);

if (p.CanWrite && !ignore)
{
@@ -134,13 +139,15 @@ public class Column
private readonly PropertyInfo _prop;

[PublicAPI]
public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None)
public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None, IColumnInformationProvider infoProvider = null)
{
var colAttr =
prop.GetCustomAttributes<ColumnAttribute>(true).FirstOrDefault();
if (infoProvider == null)
{
infoProvider = new DefaultColumnInformationProvider();
}

_prop = prop;
Name = colAttr == null ? prop.Name : colAttr.Name;
Name = infoProvider.GetColumnName(prop);
//If this type is Nullable<T> then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the actual type instead
ColumnType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
Collation = Orm.Collation(prop);
103 changes: 102 additions & 1 deletion tests/DefaulAttributeTest.cs
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ public class DefaulAttributeTest
{
private class WithDefaultValue
{

public const string CustomAttributeDefaultValue = "12345";
public const int IntVal = 666;
public static decimal DecimalVal = 666.666m;
public static string StringVal = "Working String";
@@ -60,6 +60,90 @@ public Default666Attribute() :base(usePropertyValue:false, value:IntVal)

}

private class TestDefaultValueAttribute : Attribute
{
public string DefaultValue { get; private set; }

public TestDefaultValueAttribute(string defaultValue)
{
DefaultValue = defaultValue;
}
}

public class TestColumnInformationProvider : IColumnInformationProvider
{
public string GetColumnName(PropertyInfo p)
{
return p.Name;
}

public bool IsIgnored(PropertyInfo p)
{
return false;
}

public IEnumerable<IndexedAttribute> GetIndices(MemberInfo p)
{
return p.GetCustomAttributes<IndexedAttribute>();
}

public bool IsPK(MemberInfo m)
{
return m.GetCustomAttributes<PrimaryKeyAttribute>().Any();
}
public string Collation(MemberInfo m)
{
return string.Empty;
}
public bool IsAutoInc(MemberInfo m)
{
return false;
}
public int? MaxStringLength(PropertyInfo p)
{
return null;
}
public object GetDefaultValue(PropertyInfo p)
{
var defaultValueAttributes = p.GetCustomAttributes<TestDefaultValueAttribute> ();
if (!defaultValueAttributes.Any())
{
return null;
}

return defaultValueAttributes.First().DefaultValue;
}
public bool IsMarkedNotNull(MemberInfo p)
{
return false;
}
}

public abstract class TestObjBase<T>
{
[AutoIncrement, PrimaryKey]
public int Id { get; set; }

public T Data { get; set; }

}

public class TestObjIntWithDefaultValue : TestObjBase<int>
{
[TestDefaultValue("12345")]
public string SomeValue { get; set; }
}

public class TestDbWithCustomAttributes : SQLiteConnection
{
public TestDbWithCustomAttributes(String path)
: base(new SQLitePlatformTest(), path)
{
ColumnInformationProvider = new TestColumnInformationProvider();
CreateTable<TestObjIntWithDefaultValue>();
}
}

[Test]
public void TestColumnValues()
{
@@ -96,5 +180,22 @@ public void TestColumnValues()

}
}

[Test]
public void TestCustomDefaultColumnValues()
{
using (var db = new TestDbWithCustomAttributes(TestPath.CreateTemporaryDatabase()))
{
string failed = string.Empty;
foreach (var col in db.GetMapping<TestObjIntWithDefaultValue>().Columns)
{
if (col.PropertyName == "SomeValue" && !col.DefaultValue.Equals(WithDefaultValue.CustomAttributeDefaultValue))
failed += " , SomeValue does not equal " + WithDefaultValue.CustomAttributeDefaultValue;
}

Assert.True(string.IsNullOrWhiteSpace(failed), failed);
db.ColumnInformationProvider = null;
}
}
}
}
74 changes: 74 additions & 0 deletions tests/IgnoreTest.cs
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@
using System.Linq;
using NUnit.Framework;
using SQLite.Net.Attributes;
using System;
using System.Reflection;

namespace SQLite.Net.Tests
{
@@ -20,12 +22,84 @@ public class DummyClass
public List<string> Ignored { get; set; }
}

private class TestIgnoreAttribute : Attribute
{
}

public class TestColumnInformationProvider : IColumnInformationProvider
{
public string GetColumnName(PropertyInfo p)
{
return p.Name;
}

public bool IsIgnored(PropertyInfo p)
{
return p.IsDefined(typeof (TestIgnoreAttribute), true);
}

public IEnumerable<IndexedAttribute> GetIndices(MemberInfo p)
{
return p.GetCustomAttributes<IndexedAttribute>();
}

public bool IsPK(MemberInfo m)
{
return m.GetCustomAttributes<PrimaryKeyAttribute>().Any();
}
public string Collation(MemberInfo m)
{
return string.Empty;
}
public bool IsAutoInc(MemberInfo m)
{
return false;
}
public int? MaxStringLength(PropertyInfo p)
{
return null;
}
public object GetDefaultValue(PropertyInfo p)
{
return null;
}
public bool IsMarkedNotNull(MemberInfo p)
{
return false;
}
}

public abstract class TestObjBase<T>
{
[AutoIncrement, PrimaryKey]
public int Id { get; set; }

public T Data { get; set; }

}

public class TestObjIntWithIgnore : TestObjBase<int>
{
[TestIgnore]
public List<string> Ignored { get; set; }
}

[Test]
public void NullableFloat()
{
var db = new SQLiteConnection(new SQLitePlatformTest(), TestPath.CreateTemporaryDatabase());
// if the Ignored property is not ignore this will cause an exception
db.CreateTable<DummyClass>();
}

[Test]
public void CustomIgnoreAttributeTest()
{
var db = new SQLiteConnection(new SQLitePlatformTest(), TestPath.CreateTemporaryDatabase());
db.ColumnInformationProvider = new TestColumnInformationProvider();
// if the Ignored property is not ignore this will cause an exception
db.CreateTable<TestObjIntWithIgnore>();
db.ColumnInformationProvider = null;
}
}
}
32 changes: 12 additions & 20 deletions tests/SQLite.Net.Tests.Generic/SQLite.Net.Tests.Generic.csproj
Original file line number Diff line number Diff line change
@@ -30,42 +30,34 @@
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Threading.Tasks.Extensions, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Reference Include="Microsoft.Threading.Tasks.Extensions, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
<HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<Reference Include="nunit.framework, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77">
<HintPath>..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PCLStorage, Version=1.0.2.0, Culture=neutral, PublicKeyToken=286fe515a2c35b64, processorArchitecture=MSIL">
<Reference Include="PCLStorage, Version=1.0.2.0, Culture=neutral, PublicKeyToken=286fe515a2c35b64">
<HintPath>..\..\packages\PCLStorage.1.0.2\lib\net45\PCLStorage.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PCLStorage.Abstractions, Version=1.0.2.0, Culture=neutral, PublicKeyToken=286fe515a2c35b64, processorArchitecture=MSIL">
<Reference Include="PCLStorage.Abstractions, Version=1.0.2.0, Culture=neutral, PublicKeyToken=286fe515a2c35b64">
<HintPath>..\..\packages\PCLStorage.1.0.2\lib\net45\PCLStorage.Abstractions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data.SQLite, Version=1.0.98.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
<Reference Include="System.Data.SQLite, Version=1.0.98.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139">
<HintPath>..\..\packages\System.Data.SQLite.Core.1.0.98.1\lib\net45\System.Data.SQLite.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />