From 3843108500e1ea5264425f5653566a72ea306690 Mon Sep 17 00:00:00 2001 From: Marc Sallin Date: Thu, 21 Apr 2016 09:22:34 +0200 Subject: [PATCH 1/4] #27: Added UniqueAttribute. --- .../Attributes/OnConflictAction.cs | 16 ++++++++++++++++ SQLite.CodeFirst/Attributes/UniqueAttribute.cs | 18 ++++++++++++++++++ .../DbInitializers/SqliteInitializerBase.cs | 5 +++++ SQLite.CodeFirst/SQLite.CodeFirst.csproj | 2 ++ 4 files changed, 41 insertions(+) create mode 100644 SQLite.CodeFirst/Attributes/OnConflictAction.cs create mode 100644 SQLite.CodeFirst/Attributes/UniqueAttribute.cs diff --git a/SQLite.CodeFirst/Attributes/OnConflictAction.cs b/SQLite.CodeFirst/Attributes/OnConflictAction.cs new file mode 100644 index 0000000..e2df5b0 --- /dev/null +++ b/SQLite.CodeFirst/Attributes/OnConflictAction.cs @@ -0,0 +1,16 @@ +namespace SQLite.CodeFirst +{ + /// + /// The action to resolve a UNIQUE constraint violation. + /// Is used together with the . + /// + public enum OnConflictAction + { + None, + Rollback, + Abort, + Fail, + Ignore, + Replace + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Attributes/UniqueAttribute.cs b/SQLite.CodeFirst/Attributes/UniqueAttribute.cs new file mode 100644 index 0000000..913dcde --- /dev/null +++ b/SQLite.CodeFirst/Attributes/UniqueAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace SQLite.CodeFirst +{ + /// + /// The UNIQUE Constraint prevents two records from having identical values in a particular column. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class UniqueAttribute : Attribute + { + public UniqueAttribute(OnConflictAction onConflict = OnConflictAction.None) + { + OnConflict = onConflict; + } + + public OnConflictAction OnConflict { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/DbInitializers/SqliteInitializerBase.cs b/SQLite.CodeFirst/DbInitializers/SqliteInitializerBase.cs index b063412..eee4db8 100644 --- a/SQLite.CodeFirst/DbInitializers/SqliteInitializerBase.cs +++ b/SQLite.CodeFirst/DbInitializers/SqliteInitializerBase.cs @@ -3,6 +3,7 @@ using System.Data.Entity.ModelConfiguration.Conventions; using SQLite.CodeFirst.Convention; using System.IO; +using System.Linq; using SQLite.CodeFirst.Utility; namespace SQLite.CodeFirst @@ -36,6 +37,10 @@ protected SqliteInitializerBase(DbModelBuilder modelBuilder) // See https://github.com/msallin/SQLiteCodeFirst/issues/7 for details. modelBuilder.Conventions.Remove(); + modelBuilder.Properties() + .Having(x => x.GetCustomAttributes(false).OfType().FirstOrDefault()) + .Configure((config, attribute) => config.HasColumnAnnotation("IsUnique", attribute)); + // By default there is a 'ForeignKeyIndexConvention' but it can be removed. // And there is no "Contains" and no way to enumerate the ConventionsCollection. // So a try/catch will do the job. diff --git a/SQLite.CodeFirst/SQLite.CodeFirst.csproj b/SQLite.CodeFirst/SQLite.CodeFirst.csproj index 1f1c951..b5fb745 100644 --- a/SQLite.CodeFirst/SQLite.CodeFirst.csproj +++ b/SQLite.CodeFirst/SQLite.CodeFirst.csproj @@ -83,6 +83,8 @@ Properties\AssemblySharedInfo.cs + + From 20e2f501ac0cd7ded03e90d5176b0e591ffc3b58 Mon Sep 17 00:00:00 2001 From: Marc Sallin Date: Thu, 21 Apr 2016 09:23:12 +0200 Subject: [PATCH 2/4] #27: Added UniqueConstraint and tests for it. --- .../SQLite.CodeFirst.Test.csproj | 1 + .../ColumnConstraint/UniqueConstraintTest.cs | 27 +++++++++++++++++++ .../ColumnConstraint/UniqueConstraint.cs | 20 ++++++++++++++ SQLite.CodeFirst/SQLite.CodeFirst.csproj | 1 + 4 files changed, 49 insertions(+) create mode 100644 SQLite.CodeFirst.Test/Statement/ColumnConstraint/UniqueConstraintTest.cs create mode 100644 SQLite.CodeFirst/Internal/Statement/ColumnConstraint/UniqueConstraint.cs diff --git a/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj b/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj index a22972a..dfb57d2 100644 --- a/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj +++ b/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj @@ -80,6 +80,7 @@ + diff --git a/SQLite.CodeFirst.Test/Statement/ColumnConstraint/UniqueConstraintTest.cs b/SQLite.CodeFirst.Test/Statement/ColumnConstraint/UniqueConstraintTest.cs new file mode 100644 index 0000000..198c9cb --- /dev/null +++ b/SQLite.CodeFirst.Test/Statement/ColumnConstraint/UniqueConstraintTest.cs @@ -0,0 +1,27 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Statement.ColumnConstraint; + +namespace SQLite.CodeFirst.Test.Statement.ColumnConstraint +{ + [TestClass] + public class UniqueConstraintTest + { + [TestMethod] + public void CreateStatement_StatementIsCorrect_NoConstraint() + { + var uniqueConstraint = new UniqueConstraint(); + uniqueConstraint.OnConflict = OnConflictAction.None; + string output = uniqueConstraint.CreateStatement(); + Assert.AreEqual(output, "UNIQUE"); + } + + [TestMethod] + public void CreateStatement_StatementIsCorrect_WithConstraint() + { + var uniqueConstraint = new UniqueConstraint(); + uniqueConstraint.OnConflict = OnConflictAction.Rollback; + string output = uniqueConstraint.CreateStatement(); + Assert.AreEqual(output, "UNIQUE ON CONFLICT ROLLBACK"); + } + } +} diff --git a/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/UniqueConstraint.cs b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/UniqueConstraint.cs new file mode 100644 index 0000000..7859686 --- /dev/null +++ b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/UniqueConstraint.cs @@ -0,0 +1,20 @@ +using System.Text; + +namespace SQLite.CodeFirst.Statement.ColumnConstraint +{ + internal class UniqueConstraint : IColumnConstraint + { + private const string Template = "UNIQUE {conflict-clause}"; + + public OnConflictAction OnConflict { get; set; } + + public string CreateStatement() + { + var sb = new StringBuilder(Template); + + sb.Replace("{conflict-clause}", OnConflict != OnConflictAction.None ? "ON CONFLICT " + OnConflict.ToString().ToUpperInvariant() : string.Empty); + + return sb.ToString().Trim(); + } + } +} diff --git a/SQLite.CodeFirst/SQLite.CodeFirst.csproj b/SQLite.CodeFirst/SQLite.CodeFirst.csproj index b5fb745..5392970 100644 --- a/SQLite.CodeFirst/SQLite.CodeFirst.csproj +++ b/SQLite.CodeFirst/SQLite.CodeFirst.csproj @@ -89,6 +89,7 @@ + From e4845b4b2275f2d46ffcfa78767a35da4bf45a39 Mon Sep 17 00:00:00 2001 From: Marc Sallin Date: Thu, 21 Apr 2016 09:23:39 +0200 Subject: [PATCH 3/4] #27: Use UniqueConstraint in the ColumnStatementCollectionBuilder. --- .../Builder/ColumnStatementCollectionBuilder.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs b/SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs index ca8d8b1..5a568b7 100644 --- a/SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs +++ b/SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs @@ -36,6 +36,7 @@ private IEnumerable CreateColumnStatements() AddMaxLengthConstraintIfNecessary(property, columnStatement); AdjustDatatypeForAutogenerationIfNecessary(property, columnStatement); AddNullConstraintIfNecessary(property, columnStatement); + AddUniqueConstraintIfNecessary(property, columnStatement); yield return columnStatement; } @@ -66,5 +67,19 @@ private static void AddNullConstraintIfNecessary(EdmProperty property, ColumnSta columnStatement.ColumnConstraints.Add(new NotNullConstraint()); } } + + private static void AddUniqueConstraintIfNecessary(EdmProperty property, ColumnStatement columnStatement) + { + MetadataProperty item; + bool found = property.MetadataProperties.TryGetValue("http://schemas.microsoft.com/ado/2013/11/edm/customannotation:IsUnique", true, out item); + if (found) + { + var value = (UniqueAttribute)item.Value; + columnStatement.ColumnConstraints.Add(new UniqueConstraint + { + OnConflict = value.OnConflict + }); + } + } } } From 2bf01214edf5c9a07411e07fdc3d6ddbff19f06e Mon Sep 17 00:00:00 2001 From: Marc Sallin Date: Thu, 21 Apr 2016 09:34:09 +0200 Subject: [PATCH 4/4] #27: Added sample code and updated readme. --- README.md | 1 + SQLite.CodeFirst.Console/Entity/Player.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 5cf4dc6..c6caeb2 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Currently the following is supported: - Not Null constraint - Auto increment (An int PrimaryKey will automatically be incremented) - 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`) +- Unique constraint (Decorate columsn with the `UniqueAttribute` which is part of this library) I tried to write the code in a extensible way. The logic is divided into two main parts, Builder and Statement. diff --git a/SQLite.CodeFirst.Console/Entity/Player.cs b/SQLite.CodeFirst.Console/Entity/Player.cs index 8e69fab..e2177cf 100644 --- a/SQLite.CodeFirst.Console/Entity/Player.cs +++ b/SQLite.CodeFirst.Console/Entity/Player.cs @@ -6,6 +6,7 @@ namespace SQLite.CodeFirst.Console.Entity public class Player : Person { [Index] // Automatically named 'IX_TeamPlayer_Number' + [Unique(OnConflictAction.Fail)] public int Number { get; set; } public virtual Team Team { get; set; }