diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..0901f69 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: msallin # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] diff --git a/.gitignore b/.gitignore index f8c1427..1ae906a 100644 --- a/.gitignore +++ b/.gitignore @@ -155,7 +155,6 @@ ClientBin/ *~ *.dbmdl *.dbproj.schemaview -*.pfx *.publishsettings node_modules/ @@ -184,3 +183,4 @@ FakesAssemblies/ # Custom *.GhostDoc.xml +/.vs/* diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config index 67f8ea0..3f0e003 100644 --- a/.nuget/NuGet.Config +++ b/.nuget/NuGet.Config @@ -1,6 +1,6 @@  - - - + + + \ No newline at end of file diff --git a/.nuget/NuGet.exe b/.nuget/NuGet.exe deleted file mode 100644 index 8dd7e45..0000000 Binary files a/.nuget/NuGet.exe and /dev/null differ diff --git a/.nuget/NuGet.targets b/.nuget/NuGet.targets deleted file mode 100644 index 3f8c37b..0000000 --- a/.nuget/NuGet.targets +++ /dev/null @@ -1,144 +0,0 @@ - - - - $(MSBuildProjectDirectory)\..\ - - - false - - - false - - - true - - - false - - - - - - - - - - - $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) - - - - - $(SolutionDir).nuget - - - - $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config - $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config - - - - $(MSBuildProjectDirectory)\packages.config - $(PackagesProjectConfig) - - - - - $(NuGetToolsPath)\NuGet.exe - @(PackageSource) - - "$(NuGetExePath)" - mono --runtime=v4.0.30319 "$(NuGetExePath)" - - $(TargetDir.Trim('\\')) - - -RequireConsent - -NonInteractive - - "$(SolutionDir) " - "$(SolutionDir)" - - - $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) - $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols - - - - RestorePackages; - $(BuildDependsOn); - - - - - $(BuildDependsOn); - BuildPackage; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/BuildAllConfigurations.proj b/BuildAllConfigurations.proj deleted file mode 100644 index 3a31715..0000000 --- a/BuildAllConfigurations.proj +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - %(PlatformList.Identity) - - - - - \ No newline at end of file diff --git a/LICENSE b/LICENSE index e06d208..44b57ce 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright 2015 Marc Sallin Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Lib/Moq.dll b/Lib/Moq.dll deleted file mode 100644 index f5b1545..0000000 Binary files a/Lib/Moq.dll and /dev/null differ diff --git a/README.md b/README.md index 5058ff8..63d7eb2 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,77 @@ # SQLite CodeFirst + **Release Build** [![Build status](https://ci.appveyor.com/api/projects/status/2qavdqctw0ehscm6/branch/master?svg=true)](https://ci.appveyor.com/project/msallin/sqlitecodefirst-nv6vn/branch/master) **CI Build** [![Build status](https://ci.appveyor.com/api/projects/status/oc1miog385h801qe?svg=true)](https://ci.appveyor.com/project/msallin/sqlitecodefirst) Creates a [SQLite Database](https://sqlite.org/) from Code, using [Entity Framework](https://msdn.microsoft.com/en-us/data/ef.aspx) CodeFirst. -This Project ships several `IDbInitializer` classes. These create new SQLite Databases based on your model/code. -I started with the [code](https://gist.github.com/flaub/1968486e1b3f2b9fddaf) from [flaub](https://github.com/flaub). +## Support the project PayPal donate button + +To support this project you can: *star the repository*, report bugs/request features by creating new issues, write code and create PRs or donate. +Especially if you use it for a commercial project, a donation is welcome. +If you need a specific feature for a commercial project, I am glad to offer a paid implementation. + +## Project Status + +This project was started when there was .NET Full Framework and EF 6. EF 6 does not offer code first for SQLite and this library fills this gab. +Nowadays there is .NET Core (or now just called .NET) and EF Core. EF Core supports code first and migrations for SQLite. +If you use .NET Core 3 or above together with EF Core, there is no need for this library. +If you use EF 6 (either with .NET Full Framework or with .NET Core 3 or above), this library is an option for you to get code first for SQLite. +I'm going to maintain this library as long as it is useful for some people (see [History](https://github.com/msallin/SQLiteCodeFirst/issues/166) & [Project Status and Release Schedule](https://github.com/msallin/SQLiteCodeFirst/issues/157)). + +## Features + +This project ships several `IDbInitializer` classes. These create new SQLite Databases based on your model/code. + +The following features are supported: -Currently the following is supported: - Tables from classes (supported annotations: `Table`) - Columns from properties (supported annotations: `Column`, `Key`, `MaxLength`, `Required`, `NotMapped`, `DatabaseGenerated`, `Index`) - PrimaryKey constraint (`Key` annotation, key composites are supported) - ForeignKey constraint (1-n relationships, support for 'Cascade on delete') - 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`) - -I tried to write the code in a extensible way. -The logic is divided into two main parts, Builder and Statement. -The Builder knows how to translate the EdmModel into statements where a statement class creates the SQLite-DDL-Code. -The structure of the statements is influenced by the [SQLite Language Specification](https://www.sqlite.org/lang.html). -You will find an extensive usage of the composite pattern. +- Auto increment (An int PrimaryKey will automatically be incremented and you can explicit set the "AUTOINCREMENT" constraint to a PrimaryKey using the Autoincrement-Attribute) +- Index (Decorate columns with the `Index` attribute. Indices are automatically created for foreign keys by default. To prevent this you can remove the convention `ForeignKeyIndexConvention`) +- Unique constraint (Decorate columns with the `UniqueAttribute`, which is part of this library) +- Collate constraint (Decorate columns with the `CollateAttribute`, which is part of this library. Use `CollationFunction.Custom` to specify a own collation function.) +- Default collation (pass an instance of Collation as constructor parameter for an initializer to specify a default collation). +- SQL default value (Decorate columns with the `SqlDefaultValueAttribute`, which is part of this library) ## Install + Either get the assembly from the latest [GitHub Release Page](https://github.com/msallin/SQLiteCodeFirst/releases) or install the NuGet-Package [SQLite.CodeFirst](https://www.nuget.org/packages/SQLite.CodeFirst/) (`PM> Install-Package SQLite.CodeFirst`). -The project is built to target.NET framework versions 4.0 and 4.5. +The project is built to target .NET framework versions 4.0 and 4.5 and .NET Standard 2.1. You can use the SQLite CodeFirst in projects that target the following frameworks: -- .NET 4.0 (use net40) -- .NET 4.5 (use net45) -- .NET 4.5.1 (use net45) -- .NET 4.5.2 (use net45) + +- .NET 4.0 (uses net40) +- .NET 4.5-4.8 (uses net45) +- .NET Core 3.0-3.1 (uses netstandard2.1) +- .NET 5-8 (uses netstandard2.1) ## How to use + +The functionality is exposed by using implementations of the `IDbInitializer<>` interface. +Depending on your need, you can choose from the following initializers: + +- SqliteCreateDatabaseIfNotExists +- SqliteDropCreateDatabaseAlways +- SqliteDropCreateDatabaseWhenModelChanges + +If you want to have more control, you can use the `SqliteDatabaseCreator` (implements `IDatabaseCreator`) which lets you control the creation of the SQLite database. +Or for even more control, use the `SqliteSqlGenerator` (implements `ISqlGenerator`), which lets you generate the SQL code based on your `EdmModel`. + When you want to let the Entity Framework create database if it does not exist, just set `SqliteDropCreateDatabaseAlways<>` or `SqliteCreateDatabaseIfNotExists<>` as your `IDbInitializer<>`. + +### Initializer Sample + ```csharp public class MyDbContext : DbContext { public MyDbContext() : base("ConnectionStringName") { } - + protected override void OnModelCreating(DbModelBuilder modelBuilder) { var sqliteConnectionInitializer = new SqliteCreateDatabaseIfNotExists(modelBuilder); @@ -49,9 +80,14 @@ public class MyDbContext : DbContext } ``` +Notice that the `SqliteDropCreateDatabaseWhenModelChanges<>` initializer will create a additional table in your database. +This table is used to store some information to detect model changes. If you want to use an own entity/table you have to implement the +`IHistory` interface and pass the type of your entity as parameter to the constructor of the initializer. + In a more advanced scenario, you may want to populate some core- or test-data after the database was created. -To do this, inherit from `SqliteDropCreateDatabaseAlways<>` or `SqliteCreateDatabaseIfNotExists<>` and override the `Seed(MyDbContext context)` function. -This function will be called in a transaction once the database was created. This function is only executed if a new database was successfully created. +To do this, inherit from `SqliteDropCreateDatabaseAlways<>`, `SqliteCreateDatabaseIfNotExists<>` or `SqliteDropCreateDatabaseWhenModelChanges<>` and override the `Seed(MyDbContext context)` function. +This function will be called in a transaction, once the database was created. This function is only executed if a new database was successfully created. + ```csharp public class MyDbContextInitializer : SqliteDropCreateDatabaseAlways { @@ -65,6 +101,85 @@ public class MyDbContextInitializer : SqliteDropCreateDatabaseAlways + +``` + +Add the following class. +```csharp +public class MyConfiguration : DbConfiguration, IDbConnectionFactory { + public MyConfiguration() + { + SetProviderFactory("System.Data.SQLite", SQLiteFactory.Instance); + SetProviderFactory("System.Data.SQLite.EF6", SQLiteProviderFactory.Instance); + + var providerServices = (DbProviderServices)SQLiteProviderFactory.Instance.GetService(typeof(DbProviderServices)); + + SetProviderServices("System.Data.SQLite", providerServices); + SetProviderServices("System.Data.SQLite.EF6", providerServices); + + SetDefaultConnectionFactory(this); + } + + public DbConnection CreateConnection(string connectionString) + => new SQLiteConnection(connectionString); + } +} +``` + +Also, make sure you specify the DbConfigurationType on the DBContext class as well + +```csharp +[DbConfigurationType(typeof(MyConfiguration))] +public class Context: DbContext { + //... DBContext things +} +``` +## Structure + +The code is written in an extensible way. +The logic is divided into two main parts, Builder and Statement. +The Builder knows how to translate the EdmModel into statements where a statement class creates the SQLite-DDL-Code. +The structure of the statements is influenced by the [SQLite Language Specification](https://www.sqlite.org/lang.html). +You will find an extensive usage of the composite pattern. + ## Hints + If you try to reinstall the NuGet-Packages (e.g. if you want to downgrade to .NET 4.0), the app.config will be overwritten and you may getting an exception when you try to run the console project. -In this case please check the following issue: https://github.com/msallin/SQLiteCodeFirst/issues/13. +In this case please check the following issue: + +## Recognition + +I started with the [code](https://gist.github.com/flaub/1968486e1b3f2b9fddaf) from [flaub](https://github.com/flaub). diff --git a/SQLite.CodeFirst.Console/App.config b/SQLite.CodeFirst.Console/App.config index b7d5048..9fa9559 100644 --- a/SQLite.CodeFirst.Console/App.config +++ b/SQLite.CodeFirst.Console/App.config @@ -11,9 +11,10 @@ - - + + + @@ -25,6 +26,7 @@ + \ No newline at end of file diff --git a/SQLite.CodeFirst.Console/Entity/CustomHistory.cs b/SQLite.CodeFirst.Console/Entity/CustomHistory.cs new file mode 100644 index 0000000..51ad92c --- /dev/null +++ b/SQLite.CodeFirst.Console/Entity/CustomHistory.cs @@ -0,0 +1,12 @@ +using System; + +namespace SQLite.CodeFirst.Console.Entity +{ + public class CustomHistory : IHistory + { + public int Id { get; set; } + public string Hash { get; set; } + public string Context { get; set; } + public DateTime CreateDate { get; set; } + } +} diff --git a/SQLite.CodeFirst.Console/Entity/Foo.cs b/SQLite.CodeFirst.Console/Entity/Foo.cs new file mode 100644 index 0000000..85fa6d9 --- /dev/null +++ b/SQLite.CodeFirst.Console/Entity/Foo.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.Console.Entity +{ + /// + /// See https://github.com/msallin/SQLiteCodeFirst/issues/69 and https://github.com/msallin/SQLiteCodeFirst/issues/63 + /// + public class Foo + { + private ICollection _fooSelves; + private ICollection _fooSteps; + + public int FooId { get; set; } + public string Name { get; set; } + public int? FooSelf1Id { get; set; } + public int? FooSelf2Id { get; set; } + public int? FooSelf3Id { get; set; } + + [ForeignKey("FooSelf1Id")] + public virtual Foo ParentMyEntity1 { get; set; } + + [ForeignKey("FooSelf2Id")] + public virtual Foo ParentMyEntity2 { get; set; } + + [ForeignKey("FooSelf3Id")] + public virtual Foo ParentMyEntity3 { get; set; } + + public virtual ICollection FooSteps + { + get { return _fooSteps ?? (_fooSteps = new HashSet()); } + set { _fooSteps = value; } + } + + public virtual ICollection FooSelves + { + get { return _fooSelves ?? (_fooSelves = new HashSet()); } + set { _fooSelves = value; } + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.Console/Entity/FooCompositeKey.cs b/SQLite.CodeFirst.Console/Entity/FooCompositeKey.cs new file mode 100644 index 0000000..2301500 --- /dev/null +++ b/SQLite.CodeFirst.Console/Entity/FooCompositeKey.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.Console.Entity +{ + /// + /// See https://github.com/msallin/SQLiteCodeFirst/issues/109 + /// + public class FooCompositeKey + { + [Key, Column(Order = 1)] + public int Id { get; set; } + + [Key, Column(Order = 2), StringLength(20)] + public string Version { get; set; } + + [StringLength(255)] + public string Name { get; set; } + + public virtual ICollection FooeyACollection { get; set; } + + public virtual ICollection FooeyBCollection { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.Console/Entity/FooRelationshipA.cs b/SQLite.CodeFirst.Console/Entity/FooRelationshipA.cs new file mode 100644 index 0000000..38baaf2 --- /dev/null +++ b/SQLite.CodeFirst.Console/Entity/FooRelationshipA.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace SQLite.CodeFirst.Console.Entity +{ + /// + /// See https://github.com/msallin/SQLiteCodeFirst/issues/109 + /// + public class FooRelationshipA + { + public int Id { get; set; } + + [StringLength(255)] + public string Name { get; set; } + + public virtual ICollection Fooey { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.Console/Entity/FooRelationshipB.cs b/SQLite.CodeFirst.Console/Entity/FooRelationshipB.cs new file mode 100644 index 0000000..271d8f8 --- /dev/null +++ b/SQLite.CodeFirst.Console/Entity/FooRelationshipB.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace SQLite.CodeFirst.Console.Entity +{ + /// + /// See https://github.com/msallin/SQLiteCodeFirst/issues/109 + /// + public class FooRelationshipB + { + public int Id { get; set; } + + [StringLength(255)] + public string Name { get; set; } + + public virtual ICollection Fooey { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.Console/Entity/FooSelf.cs b/SQLite.CodeFirst.Console/Entity/FooSelf.cs new file mode 100644 index 0000000..0268835 --- /dev/null +++ b/SQLite.CodeFirst.Console/Entity/FooSelf.cs @@ -0,0 +1,13 @@ +namespace SQLite.CodeFirst.Console.Entity +{ + /// + /// See https://github.com/msallin/SQLiteCodeFirst/issues/69 and https://github.com/msallin/SQLiteCodeFirst/issues/63 + /// + public class FooSelf + { + public int FooSelfId { get; set; } + public int FooId { get; set; } + public int Number { get; set; } + public virtual Foo Foo { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.Console/Entity/FooStep.cs b/SQLite.CodeFirst.Console/Entity/FooStep.cs new file mode 100644 index 0000000..aa0139f --- /dev/null +++ b/SQLite.CodeFirst.Console/Entity/FooStep.cs @@ -0,0 +1,13 @@ +namespace SQLite.CodeFirst.Console.Entity +{ + /// + /// See https://github.com/msallin/SQLiteCodeFirst/issues/69 and https://github.com/msallin/SQLiteCodeFirst/issues/63 + /// + public class FooStep + { + public int FooStepId { get; set; } + public int FooId { get; set; } + public int Number { get; set; } + public virtual Foo Foo { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.Console/Entity/IEntity.cs b/SQLite.CodeFirst.Console/Entity/IEntity.cs index e88d169..17afb3b 100644 --- a/SQLite.CodeFirst.Console/Entity/IEntity.cs +++ b/SQLite.CodeFirst.Console/Entity/IEntity.cs @@ -1,6 +1,6 @@ namespace SQLite.CodeFirst.Console.Entity { - interface IEntity + public interface IEntity { int Id { get; set; } } diff --git a/SQLite.CodeFirst.Console/Entity/Person.cs b/SQLite.CodeFirst.Console/Entity/Person.cs index acae0e9..8bc807b 100644 --- a/SQLite.CodeFirst.Console/Entity/Person.cs +++ b/SQLite.CodeFirst.Console/Entity/Person.cs @@ -1,4 +1,6 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace SQLite.CodeFirst.Console.Entity { @@ -7,6 +9,7 @@ public abstract class Person : IEntity public int Id { get; set; } [MaxLength(50)] + [Collate(CollationFunction.NoCase)] public string FirstName { get; set; } [MaxLength(50)] @@ -17,5 +20,9 @@ public abstract class Person : IEntity [Required] public string City { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Computed)] + [SqlDefaultValue(DefaultValue = "DATETIME('now')")] + public DateTime CreatedUtc { get; set; } } } diff --git a/SQLite.CodeFirst.Console/Entity/Player.cs b/SQLite.CodeFirst.Console/Entity/Player.cs index 8e69fab..5ec5be6 100644 --- a/SQLite.CodeFirst.Console/Entity/Player.cs +++ b/SQLite.CodeFirst.Console/Entity/Player.cs @@ -6,8 +6,16 @@ namespace SQLite.CodeFirst.Console.Entity public class Player : Person { [Index] // Automatically named 'IX_TeamPlayer_Number' + [Index("IX_TeamPlayer_NumberPerTeam", Order = 1, IsUnique = true)] public int Number { get; set; } + // The index attribute must be placed on the FK not on the navigation property (team). + [Index("IX_TeamPlayer_NumberPerTeam", Order = 2, IsUnique = true)] + public int TeamId { get; set; } + + // Its not possible to set an index on this property. Use the FK property (teamId). public virtual Team Team { get; set; } + + public virtual Player Mentor { get; set; } } } \ No newline at end of file diff --git a/SQLite.CodeFirst.Console/Entity/Stadion.cs b/SQLite.CodeFirst.Console/Entity/Stadion.cs index e9a49a8..d77dd2b 100644 --- a/SQLite.CodeFirst.Console/Entity/Stadion.cs +++ b/SQLite.CodeFirst.Console/Entity/Stadion.cs @@ -7,16 +7,20 @@ public class Stadion { [Key] [Column(Order = 1)] - [Index("IX_Stadion_Main", Order = 2)] // Test for combined, named index + [Index("IX_Stadion_Main", Order = 2, IsUnique = true)] // Test for combined, named index public string Name { get; set; } [Key] [Column(Order = 2)] - [Index("IX_Stadion_Main", Order = 1)] // Test for combined, named index + [Index("IX_Stadion_Main", Order = 1, IsUnique = true)] // Test for combined, named index public string Street { get; set; } [Key] [Column(Order = 3)] public string City { get; set; } + + [Column(Order = 4)] + [Index("ReservedKeyWordTest", IsUnique = true)] + public int Order { get; set; } } } diff --git a/SQLite.CodeFirst.Console/Entity/Team.cs b/SQLite.CodeFirst.Console/Entity/Team.cs index 5a5a795..905be57 100644 --- a/SQLite.CodeFirst.Console/Entity/Team.cs +++ b/SQLite.CodeFirst.Console/Entity/Team.cs @@ -6,6 +6,7 @@ namespace SQLite.CodeFirst.Console.Entity { public class Team : IEntity { + [Autoincrement] public int Id { get; set; } [Index("IX_Team_TeamsName")] // Test for named index. diff --git a/SQLite.CodeFirst.Console/FootballDbContext.cs b/SQLite.CodeFirst.Console/FootballDbContext.cs index 6f0ed9e..77b9178 100644 --- a/SQLite.CodeFirst.Console/FootballDbContext.cs +++ b/SQLite.CodeFirst.Console/FootballDbContext.cs @@ -1,111 +1,33 @@ -using System.Collections.Generic; +using System.Data.Common; using System.Data.Entity; -using System.Data.Entity.ModelConfiguration.Conventions; -using SQLite.CodeFirst.Console.Entity; namespace SQLite.CodeFirst.Console { public class FootballDbContext : DbContext { - public FootballDbContext() - : base("footballDb") + public FootballDbContext(string nameOrConnectionString) + : base(nameOrConnectionString) { - Configuration.ProxyCreationEnabled = true; - Configuration.LazyLoadingEnabled = true; + Configure(); } - protected override void OnModelCreating(DbModelBuilder modelBuilder) + public FootballDbContext(DbConnection connection, bool contextOwnsConnection) + : base(connection, contextOwnsConnection) { - modelBuilder.Conventions.Remove(); - - ConfigureTeamEntity(modelBuilder); - ConfigureStadionEntity(modelBuilder); - ConfigureCoachEntity(modelBuilder); - ConfigurePlayerEntity(modelBuilder); - - var initializer = new FootballDbInitializer(modelBuilder); - Database.SetInitializer(initializer); + Configure(); } - private static void ConfigureTeamEntity(DbModelBuilder modelBuilder) + private void Configure() { - modelBuilder.Entity().ToTable("Base.MyTable") - .HasRequired(t => t.Coach) - .WithMany() - .WillCascadeOnDelete(false); - - modelBuilder.Entity() - .HasRequired(t => t.Stadion) - .WithRequiredPrincipal() - .WillCascadeOnDelete(true); - } - - private static void ConfigureStadionEntity(DbModelBuilder modelBuilder) - { - modelBuilder.Entity(); - } - - private static void ConfigureCoachEntity(DbModelBuilder modelBuilder) - { - modelBuilder.Entity() - .HasRequired(p => p.Team) - .WithRequiredPrincipal(t => t.Coach) - .WillCascadeOnDelete(false); - } - - private static void ConfigurePlayerEntity(DbModelBuilder modelBuilder) - { - modelBuilder.Entity() - .HasRequired(p => p.Team) - .WithMany(team => team.Players) - .WillCascadeOnDelete(true); + Configuration.ProxyCreationEnabled = true; + Configuration.LazyLoadingEnabled = true; } - } - - public class FootballDbInitializer : SqliteDropCreateDatabaseAlways - { - public FootballDbInitializer(DbModelBuilder modelBuilder) - : base(modelBuilder) - { } - protected override void Seed(FootballDbContext context) + protected override void OnModelCreating(DbModelBuilder modelBuilder) { - context.Set().Add(new Team - { - Name = "YB", - Coach = new Coach - { - City = "Zürich", - FirstName = "Masssaman", - LastName = "Nachn", - Street = "Testingstreet 844" - }, - Players = new List - { - new Player - { - City = "Bern", - FirstName = "Marco", - LastName = "Bürki", - Street = "Wunderstrasse 43", - Number = 12 - }, - new Player - { - City = "Berlin", - FirstName = "Alain", - LastName = "Rochat", - Street = "Wonderstreet 13", - Number = 14 - } - }, - Stadion = new Stadion - { - Name = "Stade de Suisse", - City = "Bern", - Street = "Papiermühlestrasse 71" - } - }); + ModelConfiguration.Configure(modelBuilder); + var initializer = new FootballDbInitializer(modelBuilder); + Database.SetInitializer(initializer); } } -} +} \ No newline at end of file diff --git a/SQLite.CodeFirst.Console/FootballDbInitializer.cs b/SQLite.CodeFirst.Console/FootballDbInitializer.cs new file mode 100644 index 0000000..59e9aa6 --- /dev/null +++ b/SQLite.CodeFirst.Console/FootballDbInitializer.cs @@ -0,0 +1,17 @@ +using System.Data.Entity; +using SQLite.CodeFirst.Console.Entity; + +namespace SQLite.CodeFirst.Console +{ + public class FootballDbInitializer : SqliteDropCreateDatabaseWhenModelChanges + { + public FootballDbInitializer(DbModelBuilder modelBuilder) + : base(modelBuilder, typeof(CustomHistory)) + { } + + protected override void Seed(FootballDbContext context) + { + // Here you can seed your core data if you have any. + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.Console/ModelConfiguration.cs b/SQLite.CodeFirst.Console/ModelConfiguration.cs new file mode 100644 index 0000000..30703ad --- /dev/null +++ b/SQLite.CodeFirst.Console/ModelConfiguration.cs @@ -0,0 +1,66 @@ +using System.Data.Entity; +using SQLite.CodeFirst.Console.Entity; + +namespace SQLite.CodeFirst.Console +{ + public class ModelConfiguration + { + public static void Configure(DbModelBuilder modelBuilder) + { + ConfigureTeamEntity(modelBuilder); + ConfigureStadionEntity(modelBuilder); + ConfigureCoachEntity(modelBuilder); + ConfigurePlayerEntity(modelBuilder); + ConfigureSelfReferencingEntities(modelBuilder); + ConfigureCompositeKeyEntities(modelBuilder); + } + + private static void ConfigureTeamEntity(DbModelBuilder modelBuilder) + { + modelBuilder.Entity().ToTable("Base.MyTable") + .HasRequired(t => t.Coach) + .WithMany() + .WillCascadeOnDelete(false); + + modelBuilder.Entity() + .HasRequired(t => t.Stadion) + .WithRequiredPrincipal() + .WillCascadeOnDelete(true); + } + + private static void ConfigureStadionEntity(DbModelBuilder modelBuilder) + { + modelBuilder.Entity(); + } + + private static void ConfigureCoachEntity(DbModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasRequired(p => p.Team) + .WithRequiredPrincipal(t => t.Coach) + .WillCascadeOnDelete(false); + } + + private static void ConfigurePlayerEntity(DbModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasRequired(p => p.Team) + .WithMany(team => team.Players) + .WillCascadeOnDelete(true); + } + + private static void ConfigureSelfReferencingEntities(DbModelBuilder modelBuilder) + { + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + } + + private static void ConfigureCompositeKeyEntities(DbModelBuilder modelBuilder) + { + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + } + } +} diff --git a/SQLite.CodeFirst.Console/Program.cs b/SQLite.CodeFirst.Console/Program.cs index 79641ca..d3913c4 100644 --- a/SQLite.CodeFirst.Console/Program.cs +++ b/SQLite.CodeFirst.Console/Program.cs @@ -1,34 +1,107 @@ -using SQLite.CodeFirst.Console.Entity; +using System.Collections.Generic; +using System.Data.Entity; +using System.Data.SQLite; +using System.Linq; +using SQLite.CodeFirst.Console.Entity; namespace SQLite.CodeFirst.Console { public static class Program { - static void Main() + private static void Main() { - System.Console.WriteLine("Starting Demo Application"); + StartDemoUseInMemory(); + StartDemoUseFile(); + PressEnterToExit(); + } - var context = CreateAndSeedDatabase(); + private static void StartDemoUseInMemory() + { + System.Console.WriteLine("Starting Demo Application (In Memory)"); + System.Console.WriteLine(string.Empty); - DisplaySeededData(context); + using (var sqLiteConnection = new SQLiteConnection("data source=:memory:")) + { + // This is required if a in memory db is used. + sqLiteConnection.Open(); - PressEnterToExit(); + using (var context = new FootballDbContext(sqLiteConnection, false)) + { + CreateAndSeedDatabase(context); + DisplaySeededData(context); + } + } } - private static FootballDbContext CreateAndSeedDatabase() + private static void StartDemoUseFile() + { + System.Console.WriteLine("Starting Demo Application (File)"); + System.Console.WriteLine(string.Empty); + + using (var context = new FootballDbContext("footballDb")) + { + CreateAndSeedDatabase(context); + DisplaySeededData(context); + } + } + + private static void CreateAndSeedDatabase(DbContext context) { System.Console.WriteLine("Create and seed the database."); - var context = new FootballDbContext(); + + if (context.Set().Count() != 0) + { + return; + } + + context.Set().Add(new Team + { + Name = "YB", + Coach = new Coach + { + City = "Zürich", + FirstName = "Masssaman", + LastName = "Nachn", + Street = "Testingstreet 844" + }, + Players = new List + { + new Player + { + City = "Bern", + FirstName = "Marco", + LastName = "Bürki", + Street = "Wunderstrasse 43", + Number = 12 + }, + new Player + { + City = "Berlin", + FirstName = "Alain", + LastName = "Rochat", + Street = "Wonderstreet 13", + Number = 14 + } + }, + Stadion = new Stadion + { + Name = "Stade de Suisse", + City = "Bern", + Street = "Papiermühlestrasse 71" + } + }); + + context.SaveChanges(); + System.Console.WriteLine("Completed."); System.Console.WriteLine(); - return context; } - private static void DisplaySeededData(FootballDbContext context) + private static void DisplaySeededData(DbContext context) { System.Console.WriteLine("Display seeded data."); - foreach (var team in context.Set()) + foreach (Team team in context.Set()) { System.Console.WriteLine("\t Team:"); System.Console.WriteLine("\t Id: {0}", team.Id); @@ -49,7 +122,7 @@ private static void DisplaySeededData(FootballDbContext context) System.Console.WriteLine("\t\t City: {0}", team.Coach.City); System.Console.WriteLine(); - foreach (var player in team.Players) + foreach (Player player in team.Players) { System.Console.WriteLine("\t\t Player:"); System.Console.WriteLine("\t\t Id: {0}", player.Id); @@ -58,6 +131,7 @@ private static void DisplaySeededData(FootballDbContext context) System.Console.WriteLine("\t\t LastName: {0}", player.LastName); System.Console.WriteLine("\t\t Street: {0}", player.Street); System.Console.WriteLine("\t\t City: {0}", player.City); + System.Console.WriteLine("\t\t Created: {0}", player.CreatedUtc); System.Console.WriteLine(); } } @@ -70,4 +144,4 @@ private static void PressEnterToExit() System.Console.ReadLine(); } } -} +} \ No newline at end of file diff --git a/SQLite.CodeFirst.Console/Properties/AssemblyInfo.cs b/SQLite.CodeFirst.Console/Properties/AssemblyInfo.cs deleted file mode 100644 index 02804aa..0000000 --- a/SQLite.CodeFirst.Console/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("SQLite.CodeFirst")] -[assembly: AssemblyDescription("A console application which demonstrates how to use SQLite.CodeFirst.")] -[assembly: AssemblyProduct("SQLite.CodeFirst")] -[assembly: AssemblyCopyright("Copyright © Marc Sallin")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("945f6a68-1bcd-47ff-a551-a2820b88ff8c")] - -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersion("1.0.0.0")] diff --git a/SQLite.CodeFirst.Console/SQLite.CodeFirst.Console.csproj b/SQLite.CodeFirst.Console/SQLite.CodeFirst.Console.csproj index b04e945..0ba463e 100644 --- a/SQLite.CodeFirst.Console/SQLite.CodeFirst.Console.csproj +++ b/SQLite.CodeFirst.Console/SQLite.CodeFirst.Console.csproj @@ -1,104 +1,24 @@ - - - + - Debug - AnyCPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D} Exe - Properties - SQLite.CodeFirst.Console - SQLite.CodeFirst.Console - v4.5.2 - 512 - true - - - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - + net48 + SQLite.CodeFirst + A console application which demonstrates how to use SQLite.CodeFirst. + 1.0.0.0 AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 + true + ..\Shared\SQLite.CodeFirst.StrongNameKey.snk - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - - + + + - - - ..\packages\System.Data.SQLite.Core.1.0.97.0\lib\net451\System.Data.SQLite.dll - True - - - ..\packages\System.Data.SQLite.EF6.1.0.97.0\lib\net451\System.Data.SQLite.EF6.dll - - - ..\packages\System.Data.SQLite.Linq.1.0.97.0\lib\net451\System.Data.SQLite.Linq.dll - - - - - - - - - - - - - - - - - - - - - {50A32FE4-0E13-4213-A373-72523CDF34D9} - SQLite.CodeFirst - + - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - \ No newline at end of file diff --git a/SQLite.CodeFirst.Console/packages.config b/SQLite.CodeFirst.Console/packages.config deleted file mode 100644 index b4537bc..0000000 --- a/SQLite.CodeFirst.Console/packages.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/SQLite.CodeFirst.NetCore.Console/Configuration.cs b/SQLite.CodeFirst.NetCore.Console/Configuration.cs new file mode 100644 index 0000000..845f483 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Configuration.cs @@ -0,0 +1,28 @@ +using System.Data.Common; +using System.Data.Entity; +using System.Data.Entity.Core.Common; +using System.Data.Entity.Infrastructure; +using System.Data.SQLite; +using System.Data.SQLite.EF6; + +namespace SQLite.CodeFirst.NetCore.Console +{ + public class Configuration : DbConfiguration, IDbConnectionFactory + { + public Configuration() + { + SetProviderFactory("System.Data.SQLite", SQLiteFactory.Instance); + SetProviderFactory("System.Data.SQLite.EF6", SQLiteProviderFactory.Instance); + + var providerServices = (DbProviderServices)SQLiteProviderFactory.Instance.GetService(typeof(DbProviderServices)); + + SetProviderServices("System.Data.SQLite", providerServices); + SetProviderServices("System.Data.SQLite.EF6", providerServices); + + SetDefaultConnectionFactory(this); + } + + public DbConnection CreateConnection(string connectionString) + => new SQLiteConnection(connectionString); + } +} diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/Coach.cs b/SQLite.CodeFirst.NetCore.Console/Entity/Coach.cs new file mode 100644 index 0000000..6dfc65b --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Entity/Coach.cs @@ -0,0 +1,7 @@ +namespace SQLite.CodeFirst.NetCore.Console.Entity +{ + public class Coach : Person + { + public virtual Team Team { get; set; } + } +} diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/CustomHistory.cs b/SQLite.CodeFirst.NetCore.Console/Entity/CustomHistory.cs new file mode 100644 index 0000000..740c3da --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Entity/CustomHistory.cs @@ -0,0 +1,12 @@ +using System; + +namespace SQLite.CodeFirst.NetCore.Console.Entity +{ + public class CustomHistory : IHistory + { + public int Id { get; set; } + public string Hash { get; set; } + public string Context { get; set; } + public DateTime CreateDate { get; set; } + } +} diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/Foo.cs b/SQLite.CodeFirst.NetCore.Console/Entity/Foo.cs new file mode 100644 index 0000000..f5a1bdd --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Entity/Foo.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.NetCore.Console.Entity +{ + /// + /// See https://github.com/msallin/SQLiteCodeFirst/issues/69 and https://github.com/msallin/SQLiteCodeFirst/issues/63 + /// + public class Foo + { + private ICollection _fooSelves; + private ICollection _fooSteps; + + public int FooId { get; set; } + public string Name { get; set; } + public int? FooSelf1Id { get; set; } + public int? FooSelf2Id { get; set; } + public int? FooSelf3Id { get; set; } + + [ForeignKey("FooSelf1Id")] + public virtual Foo ParentMyEntity1 { get; set; } + + [ForeignKey("FooSelf2Id")] + public virtual Foo ParentMyEntity2 { get; set; } + + [ForeignKey("FooSelf3Id")] + public virtual Foo ParentMyEntity3 { get; set; } + + public virtual ICollection FooSteps + { + get { return _fooSteps ?? (_fooSteps = new HashSet()); } + set { _fooSteps = value; } + } + + public virtual ICollection FooSelves + { + get { return _fooSelves ?? (_fooSelves = new HashSet()); } + set { _fooSelves = value; } + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/FooCompositeKey.cs b/SQLite.CodeFirst.NetCore.Console/Entity/FooCompositeKey.cs new file mode 100644 index 0000000..b4bb2d6 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Entity/FooCompositeKey.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.NetCore.Console.Entity +{ + /// + /// See https://github.com/msallin/SQLiteCodeFirst/issues/109 + /// + public class FooCompositeKey + { + [Key, Column(Order = 1)] + public int Id { get; set; } + + [Key, Column(Order = 2), StringLength(20)] + public string Version { get; set; } + + [StringLength(255)] + public string Name { get; set; } + + public virtual ICollection FooeyACollection { get; set; } + + public virtual ICollection FooeyBCollection { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/FooRelationshipA.cs b/SQLite.CodeFirst.NetCore.Console/Entity/FooRelationshipA.cs new file mode 100644 index 0000000..f733cd7 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Entity/FooRelationshipA.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace SQLite.CodeFirst.NetCore.Console.Entity +{ + /// + /// See https://github.com/msallin/SQLiteCodeFirst/issues/109 + /// + public class FooRelationshipA + { + public int Id { get; set; } + + [StringLength(255)] + public string Name { get; set; } + + public virtual ICollection Fooey { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/FooRelationshipB.cs b/SQLite.CodeFirst.NetCore.Console/Entity/FooRelationshipB.cs new file mode 100644 index 0000000..3b75f68 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Entity/FooRelationshipB.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace SQLite.CodeFirst.NetCore.Console.Entity +{ + /// + /// See https://github.com/msallin/SQLiteCodeFirst/issues/109 + /// + public class FooRelationshipB + { + public int Id { get; set; } + + [StringLength(255)] + public string Name { get; set; } + + public virtual ICollection Fooey { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/FooSelf.cs b/SQLite.CodeFirst.NetCore.Console/Entity/FooSelf.cs new file mode 100644 index 0000000..6a8ad39 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Entity/FooSelf.cs @@ -0,0 +1,13 @@ +namespace SQLite.CodeFirst.NetCore.Console.Entity +{ + /// + /// See https://github.com/msallin/SQLiteCodeFirst/issues/69 and https://github.com/msallin/SQLiteCodeFirst/issues/63 + /// + public class FooSelf + { + public int FooSelfId { get; set; } + public int FooId { get; set; } + public int Number { get; set; } + public virtual Foo Foo { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/FooStep.cs b/SQLite.CodeFirst.NetCore.Console/Entity/FooStep.cs new file mode 100644 index 0000000..b13b3a4 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Entity/FooStep.cs @@ -0,0 +1,13 @@ +namespace SQLite.CodeFirst.NetCore.Console.Entity +{ + /// + /// See https://github.com/msallin/SQLiteCodeFirst/issues/69 and https://github.com/msallin/SQLiteCodeFirst/issues/63 + /// + public class FooStep + { + public int FooStepId { get; set; } + public int FooId { get; set; } + public int Number { get; set; } + public virtual Foo Foo { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/IEntity.cs b/SQLite.CodeFirst.NetCore.Console/Entity/IEntity.cs new file mode 100644 index 0000000..9a97b92 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Entity/IEntity.cs @@ -0,0 +1,7 @@ +namespace SQLite.CodeFirst.NetCore.Console.Entity +{ + public interface IEntity + { + int Id { get; set; } + } +} diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/Person.cs b/SQLite.CodeFirst.NetCore.Console/Entity/Person.cs new file mode 100644 index 0000000..e5ebda4 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Entity/Person.cs @@ -0,0 +1,28 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.NetCore.Console.Entity +{ + public abstract class Person : IEntity + { + public int Id { get; set; } + + [MaxLength(50)] + [Collate(CollationFunction.NoCase)] + public string FirstName { get; set; } + + [MaxLength(50)] + public string LastName { get; set; } + + [MaxLength(100)] + public string Street { get; set; } + + [Required] + public string City { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Computed)] + [SqlDefaultValue(DefaultValue = "DATETIME('now')")] + public DateTime CreatedUtc { get; set; } + } +} diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/Player.cs b/SQLite.CodeFirst.NetCore.Console/Entity/Player.cs new file mode 100644 index 0000000..f642654 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Entity/Player.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.NetCore.Console.Entity +{ + [Table("TeamPlayer")] + public class Player : Person + { + [Index] // Automatically named 'IX_TeamPlayer_Number' + [Index("IX_TeamPlayer_NumberPerTeam", Order = 1, IsUnique = true)] + public int Number { get; set; } + + // The index attribute must be placed on the FK not on the navigation property (team). + [Index("IX_TeamPlayer_NumberPerTeam", Order = 2, IsUnique = true)] + public int TeamId { get; set; } + + // Its not possible to set an index on this property. Use the FK property (teamId). + public virtual Team Team { get; set; } + + public virtual Player Mentor { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/Stadion.cs b/SQLite.CodeFirst.NetCore.Console/Entity/Stadion.cs new file mode 100644 index 0000000..d8a2c50 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Entity/Stadion.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.NetCore.Console.Entity +{ + public class Stadion + { + [Key] + [Column(Order = 1)] + [Index("IX_Stadion_Main", Order = 2, IsUnique = true)] // Test for combined, named index + public string Name { get; set; } + + [Key] + [Column(Order = 2)] + [Index("IX_Stadion_Main", Order = 1, IsUnique = true)] // Test for combined, named index + public string Street { get; set; } + + [Key] + [Column(Order = 3)] + public string City { get; set; } + + [Column(Order = 4)] + [Index("ReservedKeyWordTest", IsUnique = true)] + public int Order { get; set; } + } +} diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/Team.cs b/SQLite.CodeFirst.NetCore.Console/Entity/Team.cs new file mode 100644 index 0000000..d448a5f --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Entity/Team.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.NetCore.Console.Entity +{ + public class Team : IEntity + { + [Autoincrement] + public int Id { get; set; } + + [Index("IX_Team_TeamsName")] // Test for named index. + [Required] + public string Name { get; set; } + + public virtual Coach Coach { get; set; } + + public virtual ICollection Players { get; set; } + + public virtual Stadion Stadion { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.NetCore.Console/FootballDbContext.cs b/SQLite.CodeFirst.NetCore.Console/FootballDbContext.cs new file mode 100644 index 0000000..330c6c0 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/FootballDbContext.cs @@ -0,0 +1,33 @@ +using System.Data.Common; +using System.Data.Entity; + +namespace SQLite.CodeFirst.NetCore.Console +{ + public class FootballDbContext : DbContext + { + public FootballDbContext(string nameOrConnectionString) + : base(nameOrConnectionString) + { + Configure(); + } + + public FootballDbContext(DbConnection connection, bool contextOwnsConnection) + : base(connection, contextOwnsConnection) + { + Configure(); + } + + private void Configure() + { + Configuration.ProxyCreationEnabled = true; + Configuration.LazyLoadingEnabled = true; + } + + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + ModelConfiguration.Configure(modelBuilder); + var initializer = new FootballDbInitializer(modelBuilder); + Database.SetInitializer(initializer); + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.NetCore.Console/FootballDbInitializer.cs b/SQLite.CodeFirst.NetCore.Console/FootballDbInitializer.cs new file mode 100644 index 0000000..0d4a426 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/FootballDbInitializer.cs @@ -0,0 +1,17 @@ +using System.Data.Entity; +using SQLite.CodeFirst.NetCore.Console.Entity; + +namespace SQLite.CodeFirst.NetCore.Console +{ + public class FootballDbInitializer : SqliteDropCreateDatabaseWhenModelChanges + { + public FootballDbInitializer(DbModelBuilder modelBuilder) + : base(modelBuilder, typeof(CustomHistory)) + { } + + protected override void Seed(FootballDbContext context) + { + // Here you can seed your core data if you have any. + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.NetCore.Console/ModelConfiguration.cs b/SQLite.CodeFirst.NetCore.Console/ModelConfiguration.cs new file mode 100644 index 0000000..254f6f8 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/ModelConfiguration.cs @@ -0,0 +1,66 @@ +using System.Data.Entity; +using SQLite.CodeFirst.NetCore.Console.Entity; + +namespace SQLite.CodeFirst.NetCore.Console +{ + public class ModelConfiguration + { + public static void Configure(DbModelBuilder modelBuilder) + { + ConfigureTeamEntity(modelBuilder); + ConfigureStadionEntity(modelBuilder); + ConfigureCoachEntity(modelBuilder); + ConfigurePlayerEntity(modelBuilder); + ConfigureSelfReferencingEntities(modelBuilder); + ConfigureCompositeKeyEntities(modelBuilder); + } + + private static void ConfigureTeamEntity(DbModelBuilder modelBuilder) + { + modelBuilder.Entity().ToTable("Base.MyTable") + .HasRequired(t => t.Coach) + .WithMany() + .WillCascadeOnDelete(false); + + modelBuilder.Entity() + .HasRequired(t => t.Stadion) + .WithRequiredPrincipal() + .WillCascadeOnDelete(true); + } + + private static void ConfigureStadionEntity(DbModelBuilder modelBuilder) + { + modelBuilder.Entity(); + } + + private static void ConfigureCoachEntity(DbModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasRequired(p => p.Team) + .WithRequiredPrincipal(t => t.Coach) + .WillCascadeOnDelete(false); + } + + private static void ConfigurePlayerEntity(DbModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasRequired(p => p.Team) + .WithMany(team => team.Players) + .WillCascadeOnDelete(true); + } + + private static void ConfigureSelfReferencingEntities(DbModelBuilder modelBuilder) + { + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + } + + private static void ConfigureCompositeKeyEntities(DbModelBuilder modelBuilder) + { + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + } + } +} diff --git a/SQLite.CodeFirst.NetCore.Console/Program.cs b/SQLite.CodeFirst.NetCore.Console/Program.cs new file mode 100644 index 0000000..3bd2b42 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/Program.cs @@ -0,0 +1,147 @@ +using System.Collections.Generic; +using System.Data.Entity; +using System.Data.SQLite; +using System.Linq; +using SQLite.CodeFirst.NetCore.Console.Entity; + +namespace SQLite.CodeFirst.NetCore.Console +{ + public static class Program + { + private static void Main() + { + StartDemoUseInMemory(); + StartDemoUseFile(); + PressEnterToExit(); + } + + private static void StartDemoUseInMemory() + { + System.Console.WriteLine("Starting Demo Application (In Memory)"); + System.Console.WriteLine(string.Empty); + + using (var sqLiteConnection = new SQLiteConnection("data source=:memory:")) + { + // This is required if a in memory db is used. + sqLiteConnection.Open(); + + using (var context = new FootballDbContext(sqLiteConnection, false)) + { + CreateAndSeedDatabase(context); + DisplaySeededData(context); + } + } + } + + private static void StartDemoUseFile() + { + System.Console.WriteLine("Starting Demo Application (File)"); + System.Console.WriteLine(string.Empty); + + using (var context = new FootballDbContext(@"data source=.\db\footballDb\footballDb.sqlite;foreign keys=true")) + { + CreateAndSeedDatabase(context); + DisplaySeededData(context); + } + } + + private static void CreateAndSeedDatabase(DbContext context) + { + System.Console.WriteLine("Create and seed the database."); + + if (context.Set().Count() != 0) + { + return; + } + + context.Set().Add(new Team + { + Name = "YB", + Coach = new Coach + { + City = "Zürich", + FirstName = "Masssaman", + LastName = "Nachn", + Street = "Testingstreet 844" + }, + Players = new List + { + new Player + { + City = "Bern", + FirstName = "Marco", + LastName = "Bürki", + Street = "Wunderstrasse 43", + Number = 12 + }, + new Player + { + City = "Berlin", + FirstName = "Alain", + LastName = "Rochat", + Street = "Wonderstreet 13", + Number = 14 + } + }, + Stadion = new Stadion + { + Name = "Stade de Suisse", + City = "Bern", + Street = "Papiermühlestrasse 71" + } + }); + + context.SaveChanges(); + + System.Console.WriteLine("Completed."); + System.Console.WriteLine(); + } + + private static void DisplaySeededData(DbContext context) + { + System.Console.WriteLine("Display seeded data."); + + foreach (Team team in context.Set()) + { + System.Console.WriteLine("\t Team:"); + System.Console.WriteLine("\t Id: {0}", team.Id); + System.Console.WriteLine("\t Name: {0}", team.Name); + System.Console.WriteLine(); + + System.Console.WriteLine("\t\t Stadion:"); + System.Console.WriteLine("\t\t Name: {0}", team.Stadion.Name); + System.Console.WriteLine("\t\t Street: {0}", team.Stadion.Street); + System.Console.WriteLine("\t\t City: {0}", team.Stadion.City); + System.Console.WriteLine(); + + System.Console.WriteLine("\t\t Coach:"); + System.Console.WriteLine("\t\t Id: {0}", team.Coach.Id); + System.Console.WriteLine("\t\t FirstName: {0}", team.Coach.FirstName); + System.Console.WriteLine("\t\t LastName: {0}", team.Coach.LastName); + System.Console.WriteLine("\t\t Street: {0}", team.Coach.Street); + System.Console.WriteLine("\t\t City: {0}", team.Coach.City); + System.Console.WriteLine(); + + foreach (Player player in team.Players) + { + System.Console.WriteLine("\t\t Player:"); + System.Console.WriteLine("\t\t Id: {0}", player.Id); + System.Console.WriteLine("\t\t Number: {0}", player.Number); + System.Console.WriteLine("\t\t FirstName: {0}", player.FirstName); + System.Console.WriteLine("\t\t LastName: {0}", player.LastName); + System.Console.WriteLine("\t\t Street: {0}", player.Street); + System.Console.WriteLine("\t\t City: {0}", player.City); + System.Console.WriteLine("\t\t Created: {0}", player.CreatedUtc); + System.Console.WriteLine(); + } + } + } + + private static void PressEnterToExit() + { + System.Console.WriteLine(); + System.Console.WriteLine("Press 'Enter' to exit."); + System.Console.ReadLine(); + } + } +} diff --git a/SQLite.CodeFirst.NetCore.Console/SQLite.CodeFirst.NetCore.Console.csproj b/SQLite.CodeFirst.NetCore.Console/SQLite.CodeFirst.NetCore.Console.csproj new file mode 100644 index 0000000..5b2fa34 --- /dev/null +++ b/SQLite.CodeFirst.NetCore.Console/SQLite.CodeFirst.NetCore.Console.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + + + + + + + + + + + + diff --git a/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbCreationTest.cs b/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbCreationTest.cs new file mode 100644 index 0000000..c707341 --- /dev/null +++ b/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbCreationTest.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Console; +using SQLite.CodeFirst.Console.Entity; + +namespace SQLite.CodeFirst.Test.IntegrationTests +{ + + [TestClass] + public class InMemoryDbCreationTest : InMemoryDbTest + { + [TestMethod] + public void CreateInMemoryDatabaseTest() + { + using (var context = GetDbContext()) + { + context.Set().Add(new Team + { + Name = "New", + Coach = new Coach + { + City = "New", + FirstName = "New", + LastName = "New", + Street = "New" + }, + Players = new List + { + new Player + { + City = "New", + FirstName = "New", + LastName = "New", + Street = "New", + Number = 1 + }, + new Player + { + City = "New", + FirstName = "New", + LastName = "New", + Street = "New", + Number = 2 + } + }, + Stadion = new Stadion + { + Name = "New", + City = "New", + Street = "New" + } + }); + + context.SaveChanges(); + } + + using (var context = GetDbContext()) + { + Assert.AreEqual(1, context.Set().Count()); + } + } + } +} diff --git a/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbTest.cs b/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbTest.cs new file mode 100644 index 0000000..314b760 --- /dev/null +++ b/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbTest.cs @@ -0,0 +1,48 @@ +using System; +using System.Data.Common; +using System.Data.Entity; +using System.Data.SQLite; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace SQLite.CodeFirst.Test.IntegrationTests +{ + /// + /// Inherit this class to create a test which uses a SQLite in memory database. + /// This class provides the necessary logic to run multiple tests again the in memory database in a row. + /// + public abstract class InMemoryDbTest + where TDbContext : DbContext + { + private bool dbInitialized; + + protected DbConnection Connection { get; private set; } + + protected DbContext GetDbContext() + { + TDbContext context = (TDbContext)Activator.CreateInstance(typeof(TDbContext), Connection, false); + if (!dbInitialized) + { + context.Database.Initialize(true); + dbInitialized = true; + } + return context; + } + + [TestInitialize] + public void Initialize() + { + Connection = new SQLiteConnection("data source=:memory:"); + + // This is important! Else the in memory database will not work. + Connection.Open(); + + dbInitialized = false; + } + + [TestCleanup] + public void Cleanup() + { + Connection.Dispose(); + } + } +} diff --git a/SQLite.CodeFirst.Test/IntegrationTests/SqlGenerationDefaultCollationTest.cs b/SQLite.CodeFirst.Test/IntegrationTests/SqlGenerationDefaultCollationTest.cs new file mode 100644 index 0000000..d6cbd08 --- /dev/null +++ b/SQLite.CodeFirst.Test/IntegrationTests/SqlGenerationDefaultCollationTest.cs @@ -0,0 +1,118 @@ +using System.Data.Common; +using System.Data.Entity; +using System.Data.Entity.Infrastructure; +using System.Data.SQLite; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Console; +using SQLite.CodeFirst.Console.Entity; + +namespace SQLite.CodeFirst.Test.IntegrationTests +{ + [TestClass] + public class SqlGenerationDefaultCollationTest + { + private const string ReferenceSql = + @" +CREATE TABLE ""MyTable"" ([Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, [Name] nvarchar NOT NULL COLLATE custom_collate, FOREIGN KEY ([Id]) REFERENCES ""Coaches""([Id])); +CREATE TABLE ""Coaches"" ([Id] INTEGER PRIMARY KEY, [FirstName] nvarchar (50) COLLATE NOCASE, [LastName] nvarchar (50) COLLATE custom_collate, [Street] nvarchar (100) COLLATE custom_collate, [City] nvarchar NOT NULL COLLATE custom_collate, [CreatedUtc] datetime NOT NULL DEFAULT (DATETIME('now'))); +CREATE TABLE ""TeamPlayer"" ([Id] INTEGER PRIMARY KEY, [Number] int NOT NULL, [TeamId] int NOT NULL, [FirstName] nvarchar (50) COLLATE NOCASE, [LastName] nvarchar (50) COLLATE custom_collate, [Street] nvarchar (100) COLLATE custom_collate, [City] nvarchar NOT NULL COLLATE custom_collate, [CreatedUtc] datetime NOT NULL DEFAULT (DATETIME('now')), [Mentor_Id] int, FOREIGN KEY ([Mentor_Id]) REFERENCES ""TeamPlayer""([Id]), FOREIGN KEY ([TeamId]) REFERENCES ""MyTable""([Id]) ON DELETE CASCADE); +CREATE TABLE ""Stadions"" ([Name] nvarchar (128) NOT NULL COLLATE custom_collate, [Street] nvarchar (128) NOT NULL COLLATE custom_collate, [City] nvarchar (128) NOT NULL COLLATE custom_collate, [Order] int NOT NULL, [Team_Id] int NOT NULL, PRIMARY KEY([Name], [Street], [City]), FOREIGN KEY ([Team_Id]) REFERENCES ""MyTable""([Id]) ON DELETE CASCADE); +CREATE TABLE ""Foos"" ([FooId] INTEGER PRIMARY KEY, [Name] nvarchar COLLATE custom_collate, [FooSelf1Id] int, [FooSelf2Id] int, [FooSelf3Id] int, FOREIGN KEY ([FooSelf1Id]) REFERENCES ""Foos""([FooId]), FOREIGN KEY ([FooSelf2Id]) REFERENCES ""Foos""([FooId]), FOREIGN KEY ([FooSelf3Id]) REFERENCES ""Foos""([FooId])); +CREATE TABLE ""FooSelves"" ([FooSelfId] INTEGER PRIMARY KEY, [FooId] int NOT NULL, [Number] int NOT NULL, FOREIGN KEY ([FooId]) REFERENCES ""Foos""([FooId]) ON DELETE CASCADE); +CREATE TABLE ""FooSteps"" ([FooStepId] INTEGER PRIMARY KEY, [FooId] int NOT NULL, [Number] int NOT NULL, FOREIGN KEY ([FooId]) REFERENCES ""Foos""([FooId]) ON DELETE CASCADE); +CREATE TABLE ""FooCompositeKeys"" ([Id] int NOT NULL, [Version] nvarchar (20) NOT NULL COLLATE custom_collate, [Name] nvarchar (255) COLLATE custom_collate, PRIMARY KEY([Id], [Version])); +CREATE TABLE ""FooRelationshipAs"" ([Id] INTEGER PRIMARY KEY, [Name] nvarchar (255) COLLATE custom_collate); +CREATE TABLE ""FooRelationshipBs"" ([Id] INTEGER PRIMARY KEY, [Name] nvarchar (255) COLLATE custom_collate); +CREATE TABLE ""FooRelationshipAFooCompositeKeys"" ([FooRelationshipA_Id] int NOT NULL, [FooCompositeKey_Id] int NOT NULL, [FooCompositeKey_Version] nvarchar (20) NOT NULL COLLATE custom_collate, PRIMARY KEY([FooRelationshipA_Id], [FooCompositeKey_Id], [FooCompositeKey_Version]), FOREIGN KEY ([FooRelationshipA_Id]) REFERENCES ""FooRelationshipAs""([Id]) ON DELETE CASCADE, FOREIGN KEY ([FooCompositeKey_Id], [FooCompositeKey_Version]) REFERENCES ""FooCompositeKeys""([Id], [Version]) ON DELETE CASCADE); +CREATE TABLE ""FooRelationshipBFooCompositeKeys"" ([FooRelationshipB_Id] int NOT NULL, [FooCompositeKey_Id] int NOT NULL, [FooCompositeKey_Version] nvarchar (20) NOT NULL COLLATE custom_collate, PRIMARY KEY([FooRelationshipB_Id], [FooCompositeKey_Id], [FooCompositeKey_Version]), FOREIGN KEY ([FooRelationshipB_Id]) REFERENCES ""FooRelationshipBs""([Id]) ON DELETE CASCADE, FOREIGN KEY ([FooCompositeKey_Id], [FooCompositeKey_Version]) REFERENCES ""FooCompositeKeys""([Id], [Version]) ON DELETE CASCADE); +CREATE INDEX ""IX_MyTable_Id"" ON ""MyTable"" (""Id""); +CREATE INDEX ""IX_Team_TeamsName"" ON ""MyTable"" (""Name""); +CREATE INDEX ""IX_TeamPlayer_Number"" ON ""TeamPlayer"" (""Number""); +CREATE UNIQUE INDEX ""IX_TeamPlayer_NumberPerTeam"" ON ""TeamPlayer"" (""Number"", ""TeamId""); +CREATE INDEX ""IX_TeamPlayer_Mentor_Id"" ON ""TeamPlayer"" (""Mentor_Id""); +CREATE UNIQUE INDEX ""IX_Stadion_Main"" ON ""Stadions"" (""Street"", ""Name""); +CREATE UNIQUE INDEX ""ReservedKeyWordTest"" ON ""Stadions"" (""Order""); +CREATE INDEX ""IX_Stadion_Team_Id"" ON ""Stadions"" (""Team_Id""); +CREATE INDEX ""IX_Foo_FooSelf1Id"" ON ""Foos"" (""FooSelf1Id""); +CREATE INDEX ""IX_Foo_FooSelf2Id"" ON ""Foos"" (""FooSelf2Id""); +CREATE INDEX ""IX_Foo_FooSelf3Id"" ON ""Foos"" (""FooSelf3Id""); +CREATE INDEX ""IX_FooSelf_FooId"" ON ""FooSelves"" (""FooId""); +CREATE INDEX ""IX_FooStep_FooId"" ON ""FooSteps"" (""FooId""); +CREATE INDEX ""IX_FooRelationshipAFooCompositeKey_FooRelationshipA_Id"" ON ""FooRelationshipAFooCompositeKeys"" (""FooRelationshipA_Id""); +CREATE INDEX ""IX_FooRelationshipAFooCompositeKey_FooCompositeKey_Id_FooCompositeKey_Version"" ON ""FooRelationshipAFooCompositeKeys"" (""FooCompositeKey_Id"", ""FooCompositeKey_Version""); +CREATE INDEX ""IX_FooRelationshipBFooCompositeKey_FooRelationshipB_Id"" ON ""FooRelationshipBFooCompositeKeys"" (""FooRelationshipB_Id""); +CREATE INDEX ""IX_FooRelationshipBFooCompositeKey_FooCompositeKey_Id_FooCompositeKey_Version"" ON ""FooRelationshipBFooCompositeKeys"" (""FooCompositeKey_Id"", ""FooCompositeKey_Version""); +"; + + private static string generatedSql; + + // Does not work on the build server. No clue why. + + [TestMethod] + public void SqliteSqlGeneratorWithDefaultCollationTest() + { + using (DbConnection connection = new SQLiteConnection("FullUri=file::memory:")) + { + // This is important! Else the in memory database will not work. + connection.Open(); + + var defaultCollation = new Collation() { Function = CollationFunction.Custom, CustomFunction = "custom_collate" }; + using (var context = new DummyDbContext(connection, defaultCollation)) + { + // ReSharper disable once UnusedVariable + Player fo = context.Set().FirstOrDefault(); + + Assert.AreEqual(RemoveLineEndings(ReferenceSql), RemoveLineEndings(generatedSql)); + } + } + } + + private static string RemoveLineEndings(string input) + { + string lineSeparator = ((char)0x2028).ToString(); + string paragraphSeparator = ((char)0x2029).ToString(); + return input.Replace("\r\n", string.Empty).Replace("\n", string.Empty).Replace("\r", string.Empty).Replace(lineSeparator, string.Empty).Replace(paragraphSeparator, string.Empty); + } + + private class DummyDbContext : DbContext + { + private readonly Collation defaultCollation; + + public DummyDbContext(DbConnection connection, Collation defaultCollation = null) + : base(connection, false) + { + this.defaultCollation = defaultCollation; + } + + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + // This configuration contains all supported cases. + // So it makes a perfect test to validate whether the + // generated SQL is correct. + ModelConfiguration.Configure(modelBuilder); + var initializer = new AssertInitializer(modelBuilder, defaultCollation); + Database.SetInitializer(initializer); + } + + private class AssertInitializer : SqliteInitializerBase + { + private readonly Collation defaultCollation; + + public AssertInitializer(DbModelBuilder modelBuilder, Collation defaultCollation) + : base(modelBuilder) + { + this.defaultCollation = defaultCollation; + } + + public override void InitializeDatabase(DummyDbContext context) + { + DbModel model = ModelBuilder.Build(context.Database.Connection); + var sqliteSqlGenerator = new SqliteSqlGenerator(defaultCollation); + generatedSql = sqliteSqlGenerator.Generate(model.StoreModel); + base.InitializeDatabase(context); + } + } + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.Test/IntegrationTests/SqlGenerationTest.cs b/SQLite.CodeFirst.Test/IntegrationTests/SqlGenerationTest.cs new file mode 100644 index 0000000..6be0a61 --- /dev/null +++ b/SQLite.CodeFirst.Test/IntegrationTests/SqlGenerationTest.cs @@ -0,0 +1,111 @@ +using System.Data.Common; +using System.Data.Entity; +using System.Data.Entity.Infrastructure; +using System.Data.SQLite; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Console; +using SQLite.CodeFirst.Console.Entity; + +namespace SQLite.CodeFirst.Test.IntegrationTests +{ + [TestClass] + public class SqlGenerationTest + { + private const string ReferenceSql = + @" +CREATE TABLE ""MyTable"" ([Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, [Name] nvarchar NOT NULL, FOREIGN KEY ([Id]) REFERENCES ""Coaches""([Id])); +CREATE TABLE ""Coaches"" ([Id] INTEGER PRIMARY KEY, [FirstName] nvarchar (50) COLLATE NOCASE, [LastName] nvarchar (50), [Street] nvarchar (100), [City] nvarchar NOT NULL, [CreatedUtc] datetime NOT NULL DEFAULT (DATETIME('now'))); +CREATE TABLE ""TeamPlayer"" ([Id] INTEGER PRIMARY KEY, [Number] int NOT NULL, [TeamId] int NOT NULL, [FirstName] nvarchar (50) COLLATE NOCASE, [LastName] nvarchar (50), [Street] nvarchar (100), [City] nvarchar NOT NULL, [CreatedUtc] datetime NOT NULL DEFAULT (DATETIME('now')), [Mentor_Id] int, FOREIGN KEY ([Mentor_Id]) REFERENCES ""TeamPlayer""([Id]), FOREIGN KEY ([TeamId]) REFERENCES ""MyTable""([Id]) ON DELETE CASCADE); +CREATE TABLE ""Stadions"" ([Name] nvarchar (128) NOT NULL, [Street] nvarchar (128) NOT NULL, [City] nvarchar (128) NOT NULL, [Order] int NOT NULL, [Team_Id] int NOT NULL, PRIMARY KEY([Name], [Street], [City]), FOREIGN KEY ([Team_Id]) REFERENCES ""MyTable""([Id]) ON DELETE CASCADE); +CREATE TABLE ""Foos"" ([FooId] INTEGER PRIMARY KEY, [Name] nvarchar, [FooSelf1Id] int, [FooSelf2Id] int, [FooSelf3Id] int, FOREIGN KEY ([FooSelf1Id]) REFERENCES ""Foos""([FooId]), FOREIGN KEY ([FooSelf2Id]) REFERENCES ""Foos""([FooId]), FOREIGN KEY ([FooSelf3Id]) REFERENCES ""Foos""([FooId])); +CREATE TABLE ""FooSelves"" ([FooSelfId] INTEGER PRIMARY KEY, [FooId] int NOT NULL, [Number] int NOT NULL, FOREIGN KEY ([FooId]) REFERENCES ""Foos""([FooId]) ON DELETE CASCADE); +CREATE TABLE ""FooSteps"" ([FooStepId] INTEGER PRIMARY KEY, [FooId] int NOT NULL, [Number] int NOT NULL, FOREIGN KEY ([FooId]) REFERENCES ""Foos""([FooId]) ON DELETE CASCADE); +CREATE TABLE ""FooCompositeKeys"" ([Id] int NOT NULL, [Version] nvarchar (20) NOT NULL, [Name] nvarchar (255), PRIMARY KEY([Id], [Version])); +CREATE TABLE ""FooRelationshipAs"" ([Id] INTEGER PRIMARY KEY, [Name] nvarchar (255)); +CREATE TABLE ""FooRelationshipBs"" ([Id] INTEGER PRIMARY KEY, [Name] nvarchar (255)); +CREATE TABLE ""FooRelationshipAFooCompositeKeys"" ([FooRelationshipA_Id] int NOT NULL, [FooCompositeKey_Id] int NOT NULL, [FooCompositeKey_Version] nvarchar (20) NOT NULL, PRIMARY KEY([FooRelationshipA_Id], [FooCompositeKey_Id], [FooCompositeKey_Version]), FOREIGN KEY ([FooRelationshipA_Id]) REFERENCES ""FooRelationshipAs""([Id]) ON DELETE CASCADE, FOREIGN KEY ([FooCompositeKey_Id], [FooCompositeKey_Version]) REFERENCES ""FooCompositeKeys""([Id], [Version]) ON DELETE CASCADE); +CREATE TABLE ""FooRelationshipBFooCompositeKeys"" ([FooRelationshipB_Id] int NOT NULL, [FooCompositeKey_Id] int NOT NULL, [FooCompositeKey_Version] nvarchar (20) NOT NULL, PRIMARY KEY([FooRelationshipB_Id], [FooCompositeKey_Id], [FooCompositeKey_Version]), FOREIGN KEY ([FooRelationshipB_Id]) REFERENCES ""FooRelationshipBs""([Id]) ON DELETE CASCADE, FOREIGN KEY ([FooCompositeKey_Id], [FooCompositeKey_Version]) REFERENCES ""FooCompositeKeys""([Id], [Version]) ON DELETE CASCADE); +CREATE INDEX ""IX_MyTable_Id"" ON ""MyTable"" (""Id""); +CREATE INDEX ""IX_Team_TeamsName"" ON ""MyTable"" (""Name""); +CREATE INDEX ""IX_TeamPlayer_Number"" ON ""TeamPlayer"" (""Number""); +CREATE UNIQUE INDEX ""IX_TeamPlayer_NumberPerTeam"" ON ""TeamPlayer"" (""Number"", ""TeamId""); +CREATE INDEX ""IX_TeamPlayer_Mentor_Id"" ON ""TeamPlayer"" (""Mentor_Id""); +CREATE UNIQUE INDEX ""IX_Stadion_Main"" ON ""Stadions"" (""Street"", ""Name""); +CREATE UNIQUE INDEX ""ReservedKeyWordTest"" ON ""Stadions"" (""Order""); +CREATE INDEX ""IX_Stadion_Team_Id"" ON ""Stadions"" (""Team_Id""); +CREATE INDEX ""IX_Foo_FooSelf1Id"" ON ""Foos"" (""FooSelf1Id""); +CREATE INDEX ""IX_Foo_FooSelf2Id"" ON ""Foos"" (""FooSelf2Id""); +CREATE INDEX ""IX_Foo_FooSelf3Id"" ON ""Foos"" (""FooSelf3Id""); +CREATE INDEX ""IX_FooSelf_FooId"" ON ""FooSelves"" (""FooId""); +CREATE INDEX ""IX_FooStep_FooId"" ON ""FooSteps"" (""FooId""); +CREATE INDEX ""IX_FooRelationshipAFooCompositeKey_FooRelationshipA_Id"" ON ""FooRelationshipAFooCompositeKeys"" (""FooRelationshipA_Id""); +CREATE INDEX ""IX_FooRelationshipAFooCompositeKey_FooCompositeKey_Id_FooCompositeKey_Version"" ON ""FooRelationshipAFooCompositeKeys"" (""FooCompositeKey_Id"", ""FooCompositeKey_Version""); +CREATE INDEX ""IX_FooRelationshipBFooCompositeKey_FooRelationshipB_Id"" ON ""FooRelationshipBFooCompositeKeys"" (""FooRelationshipB_Id""); +CREATE INDEX ""IX_FooRelationshipBFooCompositeKey_FooCompositeKey_Id_FooCompositeKey_Version"" ON ""FooRelationshipBFooCompositeKeys"" (""FooCompositeKey_Id"", ""FooCompositeKey_Version""); +"; + + private static string generatedSql; + + // Does not work on the build server. No clue why. + + [TestMethod] + public void SqliteSqlGeneratorTest() + { + using (DbConnection connection = new SQLiteConnection("FullUri=file::memory:")) + { + // This is important! Else the in memory database will not work. + connection.Open(); + + using (var context = new DummyDbContext(connection)) + { + // ReSharper disable once UnusedVariable + Player fo = context.Set().FirstOrDefault(); + + Assert.AreEqual(RemoveLineEndings(ReferenceSql), RemoveLineEndings(generatedSql)); + } + } + } + + private static string RemoveLineEndings(string input) + { + string lineSeparator = ((char)0x2028).ToString(); + string paragraphSeparator = ((char)0x2029).ToString(); + return input.Replace("\r\n", string.Empty).Replace("\n", string.Empty).Replace("\r", string.Empty).Replace(lineSeparator, string.Empty).Replace(paragraphSeparator, string.Empty); + } + + private class DummyDbContext : DbContext + { + public DummyDbContext(DbConnection connection) + : base(connection, false) + { + } + + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + // This configuration contains all supported cases. + // So it makes a perfect test to validate whether the + // generated SQL is correct. + ModelConfiguration.Configure(modelBuilder); + var initializer = new AssertInitializer(modelBuilder); + Database.SetInitializer(initializer); + } + } + + private class AssertInitializer : SqliteInitializerBase + { + public AssertInitializer(DbModelBuilder modelBuilder) + : base(modelBuilder) + { + } + + public override void InitializeDatabase(DummyDbContext context) + { + DbModel model = ModelBuilder.Build(context.Database.Connection); + var sqliteSqlGenerator = new SqliteSqlGenerator(); + generatedSql = sqliteSqlGenerator.Generate(model.StoreModel); + base.InitializeDatabase(context); + } + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.Test/Properties/AssemblyInfo.cs b/SQLite.CodeFirst.Test/Properties/AssemblyInfo.cs deleted file mode 100644 index cda4f2d..0000000 --- a/SQLite.CodeFirst.Test/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("SQLite.CodeFirst.Test")] -[assembly: AssemblyDescription("Contains the Unit Tests for the SQLite.CodeFirst Library.")] -[assembly: AssemblyProduct("SQLite.CodeFirst")] -[assembly: AssemblyCopyright("Copyright © Marc Sallin")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersion("1.0.0.0")] diff --git a/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj b/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj index e4c30c6..a0798e2 100644 --- a/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj +++ b/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj @@ -1,124 +1,29 @@ - - + - Debug - AnyCPU - {E542F38D-852E-489D-81C2-BF333503E10F} - Library - Properties - SQLite.CodeFirst.Test - SQLite.CodeFirst.Test - v4.5.2 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest + net48 + SQLite.CodeFirst.Test + Contains the Unit Tests for the SQLite.CodeFirst Library. + 1.0.0.0 + true + ..\Shared\SQLite.CodeFirst.StrongNameKey.snk - true full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - True - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - True - - - ..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll - True - False - ..\Lib\Moq.dll - - - - - - - - - - - - - - False - - - - - - - - - - - - - - - - - + + + + - - - - - - - {50a32fe4-0e13-4213-a373-72523cdf34d9} - SQLite.CodeFirst - + - + + - - - - - False - - - False - - - False - - - False - - - - - - - \ No newline at end of file diff --git a/SQLite.CodeFirst.Test/UnitTests/Builder/NameCreators/IndexNameCreatorTest.cs b/SQLite.CodeFirst.Test/UnitTests/Builder/NameCreators/IndexNameCreatorTest.cs new file mode 100644 index 0000000..1604fd7 --- /dev/null +++ b/SQLite.CodeFirst.Test/UnitTests/Builder/NameCreators/IndexNameCreatorTest.cs @@ -0,0 +1,23 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Builder.NameCreators; + +namespace SQLite.CodeFirst.Test.UnitTests.Builder.NameCreators +{ + [TestClass] + public class IndexNameCreatorTest + { + [TestMethod] + public void CreateIndexName() + { + string result = IndexNameCreator.CreateName("MyTable", "MyProperty"); + Assert.AreEqual("\"IX_MyTable_MyProperty\"", result); + } + + [TestMethod] + public void CreateIndexNameEscaped() + { + string result = IndexNameCreator.CreateName("\"base.MyTable\"", "MyProperty"); + Assert.AreEqual("\"IX_base.MyTable_MyProperty\"", result); + } + } +} diff --git a/SQLite.CodeFirst.Test/UnitTests/Builder/NameCreators/NameCreatorTest.cs b/SQLite.CodeFirst.Test/UnitTests/Builder/NameCreators/NameCreatorTest.cs new file mode 100644 index 0000000..1f28e45 --- /dev/null +++ b/SQLite.CodeFirst.Test/UnitTests/Builder/NameCreators/NameCreatorTest.cs @@ -0,0 +1,16 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Builder.NameCreators; + +namespace SQLite.CodeFirst.Test.UnitTests.Builder.NameCreators +{ + [TestClass] + public class NameCreatorTest + { + [TestMethod] + public void CreateTableNameTest() + { + string result = NameCreator.EscapeName("Test"); + Assert.AreEqual("\"Test\"", result); + } + } +} diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/CollateConstraintTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/CollateConstraintTest.cs new file mode 100644 index 0000000..8697d24 --- /dev/null +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/CollateConstraintTest.cs @@ -0,0 +1,27 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Statement.ColumnConstraint; + +namespace SQLite.CodeFirst.Test.UnitTests.Statement.ColumnConstraint +{ + [TestClass] + public class CollateConstraintTest + { + [TestMethod] + public void CreateStatement_StatementIsCorrect_NoConstraint() + { + var collationConstraint = new CollateConstraint(); + collationConstraint.CollationFunction = CollationFunction.None; + string output = collationConstraint.CreateStatement(); + Assert.AreEqual(output, ""); + } + + [TestMethod] + public void CreateStatement_StatementIsCorrect_NoCase() + { + var collationConstraint = new CollateConstraint(); + collationConstraint.CollationFunction = CollationFunction.NoCase; + string output = collationConstraint.CreateStatement(); + Assert.AreEqual(output, "COLLATE NOCASE"); + } + } +} diff --git a/SQLite.CodeFirst.Test/Statement/ColumnConstraint/ColumnConstraintCollectionTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/ColumnConstraintCollectionTest.cs similarity index 95% rename from SQLite.CodeFirst.Test/Statement/ColumnConstraint/ColumnConstraintCollectionTest.cs rename to SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/ColumnConstraintCollectionTest.cs index e8d9d22..eb6aa47 100644 --- a/SQLite.CodeFirst.Test/Statement/ColumnConstraint/ColumnConstraintCollectionTest.cs +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/ColumnConstraintCollectionTest.cs @@ -2,7 +2,7 @@ using Moq; using SQLite.CodeFirst.Statement.ColumnConstraint; -namespace SQLite.CodeFirst.Test.Statement.ColumnConstraint +namespace SQLite.CodeFirst.Test.UnitTests.Statement.ColumnConstraint { [TestClass] public class ColumnConstraintCollectionTest : StatementTestBase diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/DefaultValueConstraintTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/DefaultValueConstraintTest.cs new file mode 100644 index 0000000..da2a20a --- /dev/null +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/DefaultValueConstraintTest.cs @@ -0,0 +1,36 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Statement.ColumnConstraint; + +namespace SQLite.CodeFirst.Test.UnitTests.Statement.ColumnConstraint +{ + [TestClass] + public class DefaultValueConstraintTest + { + [TestMethod] + public void CreateStatement_StatementIsCorrect_IntDefault() + { + var defaultValueConstraint = new DefaultValueConstraint(); + defaultValueConstraint.DefaultValue = "0"; + string output = defaultValueConstraint.CreateStatement(); + Assert.AreEqual(output, "DEFAULT (0)"); + } + + [TestMethod] + public void CreateStatement_StatementIsCorrect_StringDefault() + { + var defaultValueConstraint = new DefaultValueConstraint(); + defaultValueConstraint.DefaultValue = @"'Something'"; + string output = defaultValueConstraint.CreateStatement(); + Assert.AreEqual(output, "DEFAULT ('Something')"); + } + + [TestMethod] + public void CreateStatement_StatementIsCorrect_ExpressionDefault() + { + var defaultValueConstraint = new DefaultValueConstraint(); + defaultValueConstraint.DefaultValue = @"datetime('now')"; + string output = defaultValueConstraint.CreateStatement(); + Assert.AreEqual(output, "DEFAULT (datetime('now'))"); + } + } +} diff --git a/SQLite.CodeFirst.Test/Statement/ColumnConstraint/MaxLengthConstraint.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/MaxLengthConstraint.cs similarity index 91% rename from SQLite.CodeFirst.Test/Statement/ColumnConstraint/MaxLengthConstraint.cs rename to SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/MaxLengthConstraint.cs index 34259b3..1df077f 100644 --- a/SQLite.CodeFirst.Test/Statement/ColumnConstraint/MaxLengthConstraint.cs +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/MaxLengthConstraint.cs @@ -2,7 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using SQLite.CodeFirst.Statement.ColumnConstraint; -namespace SQLite.CodeFirst.Test.Statement.ColumnConstraint +namespace SQLite.CodeFirst.Test.UnitTests.Statement.ColumnConstraint { [TestClass] public class MaxLengthConstraintTest : StatementTestBase diff --git a/SQLite.CodeFirst.Test/Statement/ColumnConstraint/NotNullConstraintTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/NotNullConstraintTest.cs similarity index 86% rename from SQLite.CodeFirst.Test/Statement/ColumnConstraint/NotNullConstraintTest.cs rename to SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/NotNullConstraintTest.cs index d2b3a91..747fdbf 100644 --- a/SQLite.CodeFirst.Test/Statement/ColumnConstraint/NotNullConstraintTest.cs +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/NotNullConstraintTest.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using SQLite.CodeFirst.Statement.ColumnConstraint; -namespace SQLite.CodeFirst.Test.Statement.ColumnConstraint +namespace SQLite.CodeFirst.Test.UnitTests.Statement.ColumnConstraint { [TestClass] public class NotNullConstraintTest : StatementTestBase diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/PrimaryKeyConstraintTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/PrimaryKeyConstraintTest.cs new file mode 100644 index 0000000..5d5bc6d --- /dev/null +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/PrimaryKeyConstraintTest.cs @@ -0,0 +1,25 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Statement.ColumnConstraint; + +namespace SQLite.CodeFirst.Test.UnitTests.Statement.ColumnConstraint +{ + [TestClass] + public class PrimaryKeyConstraintTest + { + [TestMethod] + public void CreateStatement_StatementIsCorrect_WithAutoincrement() + { + var primaryKeyConstraint = new PrimaryKeyConstraint { Autoincrement = true }; + string output = primaryKeyConstraint.CreateStatement(); + Assert.AreEqual(output, "PRIMARY KEY AUTOINCREMENT"); + } + + [TestMethod] + public void CreateStatement_StatementIsCorrect() + { + var primaryKeyConstraint = new PrimaryKeyConstraint(); + string output = primaryKeyConstraint.CreateStatement(); + Assert.AreEqual(output, "PRIMARY KEY"); + } + } +} diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/UniqueConstraintTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/UniqueConstraintTest.cs new file mode 100644 index 0000000..d09e2c5 --- /dev/null +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/UniqueConstraintTest.cs @@ -0,0 +1,27 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Statement.ColumnConstraint; + +namespace SQLite.CodeFirst.Test.UnitTests.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.Test/Statement/ColumnStatementCollectionTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementCollectionTest.cs similarity index 95% rename from SQLite.CodeFirst.Test/Statement/ColumnStatementCollectionTest.cs rename to SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementCollectionTest.cs index fa0ef5c..eccb3fb 100644 --- a/SQLite.CodeFirst.Test/Statement/ColumnStatementCollectionTest.cs +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementCollectionTest.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using SQLite.CodeFirst.Statement; -namespace SQLite.CodeFirst.Test.Statement +namespace SQLite.CodeFirst.Test.UnitTests.Statement { [TestClass] public class ColumnStatementCollectionTest : StatementTestBase diff --git a/SQLite.CodeFirst.Test/Statement/ColumnStatementTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementTest.cs similarity index 90% rename from SQLite.CodeFirst.Test/Statement/ColumnStatementTest.cs rename to SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementTest.cs index 3289303..9e47dc5 100644 --- a/SQLite.CodeFirst.Test/Statement/ColumnStatementTest.cs +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementTest.cs @@ -3,7 +3,7 @@ using SQLite.CodeFirst.Statement; using SQLite.CodeFirst.Statement.ColumnConstraint; -namespace SQLite.CodeFirst.Test.Statement +namespace SQLite.CodeFirst.Test.UnitTests.Statement { [TestClass] public class ColumnStatementTest : StatementTestBase @@ -16,9 +16,9 @@ public void CreateStatement() var columnStatement = new ColumnStatement { - ColumnConstraints = columnConstraintsMock.Object, ColumnName = "dummyColumnName", - TypeName = "dummyType" + TypeName = "dummyType", + ColumnConstraints = columnConstraintsMock.Object, }; string output = columnStatement.CreateStatement(); Assert.AreEqual(output, "[dummyColumnName] dummyType dummyColumnConstraint"); diff --git a/SQLite.CodeFirst.Test/Statement/CreateDatabaseStatementTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/CreateDatabaseStatementTest.cs similarity index 95% rename from SQLite.CodeFirst.Test/Statement/CreateDatabaseStatementTest.cs rename to SQLite.CodeFirst.Test/UnitTests/Statement/CreateDatabaseStatementTest.cs index d5f4c1e..33e4f39 100644 --- a/SQLite.CodeFirst.Test/Statement/CreateDatabaseStatementTest.cs +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/CreateDatabaseStatementTest.cs @@ -2,7 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using SQLite.CodeFirst.Statement; -namespace SQLite.CodeFirst.Test.Statement +namespace SQLite.CodeFirst.Test.UnitTests.Statement { [TestClass] public class CreateDatabaseStatementTest : StatementTestBase diff --git a/SQLite.CodeFirst.Test/Statement/CreateIndexStatementCollectionTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/CreateIndexStatementCollectionTest.cs similarity index 95% rename from SQLite.CodeFirst.Test/Statement/CreateIndexStatementCollectionTest.cs rename to SQLite.CodeFirst.Test/UnitTests/Statement/CreateIndexStatementCollectionTest.cs index 41f2434..1082710 100644 --- a/SQLite.CodeFirst.Test/Statement/CreateIndexStatementCollectionTest.cs +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/CreateIndexStatementCollectionTest.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using SQLite.CodeFirst.Statement; -namespace SQLite.CodeFirst.Test.Statement +namespace SQLite.CodeFirst.Test.UnitTests.Statement { [TestClass] public class CreateIndexStatementCollectionTest : StatementTestBase diff --git a/SQLite.CodeFirst.Test/Statement/CreateTableStatementTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/CreateTableStatementTest.cs similarity index 92% rename from SQLite.CodeFirst.Test/Statement/CreateTableStatementTest.cs rename to SQLite.CodeFirst.Test/UnitTests/Statement/CreateTableStatementTest.cs index accf52a..31d3817 100644 --- a/SQLite.CodeFirst.Test/Statement/CreateTableStatementTest.cs +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/CreateTableStatementTest.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using SQLite.CodeFirst.Statement; -namespace SQLite.CodeFirst.Test.Statement +namespace SQLite.CodeFirst.Test.UnitTests.Statement { [TestClass] public class CreateTableStatementTest : StatementTestBase diff --git a/SQLite.CodeFirst.Test/Statement/ForeignKeyStatementTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ForeignKeyStatementTest.cs similarity index 77% rename from SQLite.CodeFirst.Test/Statement/ForeignKeyStatementTest.cs rename to SQLite.CodeFirst.Test/UnitTests/Statement/ForeignKeyStatementTest.cs index 47943a1..c3472de 100644 --- a/SQLite.CodeFirst.Test/Statement/ForeignKeyStatementTest.cs +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ForeignKeyStatementTest.cs @@ -2,7 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using SQLite.CodeFirst.Statement; -namespace SQLite.CodeFirst.Test.Statement +namespace SQLite.CodeFirst.Test.UnitTests.Statement { [TestClass] public class ForeignKeyStatementTest : StatementTestBase @@ -19,7 +19,7 @@ public void CreateStatementOneForeignKeyTest() }; string output = foreignKeyStatement.CreateStatement(); - Assert.AreEqual(output, "FOREIGN KEY (dummyForeignKey1) REFERENCES dummyForeignTable(dummForeignPrimaryKey1)"); + Assert.AreEqual(output, "FOREIGN KEY ([dummyForeignKey1]) REFERENCES dummyForeignTable([dummForeignPrimaryKey1])"); } [TestMethod] @@ -34,7 +34,7 @@ public void CreateStatementOneForeignKeyCascadeDeleteTest() }; string output = foreignKeyStatement.CreateStatement(); - Assert.AreEqual(output, "FOREIGN KEY (dummyForeignKey1) REFERENCES dummyForeignTable(dummForeignPrimaryKey1) ON DELETE CASCADE"); + Assert.AreEqual(output, "FOREIGN KEY ([dummyForeignKey1]) REFERENCES dummyForeignTable([dummForeignPrimaryKey1]) ON DELETE CASCADE"); } [TestMethod] @@ -49,7 +49,7 @@ public void CreateStatementTwoForeignKeyTest() }; string output = foreignKeyStatement.CreateStatement(); - Assert.AreEqual(output, "FOREIGN KEY (dummyForeignKey1, dummyForeignKey2) REFERENCES dummyForeignTable(dummForeignPrimaryKey1)"); + Assert.AreEqual(output, "FOREIGN KEY ([dummyForeignKey1], [dummyForeignKey2]) REFERENCES dummyForeignTable([dummForeignPrimaryKey1])"); } [TestMethod] @@ -64,7 +64,7 @@ public void CreateStatementTwoForeignKeyTwoPrimaryKeyTest() }; string output = foreignKeyStatement.CreateStatement(); - Assert.AreEqual(output, "FOREIGN KEY (dummyForeignKey1, dummyForeignKey2) REFERENCES dummyForeignTable(dummForeignPrimaryKey1, dummForeignPrimaryKey2)"); + Assert.AreEqual(output, "FOREIGN KEY ([dummyForeignKey1], [dummyForeignKey2]) REFERENCES dummyForeignTable([dummForeignPrimaryKey1], [dummForeignPrimaryKey2])"); } [TestMethod] @@ -79,7 +79,7 @@ public void CreateStatementOneForeignKeyTwoPrimaryKeyTest() }; string output = foreignKeyStatement.CreateStatement(); - Assert.AreEqual(output, "FOREIGN KEY (dummyForeignKey1) REFERENCES dummyForeignTable(dummForeignPrimaryKey1, dummForeignPrimaryKey2)"); + Assert.AreEqual(output, "FOREIGN KEY ([dummyForeignKey1]) REFERENCES dummyForeignTable([dummForeignPrimaryKey1], [dummForeignPrimaryKey2])"); } } } diff --git a/SQLite.CodeFirst.Test/Statement/PrimaryKeyStatementTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/PrimaryKeyStatementTest.cs similarity index 69% rename from SQLite.CodeFirst.Test/Statement/PrimaryKeyStatementTest.cs rename to SQLite.CodeFirst.Test/UnitTests/Statement/PrimaryKeyStatementTest.cs index 11204e3..beffd06 100644 --- a/SQLite.CodeFirst.Test/Statement/PrimaryKeyStatementTest.cs +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/PrimaryKeyStatementTest.cs @@ -2,7 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using SQLite.CodeFirst.Statement; -namespace SQLite.CodeFirst.Test.Statement +namespace SQLite.CodeFirst.Test.UnitTests.Statement { [TestClass] public class PrimaryKeyStatementTest : StatementTestBase @@ -12,9 +12,9 @@ public void CreateStatementWithOneKeyTest() { const string keyMember1 = "keyMember1"; - var primaryKeyStatement = new PrimaryKeyStatement(new List { keyMember1 }); + var primaryKeyStatement = new CompositePrimaryKeyStatement(new List { keyMember1 }); Assert.AreEqual(primaryKeyStatement.Count, 1); - Assert.AreEqual(primaryKeyStatement.CreateStatement(), "PRIMARY KEY(keyMember1)"); + Assert.AreEqual(primaryKeyStatement.CreateStatement(), "PRIMARY KEY([keyMember1])"); } [TestMethod] @@ -23,9 +23,9 @@ public void CreateStatementWithTwoKeyTest() const string keyMember1 = "keyMember1"; const string keyMember2 = "keyMember2"; - var primaryKeyStatement = new PrimaryKeyStatement(new List { keyMember1, keyMember2 }); + var primaryKeyStatement = new CompositePrimaryKeyStatement(new List { keyMember1, keyMember2 }); Assert.AreEqual(primaryKeyStatement.Count, 2); - Assert.AreEqual(primaryKeyStatement.CreateStatement(), "PRIMARY KEY(keyMember1, keyMember2)"); + Assert.AreEqual(primaryKeyStatement.CreateStatement(), "PRIMARY KEY([keyMember1], [keyMember2])"); } } } diff --git a/SQLite.CodeFirst.Test/Statement/StatementTestBase.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/StatementTestBase.cs similarity index 93% rename from SQLite.CodeFirst.Test/Statement/StatementTestBase.cs rename to SQLite.CodeFirst.Test/UnitTests/Statement/StatementTestBase.cs index d117e61..10250e3 100644 --- a/SQLite.CodeFirst.Test/Statement/StatementTestBase.cs +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/StatementTestBase.cs @@ -1,7 +1,7 @@ using Moq; using SQLite.CodeFirst.Statement; -namespace SQLite.CodeFirst.Test.Statement +namespace SQLite.CodeFirst.Test.UnitTests.Statement { public abstract class StatementTestBase { diff --git a/SQLite.CodeFirst.Test/UnitTests/Utility/ConnectionStringParserTest.cs b/SQLite.CodeFirst.Test/UnitTests/Utility/ConnectionStringParserTest.cs new file mode 100644 index 0000000..998f5c8 --- /dev/null +++ b/SQLite.CodeFirst.Test/UnitTests/Utility/ConnectionStringParserTest.cs @@ -0,0 +1,110 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Utility; + +namespace SQLite.CodeFirst.Test.UnitTests.Utility +{ + [TestClass] + public class ConnectionStringParserTest + { + [TestMethod] + public void GetDataSource_ReturnsCorrectDataSource() + { + // Arrange + string connectionString = @"data source=.\db\footballDb\footballDb.sqlite;foreign keys=true"; + + + // Act + string result = ConnectionStringParser.GetDataSource(connectionString); + + // Assert + Assert.AreEqual(@".\db\footballDb\footballDb.sqlite", result); + } + + [TestMethod] + public void GetDataSource_ReturnsCorrectDataSource_NotLowerCase() + { + // Arrange + string connectionString = @"DatA SOurce=.\db\footballDb\footballDb.sqlite;foreign keys=true"; + + + // Act + string result = ConnectionStringParser.GetDataSource(connectionString); + + // Assert + Assert.AreEqual(@".\db\footballDb\footballDb.sqlite", result); + } + + [TestMethod] + public void GetDataSource_ReturnsCorrectDataSource_WithSpace() + { + // Arrange + string connectionString = @"DatA SOurce =.\db\footballDb\footballDb.sqlite;foreign keys=true"; + + + // Act + string result = ConnectionStringParser.GetDataSource(connectionString); + + // Assert + Assert.AreEqual(@".\db\footballDb\footballDb.sqlite", result); + } + + [TestMethod] + public void GetDataSource_ReturnsCorrectDataSource_ReplacesDataDirectory() + { + // Arrange + string connectionString = @"data source=|DataDirectory|\db\footballDb\footballDb.sqlite;foreign keys=true"; + + + // Act + string result = ConnectionStringParser.GetDataSource(connectionString); + + // Assert + Assert.IsTrue(!result.StartsWith("|DataDirectory|")); + Assert.IsTrue(result.EndsWith(@"db\footballDb\footballDb.sqlite")); + } + + [TestMethod] + public void GetDataSource_ReturnsCorrectDataSource_ReplacesDataDirectoryCaseIsIgnored() + { + // Arrange + string connectionString = @"data source=|dAtadIrectory|\db\footballDb\footballDb.sqlite;foreign keys=true"; + + + // Act + string result = ConnectionStringParser.GetDataSource(connectionString); + + // Assert + Assert.IsTrue(!result.StartsWith("|dAtadIrectory|")); + Assert.IsTrue(result.EndsWith(@"\db\footballDb\footballDb.sqlite")); + } + + [TestMethod] + public void GetDataSource_ReturnsCorrectDataSource_ReplacesDataDirectoryAddsBackslash() + { + // Arrange + string connectionString = @"data source=|DataDirectory|db\footballDb\footballDb.sqlite;foreign keys=true"; + + + // Act + string result = ConnectionStringParser.GetDataSource(connectionString); + + // Assert + Assert.IsTrue(!result.StartsWith("|DataDirectory|")); + Assert.IsTrue(result.EndsWith(@"\db\footballDb\footballDb.sqlite")); + } + + [TestMethod] + public void GetDataSource_ReturnsCorrectDataSource_RemovesQuotationMark() + { + // Arrange + string connectionString = @"data source="".\db\footballDb\footballDb.sqlite"";foreign keys=true"; + + + // Act + string result = ConnectionStringParser.GetDataSource(connectionString); + + // Assert + Assert.AreEqual(@".\db\footballDb\footballDb.sqlite", result); + } + } +} diff --git a/SQLite.CodeFirst.Test/UnitTests/Utility/HashCreatorTest.cs b/SQLite.CodeFirst.Test/UnitTests/Utility/HashCreatorTest.cs new file mode 100644 index 0000000..6ce0367 --- /dev/null +++ b/SQLite.CodeFirst.Test/UnitTests/Utility/HashCreatorTest.cs @@ -0,0 +1,23 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Utility; + +namespace SQLite.CodeFirst.Test.UnitTests.Utility +{ + [TestClass] + public class HashCreatorTest + { + [TestMethod] + public void CreateHashTest() + { + string result = HashCreator.CreateHash("Test,Test,Test"); + Assert.AreEqual("kMbs8GbjyafvacMkuACV+tDtaoM9ii8y7pxi8AjgfcFincyIrDiD6R8kTiO5lupnmcYqZMUHtQk144aV3HTTCg==", result); + } + + [TestMethod] + public void CreateHashNotSameHashTest() + { + string result = HashCreator.CreateHash("Test,Test,Test!"); + Assert.AreNotEqual("kMbs8GbjyafvacMkuACV+tDtaoM9ii8y7pxi8AjgfcFincyIrDiD6R8kTiO5lupnmcYqZMUHtQk144aV3HTTCg==", result); + } + } +} diff --git a/SQLite.CodeFirst.Test/UnitTests/Utility/HistoryEntityTypeValidatorTest.cs b/SQLite.CodeFirst.Test/UnitTests/Utility/HistoryEntityTypeValidatorTest.cs new file mode 100644 index 0000000..61fcfb2 --- /dev/null +++ b/SQLite.CodeFirst.Test/UnitTests/Utility/HistoryEntityTypeValidatorTest.cs @@ -0,0 +1,57 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Utility; + +namespace SQLite.CodeFirst.Test.UnitTests.Utility +{ + [TestClass] + public class HistoryEntityTypeValidatorTest + { + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void EnsureValidTypeNotIHistory() + { + HistoryEntityTypeValidator.EnsureValidType(typeof(InvalidFakeHistoryType1)); + } + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void EnsureValidTypeNoParamLessCtorTest() + { + HistoryEntityTypeValidator.EnsureValidType(typeof(InvalidFakeHistoryType2)); + } + + [TestMethod] + public void EnsureValidTypeTest() + { + HistoryEntityTypeValidator.EnsureValidType(typeof(ValidFakeHistoryType)); + } + + private class ValidFakeHistoryType : IHistory + { + public int Id { get; set; } + public string Hash { get; set; } + public string Context { get; set; } + public DateTime CreateDate { get; set; } + } + + private class InvalidFakeHistoryType1 + { + public int Id { get; set; } + public string Hash { get; set; } + public string Context { get; set; } + public DateTime CreateDate { get; set; } + } + + private class InvalidFakeHistoryType2 : IHistory + { + public InvalidFakeHistoryType2(string test) + { } + + public int Id { get; set; } + public string Hash { get; set; } + public string Context { get; set; } + public DateTime CreateDate { get; set; } + } + } +} diff --git a/SQLite.CodeFirst.Test/app.config b/SQLite.CodeFirst.Test/app.config new file mode 100644 index 0000000..2f401ce --- /dev/null +++ b/SQLite.CodeFirst.Test/app.config @@ -0,0 +1,32 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SQLite.CodeFirst.Test/packages.config b/SQLite.CodeFirst.Test/packages.config deleted file mode 100644 index 1a41592..0000000 --- a/SQLite.CodeFirst.Test/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/SQLite.CodeFirst.sln b/SQLite.CodeFirst.sln index 3a10ed8..856e8f8 100644 --- a/SQLite.CodeFirst.sln +++ b/SQLite.CodeFirst.sln @@ -1,80 +1,55 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29503.13 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite.CodeFirst", "SQLite.CodeFirst\SQLite.CodeFirst.csproj", "{50A32FE4-0E13-4213-A373-72523CDF34D9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLite.CodeFirst", "SQLite.CodeFirst\SQLite.CodeFirst.csproj", "{50A32FE4-0E13-4213-A373-72523CDF34D9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite.CodeFirst.Console", "SQLite.CodeFirst.Console\SQLite.CodeFirst.Console.csproj", "{DEDABD86-6EA0-4673-A858-A4F71958F51D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{01B97A84-5983-45C5-ACF8-168E56DF297C}" - ProjectSection(SolutionItems) = preProject - .nuget\NuGet.Config = .nuget\NuGet.Config - .nuget\NuGet.exe = .nuget\NuGet.exe - .nuget\NuGet.targets = .nuget\NuGet.targets - EndProjectSection +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLite.CodeFirst.Console", "SQLite.CodeFirst.Console\SQLite.CodeFirst.Console.csproj", "{DEDABD86-6EA0-4673-A858-A4F71958F51D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{7031BD3C-AE76-43CD-91B6-B6BCD823968C}" ProjectSection(SolutionItems) = preProject - BuildAllConfigurations.proj = BuildAllConfigurations.proj + ci_appveyor.yml = ci_appveyor.yml release_appveyor.yml = release_appveyor.yml EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite.CodeFirst.Test", "SQLite.CodeFirst.Test\SQLite.CodeFirst.Test.csproj", "{E542F38D-852E-489D-81C2-BF333503E10F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLite.CodeFirst.Test", "SQLite.CodeFirst.Test\SQLite.CodeFirst.Test.csproj", "{E542F38D-852E-489D-81C2-BF333503E10F}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lib", "Lib", "{436039AE-ADD6-48A5-81B9-C413E13344A9}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1F6D4E0E-5D07-4889-961A-A6AFC0ECD7C7}" ProjectSection(SolutionItems) = preProject - Lib\Moq.dll = Lib\Moq.dll + README.md = README.md + Shared\SQLite.CodeFirst.ruleset = Shared\SQLite.CodeFirst.ruleset + Shared\SQLite.CodeFirst.StrongNameKey.snk = Shared\SQLite.CodeFirst.StrongNameKey.snk EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite.CodeFirst.NetCore.Console", "SQLite.CodeFirst.NetCore.Console\SQLite.CodeFirst.NetCore.Console.csproj", "{709F46E3-D23B-4931-9985-8E09E3817CC4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug-40|Any CPU = Debug-40|Any CPU - Debug-45|Any CPU = Debug-45|Any CPU Release|Any CPU = Release|Any CPU - Release-40|Any CPU = Release-40|Any CPU - Release-45|Any CPU = Release-45|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug|Any CPU.ActiveCfg = Debug-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug|Any CPU.Build.0 = Debug-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug-40|Any CPU.ActiveCfg = Debug-40|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug-40|Any CPU.Build.0 = Debug-40|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug-45|Any CPU.ActiveCfg = Debug-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug-45|Any CPU.Build.0 = Debug-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release|Any CPU.ActiveCfg = Release-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release|Any CPU.Build.0 = Release-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release-40|Any CPU.ActiveCfg = Release-40|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release-40|Any CPU.Build.0 = Release-40|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release-45|Any CPU.ActiveCfg = Release-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release-45|Any CPU.Build.0 = Release-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug|Any CPU.ActiveCfg = Debug-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug|Any CPU.Build.0 = Debug-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug-40|Any CPU.ActiveCfg = Debug-40|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug-40|Any CPU.Build.0 = Debug-40|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug-45|Any CPU.ActiveCfg = Debug-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug-45|Any CPU.Build.0 = Debug-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release|Any CPU.ActiveCfg = Release-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release|Any CPU.Build.0 = Release-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release-40|Any CPU.ActiveCfg = Release-40|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release-40|Any CPU.Build.0 = Release-40|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release-45|Any CPU.ActiveCfg = Release-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release-45|Any CPU.Build.0 = Release-45|Any CPU + {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release|Any CPU.Build.0 = Release|Any CPU + {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release|Any CPU.Build.0 = Release|Any CPU {E542F38D-852E-489D-81C2-BF333503E10F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E542F38D-852E-489D-81C2-BF333503E10F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Debug-40|Any CPU.ActiveCfg = Debug|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Debug-40|Any CPU.Build.0 = Debug|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Debug-45|Any CPU.ActiveCfg = Debug|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Debug-45|Any CPU.Build.0 = Debug|Any CPU {E542F38D-852E-489D-81C2-BF333503E10F}.Release|Any CPU.ActiveCfg = Release|Any CPU {E542F38D-852E-489D-81C2-BF333503E10F}.Release|Any CPU.Build.0 = Release|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Release-40|Any CPU.ActiveCfg = Release|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Release-40|Any CPU.Build.0 = Release|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Release-45|Any CPU.ActiveCfg = Release|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Release-45|Any CPU.Build.0 = Release|Any CPU + {709F46E3-D23B-4931-9985-8E09E3817CC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {709F46E3-D23B-4931-9985-8E09E3817CC4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {709F46E3-D23B-4931-9985-8E09E3817CC4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {709F46E3-D23B-4931-9985-8E09E3817CC4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D21CDFB4-5695-4346-AB51-48C80AED4418} + EndGlobalSection EndGlobal diff --git a/SQLite.CodeFirst/Builder/ColumnStatementCollectionBuilder.cs b/SQLite.CodeFirst/Builder/ColumnStatementCollectionBuilder.cs deleted file mode 100644 index ca8d8b1..0000000 --- a/SQLite.CodeFirst/Builder/ColumnStatementCollectionBuilder.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Collections.Generic; -using System.Data.Entity.Core.Metadata.Edm; -using System.Globalization; -using System.Linq; -using SQLite.CodeFirst.Statement; -using SQLite.CodeFirst.Statement.ColumnConstraint; - -namespace SQLite.CodeFirst.Builder -{ - internal class ColumnStatementCollectionBuilder : IStatementBuilder - { - private readonly IEnumerable properties; - - public ColumnStatementCollectionBuilder(IEnumerable properties) - { - this.properties = properties; - } - - public ColumnStatementCollection BuildStatement() - { - var columnDefStatement = new ColumnStatementCollection(CreateColumnStatements().ToList()); - return columnDefStatement; - } - - private IEnumerable CreateColumnStatements() - { - foreach (var property in properties) - { - var columnStatement = new ColumnStatement - { - ColumnName = property.Name, - TypeName = property.TypeName, - ColumnConstraints = new ColumnConstraintCollection() - }; - - AddMaxLengthConstraintIfNecessary(property, columnStatement); - AdjustDatatypeForAutogenerationIfNecessary(property, columnStatement); - AddNullConstraintIfNecessary(property, columnStatement); - - yield return columnStatement; - } - } - - private static void AddMaxLengthConstraintIfNecessary(EdmProperty property, ColumnStatement columnStatement) - { - if (property.MaxLength.HasValue) - { - columnStatement.ColumnConstraints.Add(new MaxLengthConstraint(property.MaxLength.Value)); - } - } - - private static void AdjustDatatypeForAutogenerationIfNecessary(EdmProperty property, ColumnStatement columnStatement) - { - if (property.StoreGeneratedPattern == StoreGeneratedPattern.Identity) - { - // Must be INTEGER else SQLite will not generate the Ids - columnStatement.TypeName = columnStatement.TypeName.ToLower(CultureInfo.InvariantCulture) == "int" ? "INTEGER" : columnStatement.TypeName; - } - } - - private static void AddNullConstraintIfNecessary(EdmProperty property, ColumnStatement columnStatement) - { - if (!property.Nullable && property.StoreGeneratedPattern != StoreGeneratedPattern.Identity) - { - // Only mark it as NotNull if it should not be generated. - columnStatement.ColumnConstraints.Add(new NotNullConstraint()); - } - } - } -} diff --git a/SQLite.CodeFirst/Builder/CreateTableStatementBuilder.cs b/SQLite.CodeFirst/Builder/CreateTableStatementBuilder.cs deleted file mode 100644 index 69081d4..0000000 --- a/SQLite.CodeFirst/Builder/CreateTableStatementBuilder.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.Entity.Core.Metadata.Edm; -using SQLite.CodeFirst.Builder.NameCreators; -using SQLite.CodeFirst.Statement; -using SQLite.CodeFirst.Utility; - -namespace SQLite.CodeFirst.Builder -{ - internal class CreateTableStatementBuilder : IStatementBuilder - { - private readonly EntitySet entitySet; - private readonly IEnumerable associationTypes; - - public CreateTableStatementBuilder(EntitySet entitySet, IEnumerable associationTypes) - { - this.entitySet = entitySet; - this.associationTypes = associationTypes; - } - - public CreateTableStatement BuildStatement() - { - var simpleColumnCollection = new ColumnStatementCollectionBuilder(entitySet.ElementType.Properties).BuildStatement(); - var primaryKeyStatement = new PrimaryKeyStatementBuilder(entitySet.ElementType.KeyMembers).BuildStatement(); - var foreignKeyCollection = new ForeignKeyStatementBuilder(associationTypes).BuildStatement(); - - var columnStatements = new List(); - columnStatements.AddRange(simpleColumnCollection); - columnStatements.Add(primaryKeyStatement); - columnStatements.AddRange(foreignKeyCollection); - - return new CreateTableStatement - { - TableName = TableNameCreator.CreateTableName(entitySet.Table), - ColumnStatementCollection = new ColumnStatementCollection(columnStatements) - }; - } - } -} diff --git a/SQLite.CodeFirst/Builder/NameCreators/TableNameCreator.cs b/SQLite.CodeFirst/Builder/NameCreators/TableNameCreator.cs deleted file mode 100644 index 96cab32..0000000 --- a/SQLite.CodeFirst/Builder/NameCreators/TableNameCreator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Globalization; - -namespace SQLite.CodeFirst.Builder.NameCreators -{ - internal static class TableNameCreator - { - public static string CreateTableName(string tableFromEntitySet) - { - return String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", SpecialChars.EscapeCharOpen, tableFromEntitySet, SpecialChars.EscapeCharClose); - } - } -} diff --git a/SQLite.CodeFirst/Builder/PrimaryKeyStatementBuilder.cs b/SQLite.CodeFirst/Builder/PrimaryKeyStatementBuilder.cs deleted file mode 100644 index f4f7d7c..0000000 --- a/SQLite.CodeFirst/Builder/PrimaryKeyStatementBuilder.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Data.Entity.Core.Metadata.Edm; -using System.Linq; -using SQLite.CodeFirst.Statement; - -namespace SQLite.CodeFirst.Builder -{ - internal class PrimaryKeyStatementBuilder : IStatementBuilder - { - private readonly IEnumerable keyMembers; - - public PrimaryKeyStatementBuilder(IEnumerable keyMembers) - { - this.keyMembers = keyMembers; - } - - public PrimaryKeyStatement BuildStatement() - { - return new PrimaryKeyStatement(keyMembers.Select(km => km.Name)); - } - } -} diff --git a/SQLite.CodeFirst/Internal/AssemblyInfo.cs b/SQLite.CodeFirst/Internal/AssemblyInfo.cs new file mode 100644 index 0000000..6de9d4c --- /dev/null +++ b/SQLite.CodeFirst/Internal/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("SQLite.CodeFirst.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e35b9d3bf8a8ebcbbc7414b81cc803f1e43b24996e5af0d552c54743651fc328a50977e6e3c10b31c26165a74f3abbd9fdf2b785b8b9ba48568914aec8fea586a4ffa287c9dc5bd2b6367a3cb48e840baab417e89b27b95fa7ac5bc5b926cf9c45a76b064307845c71e9161bdb6eb7f96a6bf24ee6db96f7a4b71b7408938fae", AllInternalsVisible = true)] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] \ No newline at end of file diff --git a/SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs b/SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs new file mode 100644 index 0000000..3fba1bb --- /dev/null +++ b/SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Data.Entity.Core.Metadata.Edm; +using System.Linq; +using SQLite.CodeFirst.Extensions; +using SQLite.CodeFirst.Statement; +using SQLite.CodeFirst.Statement.ColumnConstraint; + +namespace SQLite.CodeFirst.Builder +{ + internal class ColumnStatementCollectionBuilder : IStatementBuilder + { + private readonly IEnumerable properties; + private readonly IEnumerable keyMembers; + private readonly Collation defaultCollation; + + public ColumnStatementCollectionBuilder(IEnumerable properties, IEnumerable keyMembers, Collation defaultCollation) + { + this.properties = properties; + this.keyMembers = keyMembers; + this.defaultCollation = defaultCollation; + } + + public ColumnStatementCollection BuildStatement() + { + var columnDefStatement = new ColumnStatementCollection(CreateColumnStatements().ToList()); + return columnDefStatement; + } + + private IEnumerable CreateColumnStatements() + { + foreach (var property in properties) + { + var columnStatement = new ColumnStatement + { + ColumnName = property.Name, + TypeName = property.TypeName, + ColumnConstraints = new ColumnConstraintCollection() + }; + + AddMaxLengthConstraintIfNecessary(property, columnStatement); + AdjustDatatypeForAutogenerationIfNecessary(property, columnStatement); + AddNullConstraintIfNecessary(property, columnStatement); + AddUniqueConstraintIfNecessary(property, columnStatement); + AddCollationConstraintIfNecessary(property, columnStatement, defaultCollation); + AddPrimaryKeyConstraintAndAdjustTypeIfNecessary(property, columnStatement); + AddDefaultValueConstraintIfNecessary(property, columnStatement); + + yield return columnStatement; + } + } + + private static void AddMaxLengthConstraintIfNecessary(EdmProperty property, ColumnStatement columnStatement) + { + if (property.MaxLength.HasValue) + { + columnStatement.ColumnConstraints.Add(new MaxLengthConstraint(property.MaxLength.Value)); + } + } + + private static void AdjustDatatypeForAutogenerationIfNecessary(EdmProperty property, ColumnStatement columnStatement) + { + if (property.StoreGeneratedPattern == StoreGeneratedPattern.Identity) + { + // Must be INTEGER else SQLite will not generate the Ids + ConvertIntegerType(columnStatement); + } + } + + private static void AddNullConstraintIfNecessary(EdmProperty property, ColumnStatement columnStatement) + { + if (!property.Nullable && property.StoreGeneratedPattern != StoreGeneratedPattern.Identity) + { + // Only mark it as NotNull if it should not be generated. + columnStatement.ColumnConstraints.Add(new NotNullConstraint()); + } + } + + private static void AddCollationConstraintIfNecessary(EdmProperty property, ColumnStatement columnStatement, Collation defaultCollation) + { + var collateAttribute = property.GetCustomAnnotation(); + if (property.PrimitiveType.PrimitiveTypeKind == PrimitiveTypeKind.String) + { + // The column is a string type. Check if we have an explicit or default collation. + // If we have both, the explicitly chosen collation takes precedence. + var value = collateAttribute == null ? defaultCollation : collateAttribute.Collation; + if (value != null) + { + columnStatement.ColumnConstraints.Add(new CollateConstraint { CollationFunction = value.Function, CustomCollationFunction = value.CustomFunction }); + } + } + else if (collateAttribute != null) + { + // Only string columns can be explicitly decorated with CollateAttribute. + var name = $"{property.DeclaringType.Name}.{property.Name}"; + var errorMessage = $"CollateAttribute cannot be used on non-string property: {name} (underlying type is {property.PrimitiveType.PrimitiveTypeKind})"; + throw new InvalidOperationException(errorMessage); + } + } + + private static void AddUniqueConstraintIfNecessary(EdmProperty property, ColumnStatement columnStatement) + { + var value = property.GetCustomAnnotation(); + if (value != null) + { + columnStatement.ColumnConstraints.Add(new UniqueConstraint { OnConflict = value.OnConflict }); + } + } + + private static void AddDefaultValueConstraintIfNecessary(EdmProperty property, ColumnStatement columnStatement) + { + var value = property.GetCustomAnnotation(); + if (value != null) + { + columnStatement.ColumnConstraints.Add(new DefaultValueConstraint { DefaultValue = value.DefaultValue }); + } + } + + private void AddPrimaryKeyConstraintAndAdjustTypeIfNecessary(EdmProperty property, ColumnStatement columnStatement) + { + // Only handle a single primary key this way. + if (keyMembers.Count() != 1 || !property.Equals(keyMembers.Single())) + { + return; + } + + ConvertIntegerType(columnStatement); + var primaryKeyConstraint = new PrimaryKeyConstraint(); + primaryKeyConstraint.Autoincrement = property.GetCustomAnnotation() != null; + columnStatement.ColumnConstraints.Add(primaryKeyConstraint); + } + + private static void ConvertIntegerType(ColumnStatement columnStatement) + { + const string integerType = "INTEGER"; + columnStatement.TypeName = columnStatement.TypeName.ToUpperInvariant() == "INT" ? integerType : columnStatement.TypeName; + } + } +} diff --git a/SQLite.CodeFirst/Internal/Builder/CompositePrimaryKeyStatementBuilder.cs b/SQLite.CodeFirst/Internal/Builder/CompositePrimaryKeyStatementBuilder.cs new file mode 100644 index 0000000..5550b05 --- /dev/null +++ b/SQLite.CodeFirst/Internal/Builder/CompositePrimaryKeyStatementBuilder.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Data.Entity.Core.Metadata.Edm; +using System.Linq; +using SQLite.CodeFirst.Statement; + +namespace SQLite.CodeFirst.Builder +{ + internal class CompositePrimaryKeyStatementBuilder : IStatementBuilder + { + private readonly IEnumerable keyMembers; + + public CompositePrimaryKeyStatementBuilder(IEnumerable keyMembers) + { + this.keyMembers = keyMembers; + } + + public CompositePrimaryKeyStatement BuildStatement() + { + return new CompositePrimaryKeyStatement(keyMembers.Select(km => km.Name)); + } + } +} diff --git a/SQLite.CodeFirst/Builder/CreateDatabaseStatementBuilder.cs b/SQLite.CodeFirst/Internal/Builder/CreateDatabaseStatementBuilder.cs similarity index 55% rename from SQLite.CodeFirst/Builder/CreateDatabaseStatementBuilder.cs rename to SQLite.CodeFirst/Internal/Builder/CreateDatabaseStatementBuilder.cs index 2299e0a..ea87cca 100644 --- a/SQLite.CodeFirst/Builder/CreateDatabaseStatementBuilder.cs +++ b/SQLite.CodeFirst/Internal/Builder/CreateDatabaseStatementBuilder.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Data.Entity.Core.Metadata.Edm; using System.Linq; -using SQLite.CodeFirst.Builder.NameCreators; using SQLite.CodeFirst.Statement; using SQLite.CodeFirst.Utility; @@ -10,10 +9,12 @@ namespace SQLite.CodeFirst.Builder internal class CreateDatabaseStatementBuilder : IStatementBuilder { private readonly EdmModel edmModel; + private readonly Collation defaultCollation; - public CreateDatabaseStatementBuilder(EdmModel edmModel) + public CreateDatabaseStatementBuilder(EdmModel edmModel, Collation defaultCollation) { this.edmModel = edmModel; + this.defaultCollation = defaultCollation; } public CreateDatabaseStatement BuildStatement() @@ -27,29 +28,11 @@ public CreateDatabaseStatement BuildStatement() private IEnumerable GetCreateTableStatements() { + var associationTypeContainer = new AssociationTypeContainer(edmModel.AssociationTypes, edmModel.Container); + foreach (var entitySet in edmModel.Container.EntitySets) { - ICollection associationTypes = - edmModel.AssociationTypes.Where(a => a.Constraint.ToRole.Name == entitySet.Name).ToList(); - - IList associationTypeWrappers = new List(); - foreach (var associationType in associationTypes) - { - string fromTable = edmModel.Container.GetEntitySetByName(associationType.Constraint.FromRole.Name, true).Table; - string toTable = edmModel.Container.GetEntitySetByName(associationType.Constraint.ToRole.Name, true).Table; - - string fromTableName = TableNameCreator.CreateTableName(fromTable); - string toTableName = TableNameCreator.CreateTableName(toTable); - - associationTypeWrappers.Add(new AssociationTypeWrapper - { - AssociationType = associationType, - FromTableName = fromTableName, - ToTableName = toTableName - }); - } - - var tableStatementBuilder = new CreateTableStatementBuilder(entitySet, associationTypeWrappers); + var tableStatementBuilder = new CreateTableStatementBuilder(entitySet, associationTypeContainer, defaultCollation); yield return tableStatementBuilder.BuildStatement(); } } diff --git a/SQLite.CodeFirst/Builder/CreateIndexStatementBuilder.cs b/SQLite.CodeFirst/Internal/Builder/CreateIndexStatementBuilder.cs similarity index 87% rename from SQLite.CodeFirst/Builder/CreateIndexStatementBuilder.cs rename to SQLite.CodeFirst/Internal/Builder/CreateIndexStatementBuilder.cs index b29a4b8..aaef01c 100644 --- a/SQLite.CodeFirst/Builder/CreateIndexStatementBuilder.cs +++ b/SQLite.CodeFirst/Internal/Builder/CreateIndexStatementBuilder.cs @@ -1,10 +1,8 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.Core.Metadata.Edm; using System.Data.Entity.Infrastructure.Annotations; -using System.Globalization; using System.Linq; using SQLite.CodeFirst.Builder.NameCreators; using SQLite.CodeFirst.Extensions; @@ -31,6 +29,7 @@ public CreateIndexStatementCollection BuildStatement() .Select(x => x.Value) .OfType(); + string tableName = NameCreator.EscapeName(entitySet.Table); foreach (var index in indexAnnotations.SelectMany(ia => ia.Indexes)) { CreateIndexStatement createIndexStatement; @@ -41,7 +40,7 @@ public CreateIndexStatementCollection BuildStatement() { IsUnique = index.IsUnique, Name = indexName, - Table = TableNameCreator.CreateTableName(entitySet.Table), + Table = tableName, Columns = new Collection() }; createIndexStatments.Add(indexName, createIndexStatement); @@ -60,7 +59,7 @@ public CreateIndexStatementCollection BuildStatement() private string GetIndexName(IndexAttribute index, EdmProperty property) { - return index.Name ?? IndexNameCreator.CreateIndexName(entitySet.ElementType.GetTableName(), property.Name); + return index.Name == null ? IndexNameCreator.CreateName(entitySet.ElementType.GetTableName(), property.Name) : NameCreator.EscapeName(index.Name); } } } diff --git a/SQLite.CodeFirst/Internal/Builder/CreateTableStatementBuilder.cs b/SQLite.CodeFirst/Internal/Builder/CreateTableStatementBuilder.cs new file mode 100644 index 0000000..ac3198b --- /dev/null +++ b/SQLite.CodeFirst/Internal/Builder/CreateTableStatementBuilder.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Data.Entity.Core.Metadata.Edm; +using SQLite.CodeFirst.Builder.NameCreators; +using SQLite.CodeFirst.Statement; +using SQLite.CodeFirst.Utility; +using SQLite.CodeFirst.Extensions; +using System.Linq; + +namespace SQLite.CodeFirst.Builder +{ + internal class CreateTableStatementBuilder : IStatementBuilder + { + private readonly EntitySet entitySet; + private readonly AssociationTypeContainer associationTypeContainer; + private readonly Collation defaultCollation; + + public CreateTableStatementBuilder(EntitySet entitySet, AssociationTypeContainer associationTypeContainer, Collation defaultCollation) + { + this.entitySet = entitySet; + this.associationTypeContainer = associationTypeContainer; + this.defaultCollation = defaultCollation; + } + + public CreateTableStatement BuildStatement() + { + var keyMembers = entitySet.ElementType.KeyMembers.Cast().ToArray(); + + // Only create a CompositePrimaryKeyStatement if there is a composite primary key. + // If there is just one key member this is handled using a constraint. + CompositePrimaryKeyStatement compositePrimaryKeyStatement = null; + if (keyMembers.Length > 1) + { + compositePrimaryKeyStatement = new CompositePrimaryKeyStatementBuilder(keyMembers).BuildStatement(); + } + + var simpleColumnCollection = new ColumnStatementCollectionBuilder(entitySet.ElementType.Properties, keyMembers, defaultCollation).BuildStatement(); + var foreignKeyCollection = new ForeignKeyStatementBuilder(associationTypeContainer.GetAssociationTypes(entitySet.Name)).BuildStatement(); + + var columnStatements = new List(); + columnStatements.AddRange(simpleColumnCollection); + columnStatements.AddIfNotNull(compositePrimaryKeyStatement); + columnStatements.AddRange(foreignKeyCollection); + + return new CreateTableStatement + { + TableName = NameCreator.EscapeName(entitySet.Table), + ColumnStatementCollection = new ColumnStatementCollection(columnStatements) + }; + } + } +} diff --git a/SQLite.CodeFirst/Builder/ForeignKeyStatementBuilder.cs b/SQLite.CodeFirst/Internal/Builder/ForeignKeyStatementBuilder.cs similarity index 61% rename from SQLite.CodeFirst/Builder/ForeignKeyStatementBuilder.cs rename to SQLite.CodeFirst/Internal/Builder/ForeignKeyStatementBuilder.cs index 5e88a57..e5fd81a 100644 --- a/SQLite.CodeFirst/Builder/ForeignKeyStatementBuilder.cs +++ b/SQLite.CodeFirst/Internal/Builder/ForeignKeyStatementBuilder.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Data.Entity.Core.Metadata.Edm; using System.Linq; using SQLite.CodeFirst.Statement; using SQLite.CodeFirst.Utility; @@ -8,9 +7,9 @@ namespace SQLite.CodeFirst.Builder { internal class ForeignKeyStatementBuilder : IStatementBuilder { - private readonly IEnumerable associationTypes; + private readonly IEnumerable associationTypes; - public ForeignKeyStatementBuilder(IEnumerable associationTypes) + public ForeignKeyStatementBuilder(IEnumerable associationTypes) { this.associationTypes = associationTypes; } @@ -27,10 +26,10 @@ private IEnumerable GetForeignKeyStatements() { yield return new ForeignKeyStatement { - ForeignKey = associationType.AssociationType.Constraint.ToProperties.Select(x => x.Name), + ForeignKey = associationType.ForeignKey, ForeignTable = associationType.FromTableName, - ForeignPrimaryKey = associationType.AssociationType.Constraint.FromProperties.Select(x => x.Name), - CascadeDelete = associationType.AssociationType.Constraint.FromRole.DeleteBehavior == OperationAction.Cascade + ForeignPrimaryKey = associationType.ForeignPrimaryKey, + CascadeDelete = associationType.CascadeDelete }; } } diff --git a/SQLite.CodeFirst/Builder/IStatementBuilder.cs b/SQLite.CodeFirst/Internal/Builder/IStatementBuilder.cs similarity index 74% rename from SQLite.CodeFirst/Builder/IStatementBuilder.cs rename to SQLite.CodeFirst/Internal/Builder/IStatementBuilder.cs index 8d52cb9..8c37eda 100644 --- a/SQLite.CodeFirst/Builder/IStatementBuilder.cs +++ b/SQLite.CodeFirst/Internal/Builder/IStatementBuilder.cs @@ -2,7 +2,7 @@ namespace SQLite.CodeFirst.Builder { - public interface IStatementBuilder + internal interface IStatementBuilder where TStatement : IStatement { TStatement BuildStatement(); diff --git a/SQLite.CodeFirst/Internal/Builder/NameCreators/ColumnNameCreator.cs b/SQLite.CodeFirst/Internal/Builder/NameCreators/ColumnNameCreator.cs new file mode 100644 index 0000000..c533dfd --- /dev/null +++ b/SQLite.CodeFirst/Internal/Builder/NameCreators/ColumnNameCreator.cs @@ -0,0 +1,12 @@ +using System.Globalization; + +namespace SQLite.CodeFirst.Builder.NameCreators +{ + internal static class ColumnNameCreator + { + public static string EscapeName(string columnName) + { + return string.Format(CultureInfo.InvariantCulture, "[{0}]", columnName); + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Builder/NameCreators/IndexNameCreator.cs b/SQLite.CodeFirst/Internal/Builder/NameCreators/IndexNameCreator.cs similarity index 89% rename from SQLite.CodeFirst/Builder/NameCreators/IndexNameCreator.cs rename to SQLite.CodeFirst/Internal/Builder/NameCreators/IndexNameCreator.cs index 1098eb1..776de8b 100644 --- a/SQLite.CodeFirst/Builder/NameCreators/IndexNameCreator.cs +++ b/SQLite.CodeFirst/Internal/Builder/NameCreators/IndexNameCreator.cs @@ -7,7 +7,7 @@ internal static class IndexNameCreator { public const string IndexNamePrefix = "IX_"; - public static string CreateIndexName(string tableName, string propertyName) + public static string CreateName(string tableName, string propertyName) { // If the tableName is escaped it means that this name contains special chars e.g. a dot (base.myTable) // Because the tablename is used to build the index name the index name must also be escaped. @@ -15,4 +15,4 @@ public static string CreateIndexName(string tableName, string propertyName) return String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}_{3}{4}", SpecialChars.EscapeCharOpen, IndexNamePrefix, tableName, propertyName, SpecialChars.EscapeCharClose); } } -} +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Internal/Builder/NameCreators/NameCreator.cs b/SQLite.CodeFirst/Internal/Builder/NameCreators/NameCreator.cs new file mode 100644 index 0000000..56a8405 --- /dev/null +++ b/SQLite.CodeFirst/Internal/Builder/NameCreators/NameCreator.cs @@ -0,0 +1,19 @@ +using System.Globalization; + +namespace SQLite.CodeFirst.Builder.NameCreators +{ + internal static class NameCreator + { + public static string EscapeName(string name) + { + // Ensure that the name is not already escaped. + name = name.Trim(SpecialChars.EscapeCharOpen, SpecialChars.EscapeCharClose); + + // Escape the escape chars, if there are some of them in the name. + name = name.Replace(SpecialChars.EscapeCharOpen.ToString(), SpecialChars.EscapeCharOpen.ToString() + SpecialChars.EscapeCharOpen); + + // Escape the name. + return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", SpecialChars.EscapeCharOpen, name, SpecialChars.EscapeCharClose); + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Builder/NameCreators/SpecialChars.cs b/SQLite.CodeFirst/Internal/Builder/NameCreators/SpecialChars.cs similarity index 100% rename from SQLite.CodeFirst/Builder/NameCreators/SpecialChars.cs rename to SQLite.CodeFirst/Internal/Builder/NameCreators/SpecialChars.cs diff --git a/SQLite.CodeFirst/Convention/SqliteForeignKeyIndexConvention.cs b/SQLite.CodeFirst/Internal/Convention/SqliteForeignKeyIndexConvention.cs similarity index 87% rename from SQLite.CodeFirst/Convention/SqliteForeignKeyIndexConvention.cs rename to SQLite.CodeFirst/Internal/Convention/SqliteForeignKeyIndexConvention.cs index 6b6038a..e10205e 100644 --- a/SQLite.CodeFirst/Convention/SqliteForeignKeyIndexConvention.cs +++ b/SQLite.CodeFirst/Internal/Convention/SqliteForeignKeyIndexConvention.cs @@ -6,6 +6,7 @@ using System.Data.Entity.Infrastructure; using System.Data.Entity.Infrastructure.Annotations; using System.Data.Entity.ModelConfiguration.Conventions; +using System.Globalization; using System.Linq; using SQLite.CodeFirst.Builder.NameCreators; @@ -17,7 +18,7 @@ namespace SQLite.CodeFirst.Convention /// This is necessary because in SQLite an index-name must be unique. /// Must be added right after the -Convention. /// - public class SqliteForeignKeyIndexConvention : IStoreModelConvention + internal class SqliteForeignKeyIndexConvention : IStoreModelConvention { private const string IndexAnnotationName = "http://schemas.microsoft.com/ado/2013/11/edm/customannotation:Index"; @@ -45,7 +46,11 @@ public virtual void Apply(AssociationType item, DbModel model) } string tableName = item.Constraint.ToRole.GetEntityType().GetTableName(); - string propertyName = edmProperty.Name; + + // Special treatment for composite primary keys + string propertyName = item.Constraint.FromProperties.Count > 1 + ? String.Join("_", item.Constraint.ToProperties) + : edmProperty.Name; // The original attribute is removed. The none-ForeignKeyIndicies will be remained and readded without any modification // and the foreignKeyIncidies will be readded with the correct name. @@ -69,14 +74,14 @@ public virtual void Apply(AssociationType item, DbModel model) private static IndexAnnotation CreateIndexAnnotation(string tableName, string propertyName, int count) { - var indexName = IndexNameCreator.CreateIndexName(tableName, propertyName); + var indexName = IndexNameCreator.CreateName(tableName, propertyName); // If there are two Indicies on the same property, the count is added. // In SQLite an Index name must be global unique. // To be honest, it should never happen. But because its possible by using the API, it should be covered. if (count > 0) { - indexName = String.Format("{0}_{1}", indexName, count); + indexName = String.Format(CultureInfo.InvariantCulture, "{0}_{1}", indexName, count); } var indexAttribute = new IndexAttribute(indexName); diff --git a/SQLite.CodeFirst/Internal/Extensions/DbModelBuilderExtensions.cs b/SQLite.CodeFirst/Internal/Extensions/DbModelBuilderExtensions.cs new file mode 100644 index 0000000..bf12c83 --- /dev/null +++ b/SQLite.CodeFirst/Internal/Extensions/DbModelBuilderExtensions.cs @@ -0,0 +1,16 @@ +using System.Data.Entity; +using System.Linq; + +namespace SQLite.CodeFirst.Extensions +{ + internal static class DbModelBuilderExtensions + { + public static void RegisterAttributeAsColumnAnnotation(this DbModelBuilder modelBuilder) + where TAttribute : class + { + modelBuilder.Properties() + .Having(x => x.GetCustomAttributes(false).OfType().FirstOrDefault()) + .Configure((config, attribute) => config.HasColumnAnnotation(typeof(TAttribute).Name, attribute)); + } + } +} diff --git a/SQLite.CodeFirst/Internal/Extensions/EdmPropertyExtensions.cs b/SQLite.CodeFirst/Internal/Extensions/EdmPropertyExtensions.cs new file mode 100644 index 0000000..1fb88a8 --- /dev/null +++ b/SQLite.CodeFirst/Internal/Extensions/EdmPropertyExtensions.cs @@ -0,0 +1,21 @@ +using System; +using System.Data.Entity.Core.Metadata.Edm; + +namespace SQLite.CodeFirst.Extensions +{ + internal static class EdmPropertyExtensions + { + public static TAttribute GetCustomAnnotation(this EdmProperty property) + where TAttribute : Attribute + { + MetadataProperty item; + bool found = property.MetadataProperties.TryGetValue("http://schemas.microsoft.com/ado/2013/11/edm/customannotation:" + typeof(TAttribute).Name, true, out item); + if (found) + { + return (TAttribute) item.Value; + } + + return null; + } + } +} diff --git a/SQLite.CodeFirst/Extensions/EntityTypeExtension.cs b/SQLite.CodeFirst/Internal/Extensions/EntityTypeExtension.cs similarity index 94% rename from SQLite.CodeFirst/Extensions/EntityTypeExtension.cs rename to SQLite.CodeFirst/Internal/Extensions/EntityTypeExtension.cs index f2726c1..95282f4 100644 --- a/SQLite.CodeFirst/Extensions/EntityTypeExtension.cs +++ b/SQLite.CodeFirst/Internal/Extensions/EntityTypeExtension.cs @@ -26,7 +26,7 @@ public static string GetTableName(this EntityType entityType) // GetValue() overload with one value was introduces in .net 4.5 so use the overload with two parameters. var name = (string)metadataPropertyValueType.GetProperty("Name").GetValue(metadataPropertyValue, null); - return TableNameCreator.CreateTableName(name); + return NameCreator.EscapeName(name); } } } diff --git a/SQLite.CodeFirst/Internal/Extensions/ListExtensions.cs b/SQLite.CodeFirst/Internal/Extensions/ListExtensions.cs new file mode 100644 index 0000000..51b5bf4 --- /dev/null +++ b/SQLite.CodeFirst/Internal/Extensions/ListExtensions.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace SQLite.CodeFirst.Extensions +{ + public static class ListExtensions + { + public static void AddIfNotNull(this ICollection list, T element) + { + if (list == null || element == null) + { + return; + } + + list.Add(element); + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/CollateConstraint.cs b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/CollateConstraint.cs new file mode 100644 index 0000000..74068be --- /dev/null +++ b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/CollateConstraint.cs @@ -0,0 +1,28 @@ +using System.Text; + +namespace SQLite.CodeFirst.Statement.ColumnConstraint +{ + internal class CollateConstraint : IColumnConstraint + { + private const string Template = "COLLATE {collation-name}"; + + public CollationFunction CollationFunction { get; set; } + + public string CustomCollationFunction { get; set; } + + public string CreateStatement() + { + if (CollationFunction == CollationFunction.None) + { + return string.Empty; + } + + var sb = new StringBuilder(Template); + + string name = CollationFunction == CollationFunction.Custom ? CustomCollationFunction : CollationFunction.ToString().ToUpperInvariant(); + sb.Replace("{collation-name}", name); + + return sb.ToString().Trim(); + } + } +} diff --git a/SQLite.CodeFirst/Statement/ColumnConstraint/ColumnConstraintCollection.cs b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/ColumnConstraintCollection.cs similarity index 100% rename from SQLite.CodeFirst/Statement/ColumnConstraint/ColumnConstraintCollection.cs rename to SQLite.CodeFirst/Internal/Statement/ColumnConstraint/ColumnConstraintCollection.cs diff --git a/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/DefaultValueConstraint.cs b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/DefaultValueConstraint.cs new file mode 100644 index 0000000..9c41c22 --- /dev/null +++ b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/DefaultValueConstraint.cs @@ -0,0 +1,20 @@ +using System.Text; + +namespace SQLite.CodeFirst.Statement.ColumnConstraint +{ + internal class DefaultValueConstraint : IColumnConstraint + { + private const string Template = "DEFAULT ({defaultValue})"; + + public string DefaultValue { get; set; } + + public string CreateStatement() + { + var sb = new StringBuilder(Template); + + sb.Replace("{defaultValue}", DefaultValue); + + return sb.ToString().Trim(); + } + } +} diff --git a/SQLite.CodeFirst/Statement/ColumnConstraint/IColumnConstraint.cs b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/IColumnConstraint.cs similarity index 100% rename from SQLite.CodeFirst/Statement/ColumnConstraint/IColumnConstraint.cs rename to SQLite.CodeFirst/Internal/Statement/ColumnConstraint/IColumnConstraint.cs diff --git a/SQLite.CodeFirst/Statement/ColumnConstraint/IColumnConstraintCollection.cs b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/IColumnConstraintCollection.cs similarity index 100% rename from SQLite.CodeFirst/Statement/ColumnConstraint/IColumnConstraintCollection.cs rename to SQLite.CodeFirst/Internal/Statement/ColumnConstraint/IColumnConstraintCollection.cs diff --git a/SQLite.CodeFirst/Statement/ColumnConstraint/MaxLengthConstraint.cs b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/MaxLengthConstraint.cs similarity index 100% rename from SQLite.CodeFirst/Statement/ColumnConstraint/MaxLengthConstraint.cs rename to SQLite.CodeFirst/Internal/Statement/ColumnConstraint/MaxLengthConstraint.cs diff --git a/SQLite.CodeFirst/Statement/ColumnConstraint/NotNullConstraint.cs b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/NotNullConstraint.cs similarity index 100% rename from SQLite.CodeFirst/Statement/ColumnConstraint/NotNullConstraint.cs rename to SQLite.CodeFirst/Internal/Statement/ColumnConstraint/NotNullConstraint.cs diff --git a/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/PrimaryKeyConstraint.cs b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/PrimaryKeyConstraint.cs new file mode 100644 index 0000000..c3c94f9 --- /dev/null +++ b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/PrimaryKeyConstraint.cs @@ -0,0 +1,20 @@ +using System.Text; + +namespace SQLite.CodeFirst.Statement.ColumnConstraint +{ + internal class PrimaryKeyConstraint : IColumnConstraint + { + private const string Template = "PRIMARY KEY {autoincrement}"; + + public bool Autoincrement { get; set; } + + public string CreateStatement() + { + var sb = new StringBuilder(Template); + + sb.Replace("{autoincrement}", Autoincrement ? "AUTOINCREMENT" : string.Empty); + + return sb.ToString().Trim(); + } + } +} \ No newline at end of file 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/Statement/ColumnStatement.cs b/SQLite.CodeFirst/Internal/Statement/ColumnStatement.cs similarity index 74% rename from SQLite.CodeFirst/Statement/ColumnStatement.cs rename to SQLite.CodeFirst/Internal/Statement/ColumnStatement.cs index bf4f91d..d55b091 100644 --- a/SQLite.CodeFirst/Statement/ColumnStatement.cs +++ b/SQLite.CodeFirst/Internal/Statement/ColumnStatement.cs @@ -1,11 +1,12 @@ using System.Text; +using SQLite.CodeFirst.Builder.NameCreators; using SQLite.CodeFirst.Statement.ColumnConstraint; namespace SQLite.CodeFirst.Statement { internal class ColumnStatement : IStatement { - private const string Template = "[{column-name}] {type-name} {column-constraint}"; + private const string Template = "{column-name} {type-name} {column-constraint}"; public string ColumnName { get; set; } public string TypeName { get; set; } @@ -15,7 +16,7 @@ public string CreateStatement() { var sb = new StringBuilder(Template); - sb.Replace("{column-name}", ColumnName); + sb.Replace("{column-name}", ColumnNameCreator.EscapeName(ColumnName)); sb.Replace("{type-name}", TypeName); sb.Replace("{column-constraint}", ColumnConstraints.CreateStatement()); diff --git a/SQLite.CodeFirst/Statement/ColumnStatementCollection.cs b/SQLite.CodeFirst/Internal/Statement/ColumnStatementCollection.cs similarity index 100% rename from SQLite.CodeFirst/Statement/ColumnStatementCollection.cs rename to SQLite.CodeFirst/Internal/Statement/ColumnStatementCollection.cs diff --git a/SQLite.CodeFirst/Statement/PrimaryKeyStatement.cs b/SQLite.CodeFirst/Internal/Statement/CompositePrimaryKeyStatement.cs similarity index 57% rename from SQLite.CodeFirst/Statement/PrimaryKeyStatement.cs rename to SQLite.CodeFirst/Internal/Statement/CompositePrimaryKeyStatement.cs index ff25587..196f305 100644 --- a/SQLite.CodeFirst/Statement/PrimaryKeyStatement.cs +++ b/SQLite.CodeFirst/Internal/Statement/CompositePrimaryKeyStatement.cs @@ -1,15 +1,16 @@ -using System; +using SQLite.CodeFirst.Builder.NameCreators; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; namespace SQLite.CodeFirst.Statement { - internal class PrimaryKeyStatement : Collection, IStatement + internal class CompositePrimaryKeyStatement : Collection, IStatement { private const string Template = "PRIMARY KEY({primary-keys})"; - private const string PrimaryKeyColumnNameSeperator = ", "; - public PrimaryKeyStatement(IEnumerable keyMembers) + public CompositePrimaryKeyStatement(IEnumerable keyMembers) { foreach (var keyMember in keyMembers) { @@ -19,7 +20,7 @@ public PrimaryKeyStatement(IEnumerable keyMembers) public string CreateStatement() { - string primaryKeys = String.Join(PrimaryKeyColumnNameSeperator, this); + string primaryKeys = String.Join(", ", this.Select(c => ColumnNameCreator.EscapeName(c))); return Template.Replace("{primary-keys}", primaryKeys); } } diff --git a/SQLite.CodeFirst/Statement/CreateDatabaseStatement.cs b/SQLite.CodeFirst/Internal/Statement/CreateDatabaseStatement.cs similarity index 100% rename from SQLite.CodeFirst/Statement/CreateDatabaseStatement.cs rename to SQLite.CodeFirst/Internal/Statement/CreateDatabaseStatement.cs diff --git a/SQLite.CodeFirst/Statement/CreateIndexStatement.cs b/SQLite.CodeFirst/Internal/Statement/CreateIndexStatement.cs similarity index 91% rename from SQLite.CodeFirst/Statement/CreateIndexStatement.cs rename to SQLite.CodeFirst/Internal/Statement/CreateIndexStatement.cs index b153ad1..8eb4e63 100644 --- a/SQLite.CodeFirst/Statement/CreateIndexStatement.cs +++ b/SQLite.CodeFirst/Internal/Statement/CreateIndexStatement.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using SQLite.CodeFirst.Builder.NameCreators; namespace SQLite.CodeFirst.Statement { @@ -23,7 +24,7 @@ public string CreateStatement() stringBuilder.Replace("{index-name}", Name); stringBuilder.Replace("{table-name}", Table); - IEnumerable orderedColumnNames = Columns.OrderBy(c => c.Order).Select(c => c.Name); + IEnumerable orderedColumnNames = Columns.OrderBy(c => c.Order).Select(c => c.Name).Select(NameCreator.EscapeName); string columnDefinition = String.Join(ColumnNameSeperator, orderedColumnNames); stringBuilder.Replace("{column-def}", columnDefinition); diff --git a/SQLite.CodeFirst/Statement/CreateIndexStatementCollection.cs b/SQLite.CodeFirst/Internal/Statement/CreateIndexStatementCollection.cs similarity index 100% rename from SQLite.CodeFirst/Statement/CreateIndexStatementCollection.cs rename to SQLite.CodeFirst/Internal/Statement/CreateIndexStatementCollection.cs diff --git a/SQLite.CodeFirst/Statement/CreateTableStatement.cs b/SQLite.CodeFirst/Internal/Statement/CreateTableStatement.cs similarity index 100% rename from SQLite.CodeFirst/Statement/CreateTableStatement.cs rename to SQLite.CodeFirst/Internal/Statement/CreateTableStatement.cs diff --git a/SQLite.CodeFirst/Statement/ForeignKeyStatement.cs b/SQLite.CodeFirst/Internal/Statement/ForeignKeyStatement.cs similarity index 82% rename from SQLite.CodeFirst/Statement/ForeignKeyStatement.cs rename to SQLite.CodeFirst/Internal/Statement/ForeignKeyStatement.cs index 19b61b4..58b4974 100644 --- a/SQLite.CodeFirst/Statement/ForeignKeyStatement.cs +++ b/SQLite.CodeFirst/Internal/Statement/ForeignKeyStatement.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using SQLite.CodeFirst.Builder.NameCreators; +using System.Collections.Generic; +using System.Linq; using System.Text; namespace SQLite.CodeFirst.Statement @@ -16,11 +18,9 @@ internal class ForeignKeyStatement : IStatement public string CreateStatement() { var sb = new StringBuilder(Template); - - sb.Replace("{foreign-key}", string.Join(", ", ForeignKey)); + sb.Replace("{foreign-key}", string.Join(", ", ForeignKey.Select(c => ColumnNameCreator.EscapeName(c)))); sb.Replace("{referenced-table}", ForeignTable); - sb.Replace("{referenced-id}", string.Join(", ", ForeignPrimaryKey)); - + sb.Replace("{referenced-id}", string.Join(", ", ForeignPrimaryKey.Select(c => ColumnNameCreator.EscapeName(c)))); if (CascadeDelete) { sb.Append(" " + CascadeDeleteStatement); diff --git a/SQLite.CodeFirst/Statement/IStatement.cs b/SQLite.CodeFirst/Internal/Statement/IStatement.cs similarity index 100% rename from SQLite.CodeFirst/Statement/IStatement.cs rename to SQLite.CodeFirst/Internal/Statement/IStatement.cs diff --git a/SQLite.CodeFirst/Statement/IStatementCollection.cs b/SQLite.CodeFirst/Internal/Statement/IStatementCollection.cs similarity index 100% rename from SQLite.CodeFirst/Statement/IStatementCollection.cs rename to SQLite.CodeFirst/Internal/Statement/IStatementCollection.cs diff --git a/SQLite.CodeFirst/Internal/Utility/AssociationTypeContainer.cs b/SQLite.CodeFirst/Internal/Utility/AssociationTypeContainer.cs new file mode 100644 index 0000000..e86d20f --- /dev/null +++ b/SQLite.CodeFirst/Internal/Utility/AssociationTypeContainer.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Data.Entity.Core.Metadata.Edm; +using System.Linq; + +namespace SQLite.CodeFirst.Utility +{ + internal class AssociationTypeContainer + { + private readonly IEnumerable sqliteAssociationTypes; + + public AssociationTypeContainer(IEnumerable associationTypes, EntityContainer container) + { + sqliteAssociationTypes = associationTypes.Select(associationType => new SqliteAssociationType(associationType, container)); + } + + public IEnumerable GetAssociationTypes(string entitySetName) + { + return sqliteAssociationTypes.Where(associationType => associationType.ToRoleEntitySetName == entitySetName); + } + } +} diff --git a/SQLite.CodeFirst/SqliteConnectionStringParser.cs b/SQLite.CodeFirst/Internal/Utility/ConnectionStringParser.cs similarity index 66% rename from SQLite.CodeFirst/SqliteConnectionStringParser.cs rename to SQLite.CodeFirst/Internal/Utility/ConnectionStringParser.cs index 8d7d12d..02c2c21 100644 --- a/SQLite.CodeFirst/SqliteConnectionStringParser.cs +++ b/SQLite.CodeFirst/Internal/Utility/ConnectionStringParser.cs @@ -1,18 +1,40 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO; -namespace SQLite.CodeFirst +namespace SQLite.CodeFirst.Utility { - internal static class SqliteConnectionStringParser + internal static class ConnectionStringParser { private const string DataDirectoryToken = "|datadirectory|"; + private const string DataSourceToken = "data source"; private const char KeyValuePairSeperator = ';'; private const char KeyValueSeperator = '='; private const int KeyPosition = 0; private const int ValuePosition = 1; - public static IDictionary ParseSqliteConnectionString(string connectionString) + public static string GetDataSource(string connectionString) + { + // If the datasource token does not exists this is a FullUri connection string. + IDictionary strings = ParseConnectionString(connectionString); + if (strings.ContainsKey(DataSourceToken)) + { + var path = ExpandDataDirectory(ParseConnectionString(connectionString)[DataSourceToken]); + return path.Trim('"'); + } + + // TODO: Implement FullUri parsing. + if (connectionString.Contains(":memory:")) + { + return ":memory:"; + } + throw new NotSupportedException("FullUri format is currently only supported for :memory:."); + } + + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "ToUppercase makes no sense.")] + private static IDictionary ParseConnectionString(string connectionString) { connectionString = connectionString.Trim(); string[] keyValuePairs = connectionString.Split(KeyValuePairSeperator); @@ -23,21 +45,13 @@ public static IDictionary ParseSqliteConnectionString(string con string[] keyValue = keyValuePair.Split(KeyValueSeperator); if (keyValue.Length >= 2) { - keyValuePairDictionary.Add(keyValue[KeyPosition].ToLower(CultureInfo.InvariantCulture), keyValue[ValuePosition]); + keyValuePairDictionary.Add(keyValue[KeyPosition].Trim().ToLower(CultureInfo.InvariantCulture), keyValue[ValuePosition]); } } return keyValuePairDictionary; } - public static string GetDataSource(string connectionString) - { - var path = ExpandDataDirectory(ParseSqliteConnectionString(connectionString)["data source"]); - // remove quotation mark if exists - path = path.Trim('"'); - return path; - } - private static string ExpandDataDirectory(string path) { if (path == null || !path.StartsWith(DataDirectoryToken, StringComparison.OrdinalIgnoreCase)) @@ -61,14 +75,14 @@ private static string ExpandDataDirectory(string path) // We don't know if rootFolderpath ends with '\', and we don't know if the given name starts with onw int fileNamePosition = DataDirectoryToken.Length; // filename starts right after the '|datadirectory|' keyword - bool rootFolderEndsWith = (0 < rootFolderPath.Length) && rootFolderPath[rootFolderPath.Length - 1] == '\\'; - bool fileNameStartsWith = (fileNamePosition < path.Length) && path[fileNamePosition] == '\\'; + bool rootFolderEndsWith = (0 < rootFolderPath.Length) && rootFolderPath[rootFolderPath.Length - 1] == Path.DirectorySeparatorChar; + bool fileNameStartsWith = (fileNamePosition < path.Length) && path[fileNamePosition] == Path.DirectorySeparatorChar; // replace |datadirectory| with root folder path if (!rootFolderEndsWith && !fileNameStartsWith) { // need to insert '\' - fullPath = rootFolderPath + '\\' + path.Substring(fileNamePosition); + fullPath = rootFolderPath + Path.DirectorySeparatorChar + path.Substring(fileNamePosition); } else if (rootFolderEndsWith && fileNameStartsWith) { diff --git a/SQLite.CodeFirst/Internal/Utility/HashCreator.cs b/SQLite.CodeFirst/Internal/Utility/HashCreator.cs new file mode 100644 index 0000000..f8732a6 --- /dev/null +++ b/SQLite.CodeFirst/Internal/Utility/HashCreator.cs @@ -0,0 +1,20 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace SQLite.CodeFirst.Utility +{ + internal static class HashCreator + { + public static string CreateHash(string data) + { + byte[] dataBytes = Encoding.ASCII.GetBytes(data); + using (SHA512 sha512 = SHA512.Create()) + { + byte[] hashBytes = sha512.ComputeHash(dataBytes); + string hash = Convert.ToBase64String(hashBytes); + return hash; + } + } + } +} diff --git a/SQLite.CodeFirst/Internal/Utility/HistoryEntityTypeValidator.cs b/SQLite.CodeFirst/Internal/Utility/HistoryEntityTypeValidator.cs new file mode 100644 index 0000000..459e2ee --- /dev/null +++ b/SQLite.CodeFirst/Internal/Utility/HistoryEntityTypeValidator.cs @@ -0,0 +1,19 @@ +using System; + +namespace SQLite.CodeFirst.Utility +{ + internal class HistoryEntityTypeValidator + { + public static void EnsureValidType(Type historyEntityType) + { + if (!typeof(IHistory).IsAssignableFrom(historyEntityType)) + { + throw new InvalidOperationException("The Type " + historyEntityType.Name + " does not implement the IHistory interface."); + } + if (historyEntityType.GetConstructor(Type.EmptyTypes) == null) + { + throw new InvalidOperationException("The Type " + historyEntityType.Name + " does not provide an parameterless constructor."); + } + } + } +} diff --git a/SQLite.CodeFirst/Internal/Utility/InMemoryAwareFile.cs b/SQLite.CodeFirst/Internal/Utility/InMemoryAwareFile.cs new file mode 100644 index 0000000..d04c21c --- /dev/null +++ b/SQLite.CodeFirst/Internal/Utility/InMemoryAwareFile.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; + +namespace SQLite.CodeFirst.Utility +{ + internal class InMemoryAwareFile + { + public static FileAttributes? GetFileAttributes(string path) + { + if (IsInMemoryPath(path)) + { + return null; + } + return File.GetAttributes(path); + } + + public static void SetFileAttributes(string path, FileAttributes? fileAttributes) + { + if (IsInMemoryPath(path) || fileAttributes == null) + { + return; + } + File.SetAttributes(path, fileAttributes.Value); + } + + public static bool Exists(string path) + { + if (IsInMemoryPath(path)) + { + return false; + } + return File.Exists(path); + } + + public static bool Exists(string path, bool nullByteFileMeansNotExisting) + { + if (IsInMemoryPath(path)) + { + return false; + } + + var fileInfo = new FileInfo(path); + return nullByteFileMeansNotExisting ? fileInfo.Exists && fileInfo.Length != 0 : fileInfo.Exists; + } + + public static void Delete(string path) + { + if (IsInMemoryPath(path)) + { + return; + } + + File.Delete(path); + } + + public static void CreateDirectory(string path) + { + if (IsInMemoryPath(path)) + { + return; + } + + var dbFileInfo = new FileInfo(path); + if (dbFileInfo.Directory != null) + { + dbFileInfo.Directory.Create(); + } + } + + + private static bool IsInMemoryPath(string path) + { + return string.Equals(path, ":memory:", StringComparison.OrdinalIgnoreCase); + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Internal/Utility/SqliteAssociationType.cs b/SQLite.CodeFirst/Internal/Utility/SqliteAssociationType.cs new file mode 100644 index 0000000..6ab4f49 --- /dev/null +++ b/SQLite.CodeFirst/Internal/Utility/SqliteAssociationType.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Data.Entity.Core.Metadata.Edm; +using System.Linq; +using SQLite.CodeFirst.Builder.NameCreators; + +namespace SQLite.CodeFirst.Utility +{ + internal class SqliteAssociationType + { + public SqliteAssociationType(AssociationType associationType, EntityContainer container) + { + FromRoleEntitySetName = associationType.Constraint.FromRole.Name; + ToRoleEntitySetName = associationType.Constraint.ToRole.Name; + + string fromTable = container.GetEntitySetByName(FromRoleEntitySetName, true).Table; + string toTable; + + if (IsSelfReferencing(associationType)) + { + toTable = fromTable; + ToRoleEntitySetName = FromRoleEntitySetName; + } + else + { + toTable = container.GetEntitySetByName(ToRoleEntitySetName, true).Table; + } + + FromTableName = NameCreator.EscapeName(fromTable); + ToTableName = NameCreator.EscapeName(toTable); + ForeignKey = associationType.Constraint.ToProperties.Select(x => x.Name); + ForeignPrimaryKey = associationType.Constraint.FromProperties.Select(x => x.Name); + CascadeDelete = associationType.Constraint.FromRole.DeleteBehavior == OperationAction.Cascade; + } + + private static bool IsSelfReferencing(AssociationType associationType) + { + var toRoleRefType = (RefType)associationType.Constraint.ToRole.TypeUsage.EdmType; + var fromRoleRefType = (RefType)associationType.Constraint.FromRole.TypeUsage.EdmType; + bool isSelfReferencing = toRoleRefType.ElementType.Name == fromRoleRefType.ElementType.Name; + return isSelfReferencing; + } + + public string ToRoleEntitySetName { get; set; } + public string FromRoleEntitySetName { get; set; } + public IEnumerable ForeignKey { get; } + public string FromTableName { get; } + public string ToTableName { get; } + public IEnumerable ForeignPrimaryKey { get; } + public bool CascadeDelete { get; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Properties/AssemblyInfo.cs b/SQLite.CodeFirst/Properties/AssemblyInfo.cs deleted file mode 100644 index cb88143..0000000 --- a/SQLite.CodeFirst/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("SQLite.CodeFirst")] -[assembly: AssemblyDescription( - "Creates a SQLite Database from Code, using Entity Framework CodeFirst. " + - "This Project ships several IDbInitializer which creates " + - "a new SQLite Database, based on your model/code.")] -[assembly: AssemblyProduct("SQLite.CodeFirst")] -[assembly: AssemblyCopyright("Copyright © Marc Sallin")] -[assembly: AssemblyCompany("Marc Sallin")] - -[assembly: InternalsVisibleTo("SQLite.CodeFirst.Test", AllInternalsVisible = true)] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("46603902-448a-4c50-87ec-09cb792b740f")] - -// Will be replaced by the build server -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersion("1.0.0.0")] diff --git a/SQLite.CodeFirst/Public/Attributes/AutoincrementAttribute.cs b/SQLite.CodeFirst/Public/Attributes/AutoincrementAttribute.cs new file mode 100644 index 0000000..4dde7db --- /dev/null +++ b/SQLite.CodeFirst/Public/Attributes/AutoincrementAttribute.cs @@ -0,0 +1,24 @@ +using System; + +namespace SQLite.CodeFirst +{ + /// + /// Decorate an primary key with this attribute to create a "INTEGER PRIMARY KEY AUTOINCREMENT" column. + /// + /// 1. The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and disk I/O overhead and should be avoided if not + /// strictly needed. It is usually not needed. + /// 2. In SQLite, a column with type INTEGER PRIMARY KEY is an alias for the ROWID(except in WITHOUT ROWID tables) which + /// is always a 64-bit signed integer. + /// 3. On an INSERT, if the ROWID or INTEGER PRIMARY KEY column is not explicitly given a value, then it will be filled + /// automatically with an unused integer, usually one more than the largest ROWID currently in use.This is true + /// regardless of whether or not the AUTOINCREMENT keyword is used. + /// 4. If the AUTOINCREMENT keyword appears after INTEGER PRIMARY KEY, that changes the automatic ROWID assignment + /// algorithm to prevent the reuse of ROWIDs over the lifetime of the database.In other words, the purpose of + /// AUTOINCREMENT is to prevent the reuse of ROWIDs from previously deleted rows. + /// http://sqlite.org/autoinc.html [24.02.2017] + /// + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class AutoincrementAttribute : Attribute + {} +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Public/Attributes/CollateAttribute.cs b/SQLite.CodeFirst/Public/Attributes/CollateAttribute.cs new file mode 100644 index 0000000..636965a --- /dev/null +++ b/SQLite.CodeFirst/Public/Attributes/CollateAttribute.cs @@ -0,0 +1,30 @@ +using System; + +namespace SQLite.CodeFirst +{ + /// + /// When SQLite compares two strings, it uses a collating sequence or collating function (two words for the same thing) + /// to determine which string is greater or if the two strings are equal. SQLite has three built-in collating functions (see ). + /// It is possible to specify a custom collating function. Set to and specify the name using the function parameter. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class CollateAttribute : Attribute + { + public CollateAttribute() + { + Collation = new Collation(); + } + + public CollateAttribute(CollationFunction function) + { + Collation = new Collation(function); + } + + public CollateAttribute(CollationFunction function, string customFunction) + { + Collation = new Collation(function, customFunction); + } + + public Collation Collation { get; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Public/Attributes/OnConflictAction.cs b/SQLite.CodeFirst/Public/Attributes/OnConflictAction.cs new file mode 100644 index 0000000..e2df5b0 --- /dev/null +++ b/SQLite.CodeFirst/Public/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/Public/Attributes/SqlDefaultValueAttribute.cs b/SQLite.CodeFirst/Public/Attributes/SqlDefaultValueAttribute.cs new file mode 100644 index 0000000..6c45780 --- /dev/null +++ b/SQLite.CodeFirst/Public/Attributes/SqlDefaultValueAttribute.cs @@ -0,0 +1,16 @@ +using System; + +namespace SQLite.CodeFirst +{ + /// + /// Decorate an column with this attribute to create a "DEFAULT {defaultvalue}". + /// + /// https://www.sqlite.org/lang_createtable.html [05.10.2017] + /// + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class SqlDefaultValueAttribute : Attribute + { + public string DefaultValue { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Public/Attributes/UniqueAttribute.cs b/SQLite.CodeFirst/Public/Attributes/UniqueAttribute.cs new file mode 100644 index 0000000..d51593c --- /dev/null +++ b/SQLite.CodeFirst/Public/Attributes/UniqueAttribute.cs @@ -0,0 +1,23 @@ +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() + { + OnConflict = OnConflictAction.None; + } + + public UniqueAttribute(OnConflictAction onConflict) + { + OnConflict = onConflict; + } + + public OnConflictAction OnConflict { get; private set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Public/Collation.cs b/SQLite.CodeFirst/Public/Collation.cs new file mode 100644 index 0000000..0695e73 --- /dev/null +++ b/SQLite.CodeFirst/Public/Collation.cs @@ -0,0 +1,46 @@ +using System; + +namespace SQLite.CodeFirst +{ + /// + /// This class can be used to specify the default collation for the database. Explicit Collate attributes will take precendence. + /// When SQLite compares two strings, it uses a collating sequence or collating function (two words for the same thing) + /// to determine which string is greater or if the two strings are equal. SQLite has three built-in collating functions (see ). + /// Set to and specify the name using the function parameter. + /// + public class Collation + { + public Collation() + : this(CollationFunction.None) + { + } + + public Collation(CollationFunction function) + : this(function, null) + { + } + + public Collation(CollationFunction function, string customFunction) + { + if (function != CollationFunction.Custom && !string.IsNullOrEmpty(customFunction)) + { + throw new ArgumentException("If the collation is not set to CollationFunction.Custom a function must not be specified.", nameof(function)); + } + + if (function == CollationFunction.Custom && string.IsNullOrEmpty(customFunction)) + { + throw new ArgumentException("If the collation is set to CollationFunction.Custom a function must be specified.", nameof(function)); + } + + CustomFunction = customFunction; + Function = function; + } + + public CollationFunction Function { get; set; } + + /// + /// The name of the custom collating function to use (CollationFunction.Custom). + /// + public string CustomFunction { get; set; } + } +} diff --git a/SQLite.CodeFirst/Public/CollationFunction.cs b/SQLite.CodeFirst/Public/CollationFunction.cs new file mode 100644 index 0000000..6bd054d --- /dev/null +++ b/SQLite.CodeFirst/Public/CollationFunction.cs @@ -0,0 +1,33 @@ +namespace SQLite.CodeFirst +{ + /// + /// The collation function to use for this column. + /// Is used together with the , and when setting a default collation for the database. + /// + public enum CollationFunction + { + None, + + /// + /// The same as binary, except that trailing space characters are ignored. + /// + RTrim, + + /// + /// The same as binary, except the 26 upper case characters of ASCII are folded to their lower case equivalents before + /// the comparison is performed. Note that only ASCII characters are case folded. SQLite does not attempt to do full + /// UTF case folding due to the size of the tables required. + /// + NoCase, + + /// + /// Compares string data using memcmp(), regardless of text encoding. + /// + Binary, + + /// + /// An application can register additional collating functions using the sqlite3_create_collation() interface. + /// + Custom + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/SqliteCreateDatabaseIfNotExists.cs b/SQLite.CodeFirst/Public/DbInitializers/SqliteCreateDatabaseIfNotExists.cs similarity index 87% rename from SQLite.CodeFirst/SqliteCreateDatabaseIfNotExists.cs rename to SQLite.CodeFirst/Public/DbInitializers/SqliteCreateDatabaseIfNotExists.cs index 688ef17..c572be6 100644 --- a/SQLite.CodeFirst/SqliteCreateDatabaseIfNotExists.cs +++ b/SQLite.CodeFirst/Public/DbInitializers/SqliteCreateDatabaseIfNotExists.cs @@ -1,5 +1,6 @@ using System.Data.Entity; using System.IO; +using SQLite.CodeFirst.Utility; namespace SQLite.CodeFirst { @@ -43,18 +44,7 @@ public override void InitializeDatabase(TContext context) { string databaseFilePath = GetDatabasePathFromContext(context); - var fileInfo = new FileInfo(databaseFilePath); - - bool exists; - if (nullByteFileMeansNotExisting) - { - exists = fileInfo.Exists && fileInfo.Length != 0; - } - else - { - exists = fileInfo.Exists; - } - + bool exists = InMemoryAwareFile.Exists(databaseFilePath, nullByteFileMeansNotExisting); if (exists) { return; diff --git a/SQLite.CodeFirst/SqliteDropCreateDatabaseAlways.cs b/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseAlways.cs similarity index 74% rename from SQLite.CodeFirst/SqliteDropCreateDatabaseAlways.cs rename to SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseAlways.cs index 91a8c19..3154835 100644 --- a/SQLite.CodeFirst/SqliteDropCreateDatabaseAlways.cs +++ b/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseAlways.cs @@ -1,5 +1,6 @@ using System.Data.Entity; using System.IO; +using SQLite.CodeFirst.Utility; namespace SQLite.CodeFirst { @@ -30,13 +31,18 @@ public override void InitializeDatabase(TContext context) { string databseFilePath = GetDatabasePathFromContext(context); - bool dbExists = File.Exists(databseFilePath); - if (dbExists) + bool exists = InMemoryAwareFile.Exists(databseFilePath); + if (exists) { - File.Delete(databseFilePath); + FileAttributes? attributes = InMemoryAwareFile.GetFileAttributes(databseFilePath); + InMemoryAwareFile.Delete(databseFilePath); + base.InitializeDatabase(context); + InMemoryAwareFile.SetFileAttributes(databseFilePath, attributes); + } + else + { + base.InitializeDatabase(context); } - - base.InitializeDatabase(context); } } } diff --git a/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseWhenModelChanges.cs b/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseWhenModelChanges.cs new file mode 100644 index 0000000..f8029fc --- /dev/null +++ b/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseWhenModelChanges.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Data.Entity; +using System.Data.Entity.Infrastructure; +using System.IO; +using System.Linq; +using SQLite.CodeFirst.Utility; +using System.Diagnostics.CodeAnalysis; + +namespace SQLite.CodeFirst +{ + /// + /// An implementation of that will always recreate and optionally re-seed the + /// database the first time that a context is used in the app domain or if the model has changed. + /// To seed the database, create a derived class and override the Seed method. + /// + /// To detect model changes a new table (implementation of ) is added to the database. + /// There is one record in this table which holds the hash of the SQL-statement which was generated from the model + /// executed to create the database. When initializing the database the initializer checks if the hash of the SQL-statement for the + /// model is still the same as the hash in the database. If you use this initializer on a existing database, this initializer + /// will interpret this as model change because of the new table. + /// Notice that a database can be used by more than one context. Therefore the name of the context is saved as a part of the history record. + /// + /// + /// The type of the context. + public class SqliteDropCreateDatabaseWhenModelChanges : SqliteInitializerBase + where TContext : DbContext + { + private readonly Type historyEntityType; + + /// + /// Initializes a new instance of the class. + /// + /// The model builder. + public SqliteDropCreateDatabaseWhenModelChanges(DbModelBuilder modelBuilder) + : base(modelBuilder) + { + historyEntityType = typeof(History); + ConfigureHistoryEntity(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The model builder. + /// Type of the history entity (must implement and provide an parameterless constructor). + public SqliteDropCreateDatabaseWhenModelChanges(DbModelBuilder modelBuilder, Type historyEntityType) + : base(modelBuilder) + { + this.historyEntityType = historyEntityType; + ConfigureHistoryEntity(); + } + + + protected void ConfigureHistoryEntity() + { + HistoryEntityTypeValidator.EnsureValidType(historyEntityType); + ModelBuilder.RegisterEntityType(historyEntityType); + } + + /// + /// Initialize the database for the given context. + /// Generates the SQLite-DDL from the model and executes it against the database. + /// After that the method is executed. + /// All actions are be executed in transactions. + /// + /// The context. + public override void InitializeDatabase(TContext context) + { + string databaseFilePath = GetDatabasePathFromContext(context); + + bool dbExists = InMemoryAwareFile.Exists(databaseFilePath); + if (dbExists) + { + if (IsSameModel(context)) + { + return; + } + + FileAttributes? attributes = InMemoryAwareFile.GetFileAttributes(databaseFilePath); + CloseDatabase(context); + DeleteDatabase(context, databaseFilePath); + base.InitializeDatabase(context); + InMemoryAwareFile.SetFileAttributes(databaseFilePath, attributes); + SaveHistory(context); + } + else + { + base.InitializeDatabase(context); + SaveHistory(context); + } + } + + /// + /// Called to drop/remove Database file from disk. + /// + /// The context. + /// Filename of Database to be removed. + protected virtual void DeleteDatabase(TContext context, string databaseFilePath) + { + InMemoryAwareFile.Delete(databaseFilePath); + } + + [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect", Justification = "Required.")] + private static void CloseDatabase(TContext context) + { + context.Database.Connection.Close(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + private void SaveHistory(TContext context) + { + var hash = GetHashFromModel(context.Database.Connection); + var history = GetHistoryRecord(context); + EntityState entityState; + if (history == null) + { + history = (IHistory)Activator.CreateInstance(historyEntityType); + entityState = EntityState.Added; + } + else + { + entityState = EntityState.Modified; + } + + history.Context = context.GetType().FullName; + history.Hash = hash; + history.CreateDate = DateTime.UtcNow; + + context.Set(historyEntityType).Attach(history); + context.Entry(history).State = entityState; + context.SaveChanges(); + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + private bool IsSameModel(TContext context) + { + + var hash = GetHashFromModel(context.Database.Connection); + + try + { + var history = GetHistoryRecord(context); + return history?.Hash == hash; + } + catch (Exception) + { + // This happens if the history table does not exist. + // So it covers also the case with a null byte file (see SqliteCreateDatabaseIfNotExists). + return false; + } + } + + private IHistory GetHistoryRecord(TContext context) + { + // Yes, it seams to be complicated but it has to be done this way + // in order to be supported by .NET 4.0. + DbQuery dbQuery = context.Set(historyEntityType).AsNoTracking(); + IEnumerable records = Enumerable.Cast(dbQuery); + return records.SingleOrDefault(); + } + + private string GetHashFromModel(DbConnection connection) + { + var sql = GetSqlFromModel(connection); + string hash = HashCreator.CreateHash(sql); + return hash; + } + + private string GetSqlFromModel(DbConnection connection) + { + var model = ModelBuilder.Build(connection); + var sqliteSqlGenerator = new SqliteSqlGenerator(DefaultCollation); + return sqliteSqlGenerator.Generate(model.StoreModel); + } + } +} diff --git a/SQLite.CodeFirst/Public/DbInitializers/SqliteInitializerBase.cs b/SQLite.CodeFirst/Public/DbInitializers/SqliteInitializerBase.cs new file mode 100644 index 0000000..1309b1f --- /dev/null +++ b/SQLite.CodeFirst/Public/DbInitializers/SqliteInitializerBase.cs @@ -0,0 +1,103 @@ +using System; +using System.Data.Entity; +using System.Data.Entity.Infrastructure; +using System.Data.Entity.ModelConfiguration.Conventions; +using System.IO; +using SQLite.CodeFirst.Convention; +using SQLite.CodeFirst.Extensions; +using SQLite.CodeFirst.Utility; + +namespace SQLite.CodeFirst +{ + /// + /// An basic implementation of the interface. + /// This class provides common logic which can be used when writing an Sqlite-Initializer. + /// The logic provided is: + /// 1. Remove/Add specific conventions + /// 2. Get the path to the database file + /// 3. Create a new SQLite-database from the model (Code First) + /// 4. Seed data to the new created database + /// The following implementations are provided: , + /// . + /// + /// The type of the context. + public abstract class SqliteInitializerBase : IDatabaseInitializer + where TContext : DbContext + { + protected SqliteInitializerBase(DbModelBuilder modelBuilder, Collation defaultCollation = null) + { + ModelBuilder = modelBuilder ?? throw new ArgumentNullException(nameof(modelBuilder)); + DefaultCollation = defaultCollation; + + // This convention will crash the SQLite Provider before "InitializeDatabase" gets called. + // See https://github.com/msallin/SQLiteCodeFirst/issues/7 for details. + modelBuilder.Conventions.Remove(); + + // There is some functionality which is supported by SQLite which can not be covered + // by using the data annotation attributes from the .net framework. + // So there are some custom attributes. + modelBuilder.RegisterAttributeAsColumnAnnotation(); + modelBuilder.RegisterAttributeAsColumnAnnotation(); + modelBuilder.RegisterAttributeAsColumnAnnotation(); + modelBuilder.RegisterAttributeAsColumnAnnotation(); + + // 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. + try + { + // Place the own ForeinKeyIndexConvention right after the original. + // The own convention will rename the automatically created indicies by using the correct scheme. + modelBuilder.Conventions.AddAfter(new SqliteForeignKeyIndexConvention()); + } + catch (InvalidOperationException) + { + // Ignore it. + } + } + + public Collation DefaultCollation { get; } + + protected DbModelBuilder ModelBuilder { get; } + + /// + /// Initialize the database for the given context. + /// Generates the SQLite-DDL from the model and executs it against the database. + /// After that the method is executed. + /// All actions are be executed in transactions. + /// + /// The context. + public virtual void InitializeDatabase(TContext context) + { + DbModel model = ModelBuilder.Build(context.Database.Connection); + + string dbFile = GetDatabasePathFromContext(context); + InMemoryAwareFile.CreateDirectory(dbFile); + + var sqliteDatabaseCreator = new SqliteDatabaseCreator(DefaultCollation); + sqliteDatabaseCreator.Create(context.Database, model); + + Seed(context); + context.SaveChanges(); + } + + /// + /// Is executed right after the initialization . + /// Use this method to seed data into the empty database. + /// + /// The context. + protected virtual void Seed(TContext context) + { + } + + /// + /// Gets the database path file path from a . + /// + /// The context to get the database file path from. + /// The full path to the SQLite database file. + protected string GetDatabasePathFromContext(TContext context) + { + return ConnectionStringParser.GetDataSource(context.Database.Connection.ConnectionString); + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Public/Entities/History.cs b/SQLite.CodeFirst/Public/Entities/History.cs new file mode 100644 index 0000000..5238aee --- /dev/null +++ b/SQLite.CodeFirst/Public/Entities/History.cs @@ -0,0 +1,56 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst +{ + /// + /// Represents the database table "history" which is used to store the necessary information + /// to detect if the model has changed. + /// + [Table(nameof(History) + "_" + TableNamePostfix)] + public class History : IHistory + { + /// + /// The postfix which is appended to the history table name + /// to ensure that this table name is unique. + /// + public const string TableNamePostfix = "82c009b41631-48579635f1ff64eb62d9"; + + /// + /// Gets or sets the identifier of the record. + /// Is automatilcally generated by the database. + /// + /// + /// The identifier. + /// + [Key] + public int Id { get; set; } + + /// + /// Gets or sets the hash which represents the current database structure/state. + /// + /// + /// The hash. + /// + [Required] + public string Hash { get; set; } + + /// + /// Gets or sets the key of the DbContext to which this record belongs. + /// + /// + /// The context key. + /// + public string Context { get; set; } + + /// + /// Gets or sets the create date of the record. + /// + /// + /// The create date. + /// + [Required] + public DateTime CreateDate { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Public/Entities/IHistory.cs b/SQLite.CodeFirst/Public/Entities/IHistory.cs new file mode 100644 index 0000000..cbaf2e3 --- /dev/null +++ b/SQLite.CodeFirst/Public/Entities/IHistory.cs @@ -0,0 +1,12 @@ +using System; + +namespace SQLite.CodeFirst +{ + public interface IHistory + { + int Id { get; set; } + string Hash { get; set; } + string Context { get; set; } + DateTime CreateDate { get; set; } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Public/IDatabaseCreator.cs b/SQLite.CodeFirst/Public/IDatabaseCreator.cs new file mode 100644 index 0000000..aef2a47 --- /dev/null +++ b/SQLite.CodeFirst/Public/IDatabaseCreator.cs @@ -0,0 +1,10 @@ +using System.Data.Entity; +using System.Data.Entity.Infrastructure; + +namespace SQLite.CodeFirst +{ + public interface IDatabaseCreator + { + void Create(Database db, DbModel model); + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Public/ISqliteSqlGenerator.cs b/SQLite.CodeFirst/Public/ISqliteSqlGenerator.cs new file mode 100644 index 0000000..c57c0d2 --- /dev/null +++ b/SQLite.CodeFirst/Public/ISqliteSqlGenerator.cs @@ -0,0 +1,9 @@ +using System.Data.Entity.Core.Metadata.Edm; + +namespace SQLite.CodeFirst +{ + public interface ISqlGenerator + { + string Generate(EdmModel storeModel); + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Public/SqliteDatabaseCreator.cs b/SQLite.CodeFirst/Public/SqliteDatabaseCreator.cs new file mode 100644 index 0000000..0626c24 --- /dev/null +++ b/SQLite.CodeFirst/Public/SqliteDatabaseCreator.cs @@ -0,0 +1,40 @@ +using System; +using System.Data.Entity; +using System.Data.Entity.Infrastructure; +using System.Diagnostics; + +namespace SQLite.CodeFirst +{ + /// + /// Creates a SQLite-Database based on a Entity Framework and . + /// This creator can be used standalone or within an initializer. + /// + /// The generated DDL-SQL will be executed together as one statement. + /// If there is a open transaction on the Database, the statement will be executed within this transaction. + /// Otherwise it will be executed within a own transaction. In anyway the atomicity is guaranteed. + /// + /// + public class SqliteDatabaseCreator : IDatabaseCreator + { + public SqliteDatabaseCreator(Collation defaultCollation = null) + { + DefaultCollation = defaultCollation; + } + + public Collation DefaultCollation { get; } + + /// + /// Creates the SQLite-Database. + /// + public void Create(Database db, DbModel model) + { + if (db == null) throw new ArgumentNullException("db"); + if (model == null) throw new ArgumentNullException("model"); + + var sqliteSqlGenerator = new SqliteSqlGenerator(DefaultCollation); + string sql = sqliteSqlGenerator.Generate(model.StoreModel); + Debug.Write(sql); + db.ExecuteSqlCommand(TransactionalBehavior.EnsureTransaction, sql); + } + } +} diff --git a/SQLite.CodeFirst/Public/SqliteSqlGenerator.cs b/SQLite.CodeFirst/Public/SqliteSqlGenerator.cs new file mode 100644 index 0000000..944115e --- /dev/null +++ b/SQLite.CodeFirst/Public/SqliteSqlGenerator.cs @@ -0,0 +1,29 @@ +using System.Data.Entity.Core.Metadata.Edm; +using SQLite.CodeFirst.Builder; +using SQLite.CodeFirst.Statement; + +namespace SQLite.CodeFirst +{ + /// + /// Generates the SQL statement to create a database, based on a . + /// + public class SqliteSqlGenerator : ISqlGenerator + { + public SqliteSqlGenerator(Collation defaultCollation = null) + { + DefaultCollation = defaultCollation; + } + + public Collation DefaultCollation { get; } + + /// + /// Generates the SQL statement, based on the . + /// + public string Generate(EdmModel storeModel) + { + IStatementBuilder statementBuilder = new CreateDatabaseStatementBuilder(storeModel, DefaultCollation); + IStatement statement = statementBuilder.BuildStatement(); + return statement.CreateStatement(); + } + } +} diff --git a/SQLite.CodeFirst/SQLite.CodeFirst.csproj b/SQLite.CodeFirst/SQLite.CodeFirst.csproj index 844d66d..954af58 100644 --- a/SQLite.CodeFirst/SQLite.CodeFirst.csproj +++ b/SQLite.CodeFirst/SQLite.CodeFirst.csproj @@ -1,126 +1,26 @@ - - - + + - Debug-45 - AnyCPU - {50A32FE4-0E13-4213-A373-72523CDF34D9} - Library - Properties - SQLite.CodeFirst - SQLite.CodeFirst - 512 - true - - - ..\ - true - - - - - AnyCPU - true - DEBUG;TRACE - full - prompt - MinimumRecommendedRules.ruleset - - - v4.0 - bin\Debug\net40 - - - bin\Debug\net45 - v4.5 - - - - - AnyCPU - TRACE - true - pdbonly - prompt - MinimumRecommendedRules.ruleset - - - bin\Release\net40 - v4.0 - - - bin\Release\net45 - v4.5 - - - - - - - - - ..\packages\EntityFramework.6.1.3\lib\net40\EntityFramework.dll - - - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + net40;net45;netstandard2.1 + Marc Sallin + + + Creates a SQLite Database from Code, using Entity Framework CodeFirst. This Project ships several IDbInitializer which creates a new SQLite Database, based on your model/code. + https://github.com/msallin/SQLiteCodeFirst + https://github.com/msallin/SQLiteCodeFirst + GitHub + true + https://github.com/msallin/SQLiteCodeFirst/blob/master/LICENSE + false + Copyright (C) Marc Sallin + SQLite EntityFramework EF CodeFirst + true + true + ..\Shared\SQLite.CodeFirst.StrongNameKey.snk + + - - + - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - \ No newline at end of file + + diff --git a/SQLite.CodeFirst/SQLite.CodeFirst.csproj.nuspec b/SQLite.CodeFirst/SQLite.CodeFirst.csproj.nuspec deleted file mode 100644 index f34f826..0000000 --- a/SQLite.CodeFirst/SQLite.CodeFirst.csproj.nuspec +++ /dev/null @@ -1,31 +0,0 @@ - - - - $id$ - $version$ - $author$ - $author$ - https://github.com/msallin/SQLiteCodeFirst/blob/master/LICENSE - https://github.com/msallin/SQLiteCodeFirst - false - $description$ - Creates a SQLite Database from Code, using Entity Framework CodeFirst. - - Copyright (C) Marc Sallin - SQLite EntityFramework EF CodeFirst - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SQLite.CodeFirst/SqliteDatabaseCreator.cs b/SQLite.CodeFirst/SqliteDatabaseCreator.cs deleted file mode 100644 index 11e5d62..0000000 --- a/SQLite.CodeFirst/SqliteDatabaseCreator.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Data.Entity; -using System.Data.Entity.Infrastructure; -using SQLite.CodeFirst.Builder; -using SQLite.CodeFirst.Statement; - -namespace SQLite.CodeFirst -{ - /// - /// Creates a SQLite-Database based on a Entity Framework and . - /// This creator can be used standalone or within an initializer. - /// - public class SqliteDatabaseCreator - { - private readonly Database db; - private readonly DbModel model; - - /// - /// Initializes a new instance of the class. - /// - /// The database. - /// The model. - public SqliteDatabaseCreator(Database db, DbModel model) - { - this.db = db; - this.model = model; - } - - /// - /// Creates the SQLite-Database. - /// - public void Create() - { - IStatementBuilder statementBuilder = new CreateDatabaseStatementBuilder(model.StoreModel); - IStatement statement = statementBuilder.BuildStatement(); - string sql = statement.CreateStatement(); - db.ExecuteSqlCommand(sql); - } - } -} diff --git a/SQLite.CodeFirst/SqliteInitializerBase.cs b/SQLite.CodeFirst/SqliteInitializerBase.cs deleted file mode 100644 index 25b571f..0000000 --- a/SQLite.CodeFirst/SqliteInitializerBase.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Data.Entity; -using System.Data.Entity.ModelConfiguration.Conventions; -using SQLite.CodeFirst.Convention; -using System.IO; - -namespace SQLite.CodeFirst -{ - /// - /// An basic implementation of the interface. - /// This class provides common logic which can be used when writing an Sqlite-Initializer. - /// The logic provided is: - /// 1. Remove/Add specific Conventions - /// 2. Get the path to the database file - /// 3. Create a new SQLite-Database from the model (Code First) - /// 4. Seed data to the new created database - /// The following implementations are provided: , . - /// - /// The type of the context. - public abstract class SqliteInitializerBase : IDatabaseInitializer - where TContext : DbContext - { - private readonly DbModelBuilder modelBuilder; - - protected SqliteInitializerBase(DbModelBuilder modelBuilder) - { - if (modelBuilder == null) - { - throw new ArgumentNullException("modelBuilder"); - } - - this.modelBuilder = modelBuilder; - - // This convention will crash the SQLite Provider before "InitializeDatabase" gets called. - // See https://github.com/msallin/SQLiteCodeFirst/issues/7 for details. - modelBuilder.Conventions.Remove(); - - // 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. - try - { - // Place the own ForeinKeyIndexConvention right after the original. - // The own convention will rename the automatically created indicies by using the correct scheme. - modelBuilder.Conventions.AddAfter(new SqliteForeignKeyIndexConvention()); - } - catch (InvalidOperationException exception) - { - // Ignore it. - } - } - - /// - /// Initialize the database for the given context. - /// Generates the SQLite-DDL from the model and executs it against the database. - /// After that the method is executed. - /// All actions are be executed in transactions. - /// - /// The context. - public virtual void InitializeDatabase(TContext context) - { - var model = modelBuilder.Build(context.Database.Connection); - - var dbFile = GetDatabasePathFromContext(context); - var dbFileInfo = new FileInfo(dbFile); - dbFileInfo.Directory.Create(); - - using (var transaction = context.Database.BeginTransaction()) - { - try - { - var sqliteDatabaseCreator = new SqliteDatabaseCreator(context.Database, model); - sqliteDatabaseCreator.Create(); - transaction.Commit(); - } - catch (Exception) - { - transaction.Rollback(); - throw; - } - } - - using (var transaction = context.Database.BeginTransaction()) - { - try - { - Seed(context); - context.SaveChanges(); - transaction.Commit(); - } - catch (Exception) - { - transaction.Rollback(); - throw; - } - } - } - - /// - /// Is executed right after the initialization . - /// Use this method to seed data into the empty database. - /// - /// The context. - protected virtual void Seed(TContext context) { } - - /// - /// Gets the database path file path from a . - /// - /// The context to get the database file path from. - /// The full path to the SQLite database file. - protected string GetDatabasePathFromContext(TContext context) - { - return SqliteConnectionStringParser.GetDataSource(context.Database.Connection.ConnectionString); - } - } -} \ No newline at end of file diff --git a/SQLite.CodeFirst/Utility/AssociationTypeWrapper.cs b/SQLite.CodeFirst/Utility/AssociationTypeWrapper.cs deleted file mode 100644 index c7c1a58..0000000 --- a/SQLite.CodeFirst/Utility/AssociationTypeWrapper.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Data.Entity.Core.Metadata.Edm; - -namespace SQLite.CodeFirst.Utility -{ - internal class AssociationTypeWrapper - { - public AssociationType AssociationType { get; set; } - public string FromTableName { get; set; } - public string ToTableName { get; set; } - } -} diff --git a/SQLite.CodeFirst/packages.config b/SQLite.CodeFirst/packages.config deleted file mode 100644 index 57e2eaa..0000000 --- a/SQLite.CodeFirst/packages.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/Shared/SQLite.CodeFirst.StrongNameKey.snk b/Shared/SQLite.CodeFirst.StrongNameKey.snk new file mode 100644 index 0000000..7a0d9a6 Binary files /dev/null and b/Shared/SQLite.CodeFirst.StrongNameKey.snk differ diff --git a/Shared/SQLite.CodeFirst.ruleset b/Shared/SQLite.CodeFirst.ruleset new file mode 100644 index 0000000..5b02797 --- /dev/null +++ b/Shared/SQLite.CodeFirst.ruleset @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ci_appveyor.yml b/ci_appveyor.yml new file mode 100644 index 0000000..36b0af4 --- /dev/null +++ b/ci_appveyor.yml @@ -0,0 +1,23 @@ +version: CI_{branch}_{build} +image: Visual Studio 2022 +skip_tags: true +configuration: +- Release +- Debug +platform: Any CPU +before_build: +- ps: dotnet --version +- ps: dotnet restore +build: + project: SQLite.CodeFirst.sln + parallel: true + verbosity: normal +artifacts: +- path: SQLite.CodeFirst\bin\Debug\**\SQLite.CodeFirst.dll + name: Debug +- path: SQLite.CodeFirst\bin\Release\**\SQLite.CodeFirst.dll + name: Release +- path: SQLite.CodeFirst\bin\Release\*.nupkg + name: NuPkgDebug +- path: SQLite.CodeFirst\bin\Debug\*.nupkg + name: NuPkgRelease \ No newline at end of file diff --git a/release_appveyor.yml b/release_appveyor.yml index c7581e6..be4baa2 100644 --- a/release_appveyor.yml +++ b/release_appveyor.yml @@ -1,34 +1,36 @@ -version: 0.9.{build}.0 +version: 1.7.0.{build} +image: Visual Studio 2022 branches: only: - master skip_tags: true platform: Any CPU -assembly_info: +configuration: Release +dotnet_csproj: patch: true - file: '**\AssemblyInfo.*' + file: '**\*.csproj' + version: '{version}' + package_version: '{version}' assembly_version: '{version}' - assembly_file_version: '{version}' - assembly_informational_version: '{version}' + file_version: '{version}' + informational_version: '{version}' +before_build: +- ps: dotnet --version +- ps: dotnet restore build: - project: BuildAllConfigurations.proj + project: SQLite.CodeFirst.sln + parallel: true verbosity: normal -after_build: -- ps: >- - cd $env:APPVEYOR_BUILD_FOLDER - - cd '.nuget' - - ./nuget.exe pack '..\SQLite.CodeFirst\SQLite.CodeFirst.csproj' -Properties -Symbols -OutputDirectory '..\SQLite.CodeFirst\bin' artifacts: - path: SQLite.CodeFirst\bin name: Assemblies -- path: SQLite.CodeFirst\bin\*.nupkg +- path: SQLite.CodeFirst\bin\Release\*.nupkg name: NuPkg deploy: +# Encrypt with https://ci.appveyor.com/tools/encrypt - provider: NuGet api_key: - secure: QmbFnerlfTAFUZpnaPgVDywMH4fF8rVakefmqvhu3qm9SpuDlLGB9S4HwtdE3Nep + secure: fdOvSLLttfWbXxkmrgNG+jfczNAqx0HOIqYeVhToHGvezVwts758wz+sbGkv2RhZ on: branch: master - provider: GitHub @@ -40,10 +42,4 @@ deploy: artifact: Assemblies,NuPkg draft: true on: - branch: master -notifications: -- provider: Webhook - url: https://zapier.com/hooks/catch/b3cbz0/ - on_build_success: true - on_build_failure: true - on_build_status_changed: false \ No newline at end of file + branch: master \ No newline at end of file