Skip to content

Commit 1c12d90

Browse files
committed
Merge pull request #61 from msallin/Issue-27
Issue 27
2 parents 4f06cfd + 2bf0121 commit 1c12d90

File tree

10 files changed

+107
-0
lines changed

10 files changed

+107
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Currently the following is supported:
2222
- Not Null constraint
2323
- Auto increment (An int PrimaryKey will automatically be incremented)
2424
- Index (Decorate columns with the `Index` attribute. Indices are automatically created for foreign keys by default. To prevent this you can remove the convetion `ForeignKeyIndexConvention`)
25+
- Unique constraint (Decorate columsn with the `UniqueAttribute` which is part of this library)
2526

2627
I tried to write the code in a extensible way.
2728
The logic is divided into two main parts, Builder and Statement.

SQLite.CodeFirst.Console/Entity/Player.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace SQLite.CodeFirst.Console.Entity
66
public class Player : Person
77
{
88
[Index] // Automatically named 'IX_TeamPlayer_Number'
9+
[Unique(OnConflictAction.Fail)]
910
public int Number { get; set; }
1011

1112
public virtual Team Team { get; set; }

SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
<Compile Include="Statement\ColumnConstraint\MaxLengthConstraint.cs" />
8181
<Compile Include="Statement\ColumnConstraint\ColumnConstraintCollectionTest.cs" />
8282
<Compile Include="Statement\ColumnConstraint\NotNullConstraintTest.cs" />
83+
<Compile Include="Statement\ColumnConstraint\UniqueConstraintTest.cs" />
8384
<Compile Include="Statement\ColumnStatementCollectionTest.cs" />
8485
<Compile Include="Statement\PrimaryKeyStatementTest.cs" />
8586
<Compile Include="Statement\CreateDatabaseStatementTest.cs" />
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Microsoft.VisualStudio.TestTools.UnitTesting;
2+
using SQLite.CodeFirst.Statement.ColumnConstraint;
3+
4+
namespace SQLite.CodeFirst.Test.Statement.ColumnConstraint
5+
{
6+
[TestClass]
7+
public class UniqueConstraintTest
8+
{
9+
[TestMethod]
10+
public void CreateStatement_StatementIsCorrect_NoConstraint()
11+
{
12+
var uniqueConstraint = new UniqueConstraint();
13+
uniqueConstraint.OnConflict = OnConflictAction.None;
14+
string output = uniqueConstraint.CreateStatement();
15+
Assert.AreEqual(output, "UNIQUE");
16+
}
17+
18+
[TestMethod]
19+
public void CreateStatement_StatementIsCorrect_WithConstraint()
20+
{
21+
var uniqueConstraint = new UniqueConstraint();
22+
uniqueConstraint.OnConflict = OnConflictAction.Rollback;
23+
string output = uniqueConstraint.CreateStatement();
24+
Assert.AreEqual(output, "UNIQUE ON CONFLICT ROLLBACK");
25+
}
26+
}
27+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace SQLite.CodeFirst
2+
{
3+
/// <summary>
4+
/// The action to resolve a UNIQUE constraint violation.
5+
/// Is used together with the <see cref="UniqueAttribute"/>.
6+
/// </summary>
7+
public enum OnConflictAction
8+
{
9+
None,
10+
Rollback,
11+
Abort,
12+
Fail,
13+
Ignore,
14+
Replace
15+
}
16+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
3+
namespace SQLite.CodeFirst
4+
{
5+
/// <summary>
6+
/// The UNIQUE Constraint prevents two records from having identical values in a particular column.
7+
/// </summary>
8+
[AttributeUsage(AttributeTargets.Property)]
9+
public sealed class UniqueAttribute : Attribute
10+
{
11+
public UniqueAttribute(OnConflictAction onConflict = OnConflictAction.None)
12+
{
13+
OnConflict = onConflict;
14+
}
15+
16+
public OnConflictAction OnConflict { get; set; }
17+
}
18+
}

SQLite.CodeFirst/DbInitializers/SqliteInitializerBase.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Data.Entity.ModelConfiguration.Conventions;
44
using SQLite.CodeFirst.Convention;
55
using System.IO;
6+
using System.Linq;
67
using SQLite.CodeFirst.Utility;
78

89
namespace SQLite.CodeFirst
@@ -36,6 +37,10 @@ protected SqliteInitializerBase(DbModelBuilder modelBuilder)
3637
// See https://github.com/msallin/SQLiteCodeFirst/issues/7 for details.
3738
modelBuilder.Conventions.Remove<TimestampAttributeConvention>();
3839

40+
modelBuilder.Properties()
41+
.Having(x => x.GetCustomAttributes(false).OfType<UniqueAttribute>().FirstOrDefault())
42+
.Configure((config, attribute) => config.HasColumnAnnotation("IsUnique", attribute));
43+
3944
// By default there is a 'ForeignKeyIndexConvention' but it can be removed.
4045
// And there is no "Contains" and no way to enumerate the ConventionsCollection.
4146
// So a try/catch will do the job.

SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ private IEnumerable<ColumnStatement> CreateColumnStatements()
3636
AddMaxLengthConstraintIfNecessary(property, columnStatement);
3737
AdjustDatatypeForAutogenerationIfNecessary(property, columnStatement);
3838
AddNullConstraintIfNecessary(property, columnStatement);
39+
AddUniqueConstraintIfNecessary(property, columnStatement);
3940

4041
yield return columnStatement;
4142
}
@@ -66,5 +67,19 @@ private static void AddNullConstraintIfNecessary(EdmProperty property, ColumnSta
6667
columnStatement.ColumnConstraints.Add(new NotNullConstraint());
6768
}
6869
}
70+
71+
private static void AddUniqueConstraintIfNecessary(EdmProperty property, ColumnStatement columnStatement)
72+
{
73+
MetadataProperty item;
74+
bool found = property.MetadataProperties.TryGetValue("http://schemas.microsoft.com/ado/2013/11/edm/customannotation:IsUnique", true, out item);
75+
if (found)
76+
{
77+
var value = (UniqueAttribute)item.Value;
78+
columnStatement.ColumnConstraints.Add(new UniqueConstraint
79+
{
80+
OnConflict = value.OnConflict
81+
});
82+
}
83+
}
6984
}
7085
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Text;
2+
3+
namespace SQLite.CodeFirst.Statement.ColumnConstraint
4+
{
5+
internal class UniqueConstraint : IColumnConstraint
6+
{
7+
private const string Template = "UNIQUE {conflict-clause}";
8+
9+
public OnConflictAction OnConflict { get; set; }
10+
11+
public string CreateStatement()
12+
{
13+
var sb = new StringBuilder(Template);
14+
15+
sb.Replace("{conflict-clause}", OnConflict != OnConflictAction.None ? "ON CONFLICT " + OnConflict.ToString().ToUpperInvariant() : string.Empty);
16+
17+
return sb.ToString().Trim();
18+
}
19+
}
20+
}

SQLite.CodeFirst/SQLite.CodeFirst.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,13 @@
8383
<Compile Include="..\Shared\AssemblySharedInfo.cs">
8484
<Link>Properties\AssemblySharedInfo.cs</Link>
8585
</Compile>
86+
<Compile Include="Attributes\OnConflictAction.cs" />
87+
<Compile Include="Attributes\UniqueAttribute.cs" />
8688
<Compile Include="Entities\IHistory.cs" />
8789
<Compile Include="Internal\Builder\NameCreators\IndexNameCreator.cs" />
8890
<Compile Include="Internal\Builder\NameCreators\SpecialChars.cs" />
8991
<Compile Include="IDatabaseCreator.cs" />
92+
<Compile Include="Internal\Statement\ColumnConstraint\UniqueConstraint.cs" />
9093
<Compile Include="Internal\Utility\HashCreator.cs" />
9194
<Compile Include="Internal\Utility\HistoryEntityTypeValidator.cs" />
9295
<Compile Include="ISqliteSqlGenerator.cs" />

0 commit comments

Comments
 (0)