diff --git a/README.md b/README.md index fe628db..4a6caf3 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,8 @@ -# 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. - -## 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. +# SQLite CodeFirst (With Migrations) +Creates and update a [SQLite Database](https://sqlite.org/) from Code, using [Entity Framework](https://msdn.microsoft.com/en-us/data/ef.aspx) CodeFirst and [Migrations](https://msdn.microsoft.com/pt-br/library/system.data.entity.migrations(v=vs.113).aspx). +If you need a specific feature for a commercial project, I am glad to offer a paid implementation. +This project is forked from [msallin](https://github.com/msallin/SQLiteCodeFirst) Sqlite Code First Projet and [zaniants](https://github.com/zanyants/SQLiteCodeFirst) Sqlite Migrations code. ## Features This project ships several `IDbInitializer` classes. These create new SQLite Databases based on your model/code. @@ -49,12 +40,18 @@ Depending on your need, you can choose from the following initializers: - SqliteCreateDatabaseIfNotExists - SqliteDropCreateDatabaseAlways - SqliteDropCreateDatabaseWhenModelChanges +- SqliteMigrateDatabaseToLatestVersion 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<>`. +When using Migrations feature you will always use `SqliteMigrateDatabaseToLatestVersion<>`. + +**Know issue with Migrations:** +Entity Framework 6.2.0 has an initialization bug with Sqlite and some other databases. It already [was fixed](https://github.com/aspnet/EntityFramework6/issues/398) to EF 6.3. When you need to create a new migration change using command `Add-Migration` you will need to downgrade EF to version 6.1.3 until EF 6.3 is not released. + ### Initializer Sample ```csharp @@ -161,6 +158,12 @@ You will find an extensive usage of the composite pattern. 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: -## Recognition +Pay attention when running Migrations routines because Sqlite does not support some SQL commands suggested by the Entity Framework. For example, to rename a column will be suggested to run `Rename("dbo.table_name", "old_column_name", "new_column_name")`. However Sqlite does not support column rename command! + +When creating a new Migrations change may you need to inform StringConnection property to Migration, like this: +`Add-Migration MyChangeName -ConnectionString "Data Source='C:\path_to_your_database\footballDb.sqlite';" -ConnectionProviderName "System.Data.SQLite"` +## Recognition I started with the [code](https://gist.github.com/flaub/1968486e1b3f2b9fddaf) from [flaub](https://github.com/flaub). + +Migrations uses a code started by Julio C. Borges and upgraded by [digocesar](https://github.com/digocesar). diff --git a/SQLite.CodeFirst.Console/SQLite.CodeFirst.csproj b/SQLite.CodeFirst.Console/SQLite.CodeFirst.csproj new file mode 100644 index 0000000..9093b53 --- /dev/null +++ b/SQLite.CodeFirst.Console/SQLite.CodeFirst.csproj @@ -0,0 +1,26 @@ + + + + net40;net45 + 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 + + + + + + + diff --git a/SQLite.CodeFirst.Console/packages.config b/SQLite.CodeFirst.Console/packages.config new file mode 100644 index 0000000..6db661e --- /dev/null +++ b/SQLite.CodeFirst.Console/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/SQLite.CodeFirst.MigrationConsole/App.config b/SQLite.CodeFirst.MigrationConsole/App.config new file mode 100644 index 0000000..da28469 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/App.config @@ -0,0 +1,33 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SQLite.CodeFirst.MigrationConsole/Entity/Coach.cs b/SQLite.CodeFirst.MigrationConsole/Entity/Coach.cs new file mode 100644 index 0000000..e4b5659 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Entity/Coach.cs @@ -0,0 +1,7 @@ +namespace SQLite.CodeFirst.MigrationConsole.Entity +{ + public class Coach : Person + { + public virtual Team Team { get; set; } + } +} diff --git a/SQLite.CodeFirst.MigrationConsole/Entity/CustomHistory.cs b/SQLite.CodeFirst.MigrationConsole/Entity/CustomHistory.cs new file mode 100644 index 0000000..a1de1f3 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Entity/CustomHistory.cs @@ -0,0 +1,12 @@ +using System; + +namespace SQLite.CodeFirst.MigrationConsole.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.MigrationConsole/Entity/Foo.cs b/SQLite.CodeFirst.MigrationConsole/Entity/Foo.cs new file mode 100644 index 0000000..f11cde8 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Entity/Foo.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.MigrationConsole.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.MigrationConsole/Entity/FooCompositeKey.cs b/SQLite.CodeFirst.MigrationConsole/Entity/FooCompositeKey.cs new file mode 100644 index 0000000..c788bd9 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Entity/FooCompositeKey.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.MigrationConsole.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.MigrationConsole/Entity/FooRelationshipA.cs b/SQLite.CodeFirst.MigrationConsole/Entity/FooRelationshipA.cs new file mode 100644 index 0000000..88a51ab --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Entity/FooRelationshipA.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace SQLite.CodeFirst.MigrationConsole.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.MigrationConsole/Entity/FooRelationshipB.cs b/SQLite.CodeFirst.MigrationConsole/Entity/FooRelationshipB.cs new file mode 100644 index 0000000..0a1b73d --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Entity/FooRelationshipB.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace SQLite.CodeFirst.MigrationConsole.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.MigrationConsole/Entity/FooSelf.cs b/SQLite.CodeFirst.MigrationConsole/Entity/FooSelf.cs new file mode 100644 index 0000000..f097f9d --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Entity/FooSelf.cs @@ -0,0 +1,13 @@ +namespace SQLite.CodeFirst.MigrationConsole.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.MigrationConsole/Entity/FooStep.cs b/SQLite.CodeFirst.MigrationConsole/Entity/FooStep.cs new file mode 100644 index 0000000..192e35f --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Entity/FooStep.cs @@ -0,0 +1,13 @@ +namespace SQLite.CodeFirst.MigrationConsole.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.MigrationConsole/Entity/IEntity.cs b/SQLite.CodeFirst.MigrationConsole/Entity/IEntity.cs new file mode 100644 index 0000000..395c495 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Entity/IEntity.cs @@ -0,0 +1,7 @@ +namespace SQLite.CodeFirst.MigrationConsole.Entity +{ + interface IEntity + { + int Id { get; set; } + } +} diff --git a/SQLite.CodeFirst.MigrationConsole/Entity/Person.cs b/SQLite.CodeFirst.MigrationConsole/Entity/Person.cs new file mode 100644 index 0000000..ae12e71 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Entity/Person.cs @@ -0,0 +1,29 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.MigrationConsole.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.MigrationConsole/Entity/Player.cs b/SQLite.CodeFirst.MigrationConsole/Entity/Player.cs new file mode 100644 index 0000000..dae9fb3 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Entity/Player.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.MigrationConsole.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.MigrationConsole/Entity/Stadion.cs b/SQLite.CodeFirst.MigrationConsole/Entity/Stadion.cs new file mode 100644 index 0000000..4fb73de --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Entity/Stadion.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.MigrationConsole.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.MigrationConsole/Entity/Team.cs b/SQLite.CodeFirst.MigrationConsole/Entity/Team.cs new file mode 100644 index 0000000..7719600 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Entity/Team.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace SQLite.CodeFirst.MigrationConsole.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.MigrationConsole/FootballDbContext.cs b/SQLite.CodeFirst.MigrationConsole/FootballDbContext.cs new file mode 100644 index 0000000..1653514 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/FootballDbContext.cs @@ -0,0 +1,39 @@ +using System.Data.Common; +using System.Data.Entity; + +namespace SQLite.CodeFirst.MigrationConsole +{ + public class FootballDbContext : DbContext + { + public FootballDbContext() + { + Configure(); + } + + 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; + + Database.Log += s => { System.Diagnostics.Debug.WriteLine(s); }; + } + + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + ModelConfiguration.Configure(modelBuilder); + Database.SetInitializer(new SqliteMigrateDatabaseToLatestVersion(modelBuilder, true)); + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.MigrationConsole/Migrations/201805181917550_InitialCreate.Designer.cs b/SQLite.CodeFirst.MigrationConsole/Migrations/201805181917550_InitialCreate.Designer.cs new file mode 100644 index 0000000..217c0a7 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Migrations/201805181917550_InitialCreate.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SQLite.CodeFirst.MigrationConsole.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class InitialCreate : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(InitialCreate)); + + string IMigrationMetadata.Id + { + get { return "201805181917550_InitialCreate"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/SQLite.CodeFirst.MigrationConsole/Migrations/201805181917550_InitialCreate.cs b/SQLite.CodeFirst.MigrationConsole/Migrations/201805181917550_InitialCreate.cs new file mode 100644 index 0000000..dcf128a --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Migrations/201805181917550_InitialCreate.cs @@ -0,0 +1,299 @@ +namespace SQLite.CodeFirst.MigrationConsole.Migrations +{ + using System; + using System.Collections.Generic; + using System.Data.Entity.Infrastructure.Annotations; + using System.Data.Entity.Migrations; + + public partial class InitialCreate : DbMigration + { + public override void Up() + { + CreateTable( + "dbo.Teams", + c => new + { + Id = c.Int(nullable: false, + annotations: new Dictionary + { + { + "Autoincrement", + new AnnotationValues(oldValue: null, newValue: "True") + }, + }), + Name = c.String(nullable: false), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.Coaches", t => t.Id) + .Index(t => t.Id) + .Index(t => t.Name, name: "IX_Team_TeamsName"); + + CreateTable( + "dbo.Coaches", + c => new + { + Id = c.Int(nullable: false, identity: true), + FirstName = c.String(maxLength: 50, + annotations: new Dictionary + { + { + "Collate", + new AnnotationValues(oldValue: null, newValue: "NoCase") + }, + }), + LastName = c.String(maxLength: 50), + Street = c.String(maxLength: 100), + City = c.String(nullable: false), + CreatedUtc = c.DateTime(nullable: false, + annotations: new Dictionary + { + { + "SqlDefaultValue", + new AnnotationValues(oldValue: null, newValue: "DATETIME('now')") + }, + }), + }) + .PrimaryKey(t => t.Id); + + CreateTable( + "dbo.TeamPlayer", + c => new + { + Id = c.Int(nullable: false, identity: true), + Number = c.Int(nullable: false), + TeamId = c.Int(nullable: false), + FirstName = c.String(maxLength: 50, + annotations: new Dictionary + { + { + "Collate", + new AnnotationValues(oldValue: null, newValue: "NoCase") + }, + }), + LastName = c.String(maxLength: 50), + Street = c.String(maxLength: 100), + City = c.String(nullable: false), + CreatedUtc = c.DateTime(nullable: false, + annotations: new Dictionary + { + { + "SqlDefaultValue", + new AnnotationValues(oldValue: null, newValue: "DATETIME('now')") + }, + }), + Mentor_Id = c.Int(), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.TeamPlayer", t => t.Mentor_Id) + .ForeignKey("dbo.Teams", t => t.TeamId, cascadeDelete: true) + .Index(t => t.Number) + .Index(t => new { t.Number, t.TeamId }, unique: true, name: "IX_TeamPlayer_NumberPerTeam") + .Index(t => t.Mentor_Id); + + CreateTable( + "dbo.Stadions", + c => new + { + Name = c.String(nullable: false, maxLength: 128), + Street = c.String(nullable: false, maxLength: 128), + City = c.String(nullable: false, maxLength: 128), + Order = c.Int(nullable: false), + Team_Id = c.Int(nullable: false), + }) + .PrimaryKey(t => new { t.Name, t.Street, t.City }) + .ForeignKey("dbo.Teams", t => t.Team_Id, cascadeDelete: true) + .Index(t => new { t.Street, t.Name }, unique: true, name: "IX_Stadion_Main") + .Index(t => t.Order, unique: true, name: "ReservedKeyWordTest") + .Index(t => t.Team_Id); + + CreateTable( + "dbo.Foos", + c => new + { + FooId = c.Int(nullable: false, identity: true), + Name = c.String(), + FooSelf1Id = c.Int(), + FooSelf2Id = c.Int(), + FooSelf3Id = c.Int(), + }) + .PrimaryKey(t => t.FooId) + .ForeignKey("dbo.Foos", t => t.FooSelf1Id) + .ForeignKey("dbo.Foos", t => t.FooSelf2Id) + .ForeignKey("dbo.Foos", t => t.FooSelf3Id) + .Index(t => t.FooSelf1Id) + .Index(t => t.FooSelf2Id) + .Index(t => t.FooSelf3Id); + + CreateTable( + "dbo.FooSelves", + c => new + { + FooSelfId = c.Int(nullable: false, identity: true), + FooId = c.Int(nullable: false), + Number = c.Int(nullable: false), + }) + .PrimaryKey(t => t.FooSelfId) + .ForeignKey("dbo.Foos", t => t.FooId, cascadeDelete: true) + .Index(t => t.FooId); + + CreateTable( + "dbo.FooSteps", + c => new + { + FooStepId = c.Int(nullable: false, identity: true), + FooId = c.Int(nullable: false), + Number = c.Int(nullable: false), + }) + .PrimaryKey(t => t.FooStepId) + .ForeignKey("dbo.Foos", t => t.FooId, cascadeDelete: true) + .Index(t => t.FooId); + + CreateTable( + "dbo.FooCompositeKeys", + c => new + { + Id = c.Int(nullable: false), + Version = c.String(nullable: false, maxLength: 20), + Name = c.String(maxLength: 255), + }) + .PrimaryKey(t => new { t.Id, t.Version }); + + CreateTable( + "dbo.FooRelationshipAs", + c => new + { + Id = c.Int(nullable: false, identity: true), + Name = c.String(maxLength: 255), + }) + .PrimaryKey(t => t.Id); + + CreateTable( + "dbo.FooRelationshipBs", + c => new + { + Id = c.Int(nullable: false, identity: true), + Name = c.String(maxLength: 255), + }) + .PrimaryKey(t => t.Id); + + CreateTable( + "dbo.FooRelationshipAFooCompositeKeys", + c => new + { + FooRelationshipA_Id = c.Int(nullable: false), + FooCompositeKey_Id = c.Int(nullable: false), + FooCompositeKey_Version = c.String(nullable: false, maxLength: 20), + }) + .PrimaryKey(t => new { t.FooRelationshipA_Id, t.FooCompositeKey_Id, t.FooCompositeKey_Version }) + .ForeignKey("dbo.FooRelationshipAs", t => t.FooRelationshipA_Id, cascadeDelete: true) + .ForeignKey("dbo.FooCompositeKeys", t => new { t.FooCompositeKey_Id, t.FooCompositeKey_Version }, cascadeDelete: true) + .Index(t => t.FooRelationshipA_Id) + .Index(t => new { t.FooCompositeKey_Id, t.FooCompositeKey_Version }); + + CreateTable( + "dbo.FooRelationshipBFooCompositeKeys", + c => new + { + FooRelationshipB_Id = c.Int(nullable: false), + FooCompositeKey_Id = c.Int(nullable: false), + FooCompositeKey_Version = c.String(nullable: false, maxLength: 20), + }) + .PrimaryKey(t => new { t.FooRelationshipB_Id, t.FooCompositeKey_Id, t.FooCompositeKey_Version }) + .ForeignKey("dbo.FooRelationshipBs", t => t.FooRelationshipB_Id, cascadeDelete: true) + .ForeignKey("dbo.FooCompositeKeys", t => new { t.FooCompositeKey_Id, t.FooCompositeKey_Version }, cascadeDelete: true) + .Index(t => t.FooRelationshipB_Id) + .Index(t => new { t.FooCompositeKey_Id, t.FooCompositeKey_Version }); + + } + + public override void Down() + { + DropForeignKey("dbo.FooRelationshipBFooCompositeKeys", new[] { "FooCompositeKey_Id", "FooCompositeKey_Version" }, "dbo.FooCompositeKeys"); + DropForeignKey("dbo.FooRelationshipBFooCompositeKeys", "FooRelationshipB_Id", "dbo.FooRelationshipBs"); + DropForeignKey("dbo.FooRelationshipAFooCompositeKeys", new[] { "FooCompositeKey_Id", "FooCompositeKey_Version" }, "dbo.FooCompositeKeys"); + DropForeignKey("dbo.FooRelationshipAFooCompositeKeys", "FooRelationshipA_Id", "dbo.FooRelationshipAs"); + DropForeignKey("dbo.Foos", "FooSelf3Id", "dbo.Foos"); + DropForeignKey("dbo.Foos", "FooSelf2Id", "dbo.Foos"); + DropForeignKey("dbo.Foos", "FooSelf1Id", "dbo.Foos"); + DropForeignKey("dbo.FooSteps", "FooId", "dbo.Foos"); + DropForeignKey("dbo.FooSelves", "FooId", "dbo.Foos"); + DropForeignKey("dbo.Stadions", "Team_Id", "dbo.Teams"); + DropForeignKey("dbo.TeamPlayer", "TeamId", "dbo.Teams"); + DropForeignKey("dbo.TeamPlayer", "Mentor_Id", "dbo.TeamPlayer"); + DropForeignKey("dbo.Teams", "Id", "dbo.Coaches"); + DropIndex("dbo.FooRelationshipBFooCompositeKeys", new[] { "FooCompositeKey_Id", "FooCompositeKey_Version" }); + DropIndex("dbo.FooRelationshipBFooCompositeKeys", new[] { "FooRelationshipB_Id" }); + DropIndex("dbo.FooRelationshipAFooCompositeKeys", new[] { "FooCompositeKey_Id", "FooCompositeKey_Version" }); + DropIndex("dbo.FooRelationshipAFooCompositeKeys", new[] { "FooRelationshipA_Id" }); + DropIndex("dbo.FooSteps", new[] { "FooId" }); + DropIndex("dbo.FooSelves", new[] { "FooId" }); + DropIndex("dbo.Foos", new[] { "FooSelf3Id" }); + DropIndex("dbo.Foos", new[] { "FooSelf2Id" }); + DropIndex("dbo.Foos", new[] { "FooSelf1Id" }); + DropIndex("dbo.Stadions", new[] { "Team_Id" }); + DropIndex("dbo.Stadions", "ReservedKeyWordTest"); + DropIndex("dbo.Stadions", "IX_Stadion_Main"); + DropIndex("dbo.TeamPlayer", new[] { "Mentor_Id" }); + DropIndex("dbo.TeamPlayer", "IX_TeamPlayer_NumberPerTeam"); + DropIndex("dbo.TeamPlayer", new[] { "Number" }); + DropIndex("dbo.Teams", "IX_Team_TeamsName"); + DropIndex("dbo.Teams", new[] { "Id" }); + DropTable("dbo.FooRelationshipBFooCompositeKeys"); + DropTable("dbo.FooRelationshipAFooCompositeKeys"); + DropTable("dbo.FooRelationshipBs"); + DropTable("dbo.FooRelationshipAs"); + DropTable("dbo.FooCompositeKeys"); + DropTable("dbo.FooSteps"); + DropTable("dbo.FooSelves"); + DropTable("dbo.Foos"); + DropTable("dbo.Stadions"); + DropTable("dbo.TeamPlayer", + removedColumnAnnotations: new Dictionary> + { + { + "CreatedUtc", + new Dictionary + { + { "SqlDefaultValue", "DATETIME('now')" }, + } + }, + { + "FirstName", + new Dictionary + { + { "Collate", "NoCase" }, + } + }, + }); + DropTable("dbo.Coaches", + removedColumnAnnotations: new Dictionary> + { + { + "CreatedUtc", + new Dictionary + { + { "SqlDefaultValue", "DATETIME('now')" }, + } + }, + { + "FirstName", + new Dictionary + { + { "Collate", "NoCase" }, + } + }, + }); + DropTable("dbo.Teams", + removedColumnAnnotations: new Dictionary> + { + { + "Id", + new Dictionary + { + { "Autoincrement", "True" }, + } + }, + }); + } + } +} diff --git a/SQLite.CodeFirst.MigrationConsole/Migrations/201805181917550_InitialCreate.resx b/SQLite.CodeFirst.MigrationConsole/Migrations/201805181917550_InitialCreate.resx new file mode 100644 index 0000000..e2eb763 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Migrations/201805181917550_InitialCreate.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/SQLite.CodeFirst.MigrationConsole/Migrations/Configuration.cs b/SQLite.CodeFirst.MigrationConsole/Migrations/Configuration.cs new file mode 100644 index 0000000..2dca50c --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Migrations/Configuration.cs @@ -0,0 +1,26 @@ +namespace SQLite.CodeFirst.MigrationConsole.Migrations +{ + using System; + using System.Data.Entity; + using System.Data.Entity.Migrations; + using System.Linq; + + internal sealed class Configuration : DbMigrationsConfiguration + { + public Configuration() + { + AutomaticMigrationsEnabled = true; + + // This command alter the class to support Migration to SQLite. + SetSqlGenerator("System.Data.SQLite", new SqliteMigrationSqlGenerator()); + } + + protected override void Seed(SQLite.CodeFirst.MigrationConsole.FootballDbContext context) + { + // This method will be called after migrating to the latest version. + + // You can use the DbSet.AddOrUpdate() helper extension method + // to avoid creating duplicate seed data. + } + } +} diff --git a/SQLite.CodeFirst.MigrationConsole/ModelConfiguration.cs b/SQLite.CodeFirst.MigrationConsole/ModelConfiguration.cs new file mode 100644 index 0000000..9338b93 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/ModelConfiguration.cs @@ -0,0 +1,66 @@ +using System.Data.Entity; +using SQLite.CodeFirst.MigrationConsole.Entity; + +namespace SQLite.CodeFirst.MigrationConsole +{ + 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.MigrationConsole/Program.cs b/SQLite.CodeFirst.MigrationConsole/Program.cs new file mode 100644 index 0000000..fc7132c --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/Program.cs @@ -0,0 +1,179 @@ +using System.Collections.Generic; +using System.Data.Entity; +using System.Data.SQLite; +using System.IO; +using System.Linq; +using SQLite.CodeFirst.MigrationConsole.Entity; + +namespace SQLite.CodeFirst.MigrationConsole +{ + 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)) + { + CreateOrUpdateDatabase(context); + DisplaySeededData(context); + } + } + } + + private static void StartDemoUseFile() + { + System.Console.WriteLine("Starting Demo Application (File)"); + System.Console.WriteLine(string.Empty); + + //var connection = new SQLiteConnection(@"data source=footballDb.sqlite;foreign keys=true", true); + + //var context = new FootballDbContext(connection, false); + + using (var context = new FootballDbContext("footballDb")) + { + CreateOrUpdateDatabase(context); + DisplaySeededData(context); + + var connectionBuilder = new SQLiteConnectionStringBuilder(context.Database.Connection.ConnectionString); + if (string.IsNullOrEmpty(Path.GetPathRoot(connectionBuilder.DataSource))) + { + connectionBuilder.DataSource = Path.Combine(Directory.GetCurrentDirectory(), connectionBuilder.DataSource); + } + + System.Console.WriteLine(); + System.Console.WriteLine("After change CodeFirst database structure you need to create a new Migration file with the following command:"); + System.Console.WriteLine("Add-Migration your_change_name -ConnectionString \"{0}\" -ConnectionProviderName \"System.Data.SQLite\"", connectionBuilder); + System.Console.WriteLine(); + System.Console.WriteLine("But may you need to downgrade Entity Framework to version 6.1.3 to resolve this problem:"); + System.Console.WriteLine("https://stackoverflow.com/questions/47329496/updating-to-ef-6-2-0-from-ef-6-1-3-causes-cannot-access-a-disposed-object-error"); + } + } + + private static void CreateOrUpdateDatabase(DbContext context) + { + var connectionInfo = new SQLiteConnectionStringBuilder(context.Database.Connection.ConnectionString); + + if (!File.Exists(connectionInfo.DataSource)) + { + System.Console.WriteLine("Creating database..."); + var databaseFolder = Path.GetDirectoryName(connectionInfo.DataSource); + if (!string.IsNullOrWhiteSpace(databaseFolder)) + Directory.CreateDirectory(databaseFolder); + } + else + System.Console.WriteLine("Updating database..."); + + context.Database.Initialize(false); + + if (context.Set().Count() != 0) + { + return; + } + + System.Console.WriteLine("Seed the database."); + + 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(); + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst.MigrationConsole/SQLite.CodeFirst.MigrationConsole.csproj b/SQLite.CodeFirst.MigrationConsole/SQLite.CodeFirst.MigrationConsole.csproj new file mode 100644 index 0000000..5d29d35 --- /dev/null +++ b/SQLite.CodeFirst.MigrationConsole/SQLite.CodeFirst.MigrationConsole.csproj @@ -0,0 +1,31 @@ + + + Exe + net48 + SQLite.CodeFirst + A console application which demonstrates how to use SQLite.CodeFirst. + 1.0.0.0 + AnyCPU + true + ..\Shared\SQLite.CodeFirst.StrongNameKey.snk + + + + + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SQLite.CodeFirst.sln b/SQLite.CodeFirst.sln index 856e8f8..2a86a39 100644 --- a/SQLite.CodeFirst.sln +++ b/SQLite.CodeFirst.sln @@ -21,30 +21,80 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1F6D4E 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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLite.CodeFirst.NetCore.Console", "SQLite.CodeFirst.NetCore.Console\SQLite.CodeFirst.NetCore.Console.csproj", "{709F46E3-D23B-4931-9985-8E09E3817CC4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite.CodeFirst.MigrationConsole", "SQLite.CodeFirst.MigrationConsole\SQLite.CodeFirst.MigrationConsole.csproj", "{7583B70D-D73A-4F90-9CA7-7D2B5CF05D36}" 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|Any CPU {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug-40|Any CPU.ActiveCfg = Debug|Any CPU + {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug-40|Any CPU.Build.0 = Debug|Any CPU + {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug-45|Any CPU.ActiveCfg = Debug|Any CPU + {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug-45|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 + {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release-40|Any CPU.ActiveCfg = Release|Any CPU + {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release-40|Any CPU.Build.0 = Release|Any CPU + {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release-45|Any CPU.ActiveCfg = Release|Any CPU + {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release-45|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}.Debug-40|Any CPU.ActiveCfg = Debug|Any CPU + {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug-40|Any CPU.Build.0 = Debug|Any CPU + {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug-45|Any CPU.ActiveCfg = Debug|Any CPU + {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug-45|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 + {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release-40|Any CPU.ActiveCfg = Release|Any CPU + {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release-40|Any CPU.Build.0 = Release|Any CPU + {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release-45|Any CPU.ActiveCfg = Release|Any CPU + {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release-45|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}.Debug-40|Any CPU.ActiveCfg = Debug|Any CPU + {709F46E3-D23B-4931-9985-8E09E3817CC4}.Debug-40|Any CPU.Build.0 = Debug|Any CPU + {709F46E3-D23B-4931-9985-8E09E3817CC4}.Debug-45|Any CPU.ActiveCfg = Debug|Any CPU + {709F46E3-D23B-4931-9985-8E09E3817CC4}.Debug-45|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 + {709F46E3-D23B-4931-9985-8E09E3817CC4}.Release-40|Any CPU.ActiveCfg = Release|Any CPU + {709F46E3-D23B-4931-9985-8E09E3817CC4}.Release-40|Any CPU.Build.0 = Release|Any CPU + {709F46E3-D23B-4931-9985-8E09E3817CC4}.Release-45|Any CPU.ActiveCfg = Release|Any CPU + {709F46E3-D23B-4931-9985-8E09E3817CC4}.Release-45|Any CPU.Build.0 = Release|Any CPU + {7583B70D-D73A-4F90-9CA7-7D2B5CF05D36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7583B70D-D73A-4F90-9CA7-7D2B5CF05D36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7583B70D-D73A-4F90-9CA7-7D2B5CF05D36}.Debug-40|Any CPU.ActiveCfg = Debug|Any CPU + {7583B70D-D73A-4F90-9CA7-7D2B5CF05D36}.Debug-40|Any CPU.Build.0 = Debug|Any CPU + {7583B70D-D73A-4F90-9CA7-7D2B5CF05D36}.Debug-45|Any CPU.ActiveCfg = Debug|Any CPU + {7583B70D-D73A-4F90-9CA7-7D2B5CF05D36}.Debug-45|Any CPU.Build.0 = Debug|Any CPU + {7583B70D-D73A-4F90-9CA7-7D2B5CF05D36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7583B70D-D73A-4F90-9CA7-7D2B5CF05D36}.Release|Any CPU.Build.0 = Release|Any CPU + {7583B70D-D73A-4F90-9CA7-7D2B5CF05D36}.Release-40|Any CPU.ActiveCfg = Release|Any CPU + {7583B70D-D73A-4F90-9CA7-7D2B5CF05D36}.Release-40|Any CPU.Build.0 = Release|Any CPU + {7583B70D-D73A-4F90-9CA7-7D2B5CF05D36}.Release-45|Any CPU.ActiveCfg = Release|Any CPU + {7583B70D-D73A-4F90-9CA7-7D2B5CF05D36}.Release-45|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SQLite.CodeFirst/Internal/Builder/MigrationBuilder.cs b/SQLite.CodeFirst/Internal/Builder/MigrationBuilder.cs new file mode 100644 index 0000000..02f140f --- /dev/null +++ b/SQLite.CodeFirst/Internal/Builder/MigrationBuilder.cs @@ -0,0 +1,567 @@ +using System; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.Data.Common; +using System.Data.Entity.Core.Common; +using System.Data.Entity.Core.Metadata.Edm; +using System.Data.Entity.Infrastructure.Annotations; +using System.Data.Entity.Migrations.Model; +using System.Data.Entity.Migrations.Sql; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Format = SQLite.CodeFirst.Utility.MigrationFormatter; + +namespace SQLite.CodeFirst.Builder +{ + internal class MigrationBuilder + { + private const string _providerInvariantName = "System.Data.SQLite"; + private const string _defaultDateTimeFormat = "yyyy-MM-dd hh:mm:ss"; + + private static readonly Regex _rxMatchParameterReference = new Regex("@p[0-9]+", + RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Multiline); + + private readonly List _migrationStatements; + private readonly DbProviderManifest _providerManifest; + + private readonly DbProviderServices _providerServices; + + public MigrationBuilder(string providerManifestToken) + { + _migrationStatements = new List(); + + var factory = DbProviderFactories.GetFactory(_providerInvariantName); + + using (var connection = factory.CreateConnection()) + { + _providerServices = DbProviderServices.GetProviderServices(connection); + _providerManifest = _providerServices.GetProviderManifest(providerManifestToken); + } + } + + public IEnumerable MigrationStatements => _migrationStatements; + + internal void Generate(IEnumerable migrationOperations) + { + foreach (dynamic dynamicOperation in migrationOperations) + { + Generate(dynamicOperation); + } + } + + private void Generate(CreateTableOperation op) + { + foreach (var column in op.Columns) + { + SetAnnotatedColumn(column, op.Name); + } + + using (var tw = Format.CreateIndentedTextWriter()) + { + GenerateCreateTableCommand(op, tw); + + AddSqlStatement(tw); + } + } + + private void Generate(AddForeignKeyOperation op) + { + if (!_migrationStatements.Any(item => item.Sql.Contains("CREATE TABLE"))) + { + return; + } + + var createScript = string.Format(CultureInfo.InvariantCulture, + "CREATE TABLE {0} (", Format.ReservedWord(Format.RemoveDbo(op.DependentTable))); + + var migrationStatement = _migrationStatements + .FirstOrDefault(item => item.Sql + .Contains(createScript)); + + if (migrationStatement == null) + { + throw new NotSupportedException("SQL command to create the dependent table not found."); + } + + using (var tw = Format.CreateIndentedTextWriter()) + { + tw.Write(migrationStatement.Sql.TrimEnd(')').TrimEnd('\r', '\n')); + + tw.WriteLine(","); + + tw.Indent++; + + var referenceList = op.DependentColumns.Select(dependentColumn => + op.PrincipalColumns[op.DependentColumns.IndexOf(dependentColumn)]).ToList(); + + var cmd = string.Format(CultureInfo.InvariantCulture, "FOREIGN KEY ({0}) REFERENCES [{1}] ({2}) {3}", + string.Join(", ", op.DependentColumns.Select(Format.ReservedWord)), + Format.ReservedWord(op.PrincipalTable), + string.Join(", ", referenceList.Select(Format.ReservedWord)), + op.CascadeDelete + ? "ON DELETE CASCADE" + : "ON DELETE NO ACTION"); + + tw.WriteLine(Format.RemoveDbo(cmd)); + + tw.Indent--; + tw.Write(")"); + + migrationStatement.Sql = tw.InnerWriter.ToString(); + } + } + + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "op")] + private static void Generate(DropForeignKeyOperation op) + { + throw new NotSupportedException(); + } + + private void Generate(CreateIndexOperation op) + { + using (var tw = Format.CreateIndentedTextWriter()) + { + var indexName = op.HasDefaultName + ? string.Format(CultureInfo.InvariantCulture, "{0}_{1}", op.Name, Format.RemoveDbo(op.Table)) + : op.Name; + + tw.Write("CREATE "); + + if (op.IsUnique) + { + tw.Write(" UNIQUE "); + } + + tw.Write("INDEX "); + tw.Write(Format.ReservedWord(indexName)); + tw.Write(" ON "); + tw.Write(Format.ReservedWord(Format.RemoveDbo(op.Table))); + tw.Write("("); + + for (var i = 0; i < op.Columns.Count; i++) + { + tw.Write(Format.ReservedWord(op.Columns[i])); + + if (i < op.Columns.Count - 1) + { + tw.WriteLine(","); + } + } + + tw.Write(")"); + + AddSqlStatement(tw); + } + } + + private void Generate(DropIndexOperation op) + { + using (var tw = Format.CreateIndentedTextWriter()) + { + var indexName = op.HasDefaultName + ? string.Format(CultureInfo.InvariantCulture, "{0}_{1}", op.Name, + Format.ReservedWord(Format.RemoveDbo(op.Table))) + : op.Name; + + tw.Write("DROP INDEX "); + tw.Write(indexName); + + AddSqlStatement(tw); + } + } + + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "op")] + private static void Generate(AddPrimaryKeyOperation op) + { + throw new NotImplementedException( + "AddPrimaryKey is non-trivial and has not been implemented. See http://sqlite.org/lang_altertable.html"); + } + + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "op")] + private static void Generate(DropPrimaryKeyOperation op) + { + throw new NotImplementedException( + "DropPrimaryKey is non-trivial and has not been implemented. See http://sqlite.org/lang_altertable.html"); + } + + private void Generate(AddColumnOperation op) + { + SetAnnotatedColumn(op.Column, op.Table); + + using (var tw = Format.CreateIndentedTextWriter()) + { + tw.Write("ALTER TABLE "); + tw.Write(Format.ReservedWord(Format.RemoveDbo(op.Table))); + tw.Write(" ADD "); + + var col = op.Column; + + Generate(col, tw, null); + + if (col.IsNullable != null + && !col.IsNullable.Value + && col.DefaultValue == null + && string.IsNullOrWhiteSpace(col.DefaultValueSql) + && !col.IsIdentity + && !col.IsTimestamp + && ( + col.StoreType == null || !col.StoreType.Equals("rowversion", StringComparison.OrdinalIgnoreCase) + && !col.StoreType.Equals("timestamp", StringComparison.OrdinalIgnoreCase))) + { + tw.Write(" DEFAULT "); + + if (col.Type == PrimitiveTypeKind.DateTime) + { + tw.Write(Generate(DateTime.Parse("1900-01-01 00:00:00", CultureInfo.InvariantCulture))); + } + else + { + tw.Write(Generate((dynamic) col.ClrDefaultValue)); + } + } + + AddSqlStatement(tw); + } + } + + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "op")] + private static void Generate(DropColumnOperation op) + { + throw new NotImplementedException( + "DropColumn is non-trivial and has not been implemented. See http://sqlite.org/lang_altertable.html"); + } + + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "op")] + private static void Generate(AlterColumnOperation op) + { + throw new NotImplementedException( + "AlterColumn is non-trivial and has not been implemented. See http://sqlite.org/lang_altertable.html"); + } + + private void Generate(DropTableOperation op) + { + using (var tw = Format.CreateIndentedTextWriter()) + { + tw.Write("DROP TABLE "); + tw.Write(Format.ReservedWord(Format.RemoveDbo(op.Name))); + + AddSqlStatement(tw); + } + } + + private void Generate(SqlOperation opeSQL) + { + var sql = Format.RemoveDbo(opeSQL.Sql); + + AddSqlStatement(sql, opeSQL.SuppressTransaction); + } + + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "op")] + private static void Generate(RenameColumnOperation op) + { + throw new NotImplementedException( + "RenameColumn is non-trivial and has not been implemented. See http://sqlite.org/lang_altertable.html"); + } + + private void Generate(RenameTableOperation op) + { + using (var tw = Format.CreateIndentedTextWriter()) + { + tw.Write("ALTER TABLE "); + tw.Write(Format.ReservedWord(Format.RemoveDbo(op.Name))); + tw.Write(" RENAME TO "); + tw.Write(Format.ReservedWord(Format.RemoveDbo(op.NewName))); + + AddSqlStatement(tw); + } + } + + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "opeMoveTable")] + private static void Generate(MoveTableOperation opeMoveTable) + { + throw new NotSupportedException(); + } + + /// + /// Generate column definition. Returns true if the column was the primary key. + /// + private bool Generate(ColumnModel column, IndentedTextWriter tw, PrimaryKeyOperation primaryKeyOp) + { + var isIdPk = false; + + tw.Write(Format.ReservedWord(column.Name)); + tw.Write(" "); + var isPrimaryKey = false; + + if (primaryKeyOp != null) + { + isPrimaryKey = primaryKeyOp.Columns.Contains(column.Name); + } + + if (isPrimaryKey) + { + if (column.Type == PrimitiveTypeKind.Int16 || + column.Type == PrimitiveTypeKind.Int32) + { + tw.Write("INTEGER"); + } + else + { + tw.Write(Format.BuildColumnType(_providerManifest, column)); + } + + if (column.IsIdentity || column.Annotations.ContainsKey("Autoincrement")) + { + tw.Write(" PRIMARY KEY"); + isIdPk = true; + + if (column.Annotations.ContainsKey("Autoincrement")) + { + tw.Write(" AUTOINCREMENT"); + } + } + } + else + { + tw.Write(Format.BuildColumnType(_providerManifest, column)); + + if (column.IsNullable != null + && !column.IsNullable.Value) + { + tw.Write(" NOT NULL"); + } + + if (column.DefaultValue != null) + { + tw.Write(" DEFAULT "); + tw.Write(Generate((dynamic) column.DefaultValue)); + } + else if (!string.IsNullOrWhiteSpace(column.DefaultValueSql)) + { + tw.Write(" DEFAULT "); + tw.Write("(" + column.DefaultValueSql + ")"); + } + + if (column.Annotations.TryGetValue("Unique", out var uniqueAnnotation)) + { + tw.Write(" UNIQUE" + Format.UniqueConflictText(uniqueAnnotation)); + } + + if (column.Annotations.TryGetValue("Collate", out var collateAnnotation)) + { + tw.Write(Format.CollateFunctionText(collateAnnotation)); + } + } + + return isIdPk; + } + + private void Generate(HistoryOperation operation) + { + foreach (var cmdTree in operation.CommandTrees) + { + // Note: For comparison, within the SqlServer-specific implementation, there's a method equivalent to + // CreateCommandDefinition that takes a boolean flag "generateParameters". This controls whether paremeters + // are generated as references (ie, "@p0" etc) or literals (ie, "'SomeLiteralString'" etc). There's no + // equivalent exposed from System.Data.SQLite. So instead we replace parameter references with the literals + // within the generated CommandText. The former approach would, of course, be preferable if it were + // available. + + var cmdDef = _providerServices.CreateCommandDefinition(_providerManifest, cmdTree); + var cmd = cmdDef.CreateCommand(); + var cmdText = cmd.CommandText; + + if (cmd.Parameters.Count > 0) + { + cmdText = _rxMatchParameterReference.Replace( + cmdText, + m => + { + var idx = int.Parse(m.Value.Substring(2), CultureInfo.InvariantCulture); + dynamic dynValue = cmd.Parameters[idx].Value; + string literal = Generate(dynValue); + return literal; + }); + } + + AddSqlStatement(cmdText); + } + } + + /// + /// Generate byte array literal. + /// + private static string Generate(byte[] v) + { + var sb = new StringBuilder(v.Length * 2 + 3); + + sb.Append("x'"); + + foreach (var b in v) + { + sb.Append(b.ToString("X2", CultureInfo.InvariantCulture)); + } + + sb.Append("'"); + + return sb.ToString(); + } + + /// + /// Generate boolean literal. + /// + private static string Generate(bool v) + { + return v ? "1" : "0"; + } + + /// + /// Generate DateTime literal. + /// + private static string Generate(DateTime v) + { + return "'" + v.ToString(_defaultDateTimeFormat, CultureInfo.InvariantCulture) + "'"; + } + + /// + /// Generate DateTimeOffSet literal. + /// + private static string Generate(DateTimeOffset v) + { + return "'" + v.ToString(_defaultDateTimeFormat, CultureInfo.InvariantCulture) + "'"; + } + + /// + /// Generate Guid literal. + /// + private static string Generate(Guid v) + { + return "'" + v + "'"; + } + + /// + /// Generate string literal. + /// + private static string Generate(string v) + { + return "'" + v.Replace("'", "''") + "'"; + } + + /// + /// Generate TimeSpan literal. + /// + private static string Generate(TimeSpan v) + { + return "'" + v + "'"; + } + + /// + /// Generate literal for other object. + /// + private static string Generate(object v) + { + return string.Format(CultureInfo.InvariantCulture, "{0}", v); + } + + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "tableName")] + private static void SetAnnotatedColumn(ColumnModel column, string tableName) + { + AnnotationValues values; + if (column.Annotations.TryGetValue("SqlDefaultValue", out values)) + { + if (values.NewValue == null) + { + column.DefaultValueSql = null; + using (var writer = Format.CreateIndentedTextWriter()) + { + // Drop Constraint + //writer.WriteLine(GetSqlDropConstraintQuery(tableName, column.Name)); + //Statement(writer); + } + } + else + { + column.DefaultValueSql = (string) values.NewValue; + } + } + } + + /// + /// Adds a new Statement to be executed against the database. + /// + /// + /// + private void AddSqlStatement(string sql, bool suppressTransaction = false) + { + _migrationStatements.Add(new MigrationStatement + { + Sql = sql, + SuppressTransaction = suppressTransaction + }); + } + + /// + /// Adds a new Statement to be executed against the database. + /// + /// The writer containing the SQL to be executed. + private void AddSqlStatement(IndentedTextWriter tw) + { + AddSqlStatement(tw.InnerWriter.ToString()); + } + + /// + /// Generate CREATE TABLE command + /// + private void GenerateCreateTableCommand(CreateTableOperation op, IndentedTextWriter tw) + { + tw.WriteLine("CREATE TABLE " + Format.ReservedWord(Format.RemoveDbo(op.Name)) + " ("); + tw.Indent++; + + var hasGeneratedIdPk = false; + + for (var i = 0; i < op.Columns.Count; i++) + { + var lcmDadosColuna = op.Columns.ToList()[i]; + hasGeneratedIdPk |= Generate(lcmDadosColuna, tw, op.PrimaryKey); + + if (i < op.Columns.Count - 1) + { + tw.WriteLine(","); + } + } + + if (op.PrimaryKey != null && !hasGeneratedIdPk) + { + tw.WriteLine(","); + tw.Write("CONSTRAINT "); + tw.Write(Format.RemoveDbo(op.PrimaryKey.Name)); + tw.Write(" PRIMARY KEY "); + tw.Write("("); + + for (var li = 0; li < op.PrimaryKey.Columns.Count; li++) + { + var lstrNomeColuna = op.PrimaryKey.Columns[li]; + + tw.Write(lstrNomeColuna); + + if (li < op.PrimaryKey.Columns.Count - 1) + { + tw.WriteLine(","); + } + } + + tw.WriteLine(")"); + } + else + { + tw.WriteLine(); + } + + tw.Indent--; + tw.Write(")"); + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/Internal/Utility/MigrationFormatter.cs b/SQLite.CodeFirst/Internal/Utility/MigrationFormatter.cs new file mode 100644 index 0000000..d2c26f5 --- /dev/null +++ b/SQLite.CodeFirst/Internal/Utility/MigrationFormatter.cs @@ -0,0 +1,303 @@ +using System; +using System.CodeDom.Compiler; +using System.Data.Entity.Core.Common; +using System.Data.Entity.Infrastructure.Annotations; +using System.Data.Entity.Migrations.Model; +using System.Globalization; +using System.IO; + +namespace SQLite.CodeFirst.Utility +{ + public static class MigrationFormatter + { + const int _defaultStringMaxLength = 255; + const int _defaultNumericPrecision = 10; + const byte _defaultTimePrecision = 7; + const byte _defaultNumericScale = 0; + + /// + /// Builds a column type + /// + /// SQL representing the data type. + public static string BuildColumnType(DbProviderManifest providerManifest, ColumnModel column) + { + if (column == null) + return string.Empty; + + return column.IsTimestamp ? "rowversion" : BuildPropertyType(providerManifest, column); + } + + /// + /// Builds a SQL property type fragment from the specified . + /// + public static string BuildPropertyType(DbProviderManifest providerManifest, PropertyModel column) + { + if (providerManifest == null || column == null) + return string.Empty; + + var originalStoreType = column.StoreType; + + if (string.IsNullOrWhiteSpace(originalStoreType)) + { + var typeUsage = providerManifest.GetStoreType(column.TypeUsage).EdmType; + originalStoreType = typeUsage.Name; + } + + var storeType = originalStoreType; + + const string maxSuffix = "(max)"; + + if (storeType.EndsWith(maxSuffix, StringComparison.Ordinal)) + storeType = storeType.Substring(0, storeType.Length - maxSuffix.Length) + maxSuffix; + + switch (originalStoreType.ToUpperInvariant()) + { + case "DECIMAL": + case "NUMERIC": + storeType += "(" + (column.Precision ?? _defaultNumericPrecision) + + ", " + (column.Scale ?? _defaultNumericScale) + ")"; + break; + case "DATETIME": + case "TIME": + storeType += "(" + (column.Precision ?? _defaultTimePrecision) + ")"; + break; + case "BLOB": + case "VARCHAR2": + case "VARCHAR": + case "CHAR": + case "NVARCHAR": + case "NVARCHAR2": + storeType += "(" + (column.MaxLength ?? _defaultStringMaxLength) + ")"; + break; + } + + return storeType; + } + + /// + /// Gets an object used to format SQL script. + /// + /// An empty text writer to use for SQL generation. + public static IndentedTextWriter CreateIndentedTextWriter() + { + var writer = new StringWriter(CultureInfo.InvariantCulture); + try + { + return new IndentedTextWriter(writer); + } + catch + { + writer.Dispose(); + throw; + } + } + + /// + /// Remove occurrences of "dbo." from the supplied string. + /// + public static string RemoveDbo(string str) + { + if (str == null) + return string.Empty; + + return str.Replace("dbo.", string.Empty); + } + + /// + /// Surround with double-quotes Sqlite reserved words. + /// + public static string ReservedWord(string word) + { + if (word == null) + return string.Empty; + + switch (word.ToUpper(CultureInfo.InvariantCulture)) + { + case "ABORT": + case "ACTION": + case "ADD": + case "AFTER": + case "ALL": + case "ALTER": + case "ANALYZE": + case "AND": + case "AS": + case "ASC": + case "ATTACH": + case "AUTOINCREMENT": + case "BEFORE": + case "BEGIN": + case "BETWEEN": + case "BY": + case "CASCADE": + case "CASE": + case "CAST": + case "CHECK": + case "COLLATE": + case "COLUMN": + case "COMMIT": + case "CONFLICT": + case "CONSTRAINT": + case "CREATE": + case "CROSS": + case "CURRENT_DATE": + case "CURRENT_TIME": + case "CURRENT_TIMESTAMP": + case "DATABASE": + case "DEFAULT": + case "DEFERRABLE": + case "DEFERRED": + case "DELETE": + case "DESC": + case "DETACH": + case "DISTINCT": + case "DROP": + case "EACH": + case "ELSE": + case "END": + case "ESCAPE": + case "EXCEPT": + case "EXCLUSIVE": + case "EXISTS": + case "EXPLAIN": + case "FAIL": + case "FOR": + case "FOREIGN": + case "FROM": + case "FULL": + case "GLOB": + case "GROUP": + case "HAVING": + case "IF": + case "IGNORE": + case "IMMEDIATE": + case "IN": + case "INDEX": + case "INDEXED": + case "INITIALLY": + case "INNER": + case "INSERT": + case "INSTEAD": + case "INTERSECT": + case "INTO": + case "IS": + case "ISNULL": + case "JOIN": + case "KEY": + case "LEFT": + case "LIKE": + case "LIMIT": + case "MATCH": + case "NATURAL": + case "NO": + case "NOT": + case "NOTNULL": + case "NULL": + case "OF": + case "OFFSET": + case "ON": + case "OR": + case "ORDER": + case "OUTER": + case "PLAN": + case "PRAGMA": + case "PRIMARY": + case "QUERY": + case "RAISE": + case "RECURSIVE": + case "REFERENCES": + case "REGEXP": + case "REINDEX": + case "RELEASE": + case "RENAME": + case "REPLACE": + case "RESTRICT": + case "RIGHT": + case "ROLLBACK": + case "ROW": + case "SAVEPOINT": + case "SELECT": + case "SET": + case "TABLE": + case "TEMP": + case "TEMPORARY": + case "THEN": + case "TO": + case "TRANSACTION": + case "TRIGGER": + case "UNION": + case "UNIQUE": + case "UPDATE": + case "USING": + case "VACUUM": + case "VALUES": + case "VIEW": + case "VIRTUAL": + case "WHEN": + case "WHERE": + case "WITH": + case "WITHOUT": + return '"' + word + '"'; + + default: + return word; + } + } + + public static string UniqueConflictText(AnnotationValues uniqueAnnotation) + { + if (uniqueAnnotation == null) + return string.Empty; + + var uniqueText = Convert.ToString(uniqueAnnotation.NewValue, CultureInfo.InvariantCulture); + OnConflictAction action; + + if (!uniqueText.StartsWith("OnConflict:", StringComparison.OrdinalIgnoreCase)) + return string.Empty; + + var actionText = uniqueText.Remove(0, "OnConflict:".Length).Trim(); + if (!Enum.TryParse(actionText, out action)) + return string.Empty; + + if (action == OnConflictAction.None) + return string.Empty; + + return " ON CONFLICT " + action.ToString().ToUpperInvariant(); + } + + public static string CollateFunctionText(AnnotationValues collateAnnotation) + { + if (collateAnnotation == null) + return string.Empty; + + var collateAttributeText = Convert.ToString(collateAnnotation.NewValue, CultureInfo.InvariantCulture); + string collateFunction; + string collateCustomFunction; + + if (collateAttributeText.IndexOf(':') > -1) + { + collateFunction = collateAttributeText.Substring(0, collateAttributeText.IndexOf(':')); + collateCustomFunction = collateAttributeText.Remove(0, collateAttributeText.IndexOf(':') + 1).Trim(); + } + else + { + collateFunction = collateAttributeText; + collateCustomFunction = string.Empty; + } + + CollationFunction colatteFunctionType; + if (!Enum.TryParse(collateFunction, out colatteFunctionType)) + return string.Empty; + + if (colatteFunctionType == CollationFunction.None) + { + return string.Empty; + } + + return colatteFunctionType == CollationFunction.Custom + ? " COLLATE " + collateCustomFunction + : " COLLATE " + colatteFunctionType.ToString().ToUpperInvariant(); + } + + } +} diff --git a/SQLite.CodeFirst/Public/DbInitializers/SqliteCreateDatabaseIfNotExists.cs b/SQLite.CodeFirst/Public/DbInitializers/SqliteCreateDatabaseIfNotExists.cs index c572be6..ca487e6 100644 --- a/SQLite.CodeFirst/Public/DbInitializers/SqliteCreateDatabaseIfNotExists.cs +++ b/SQLite.CodeFirst/Public/DbInitializers/SqliteCreateDatabaseIfNotExists.cs @@ -1,5 +1,4 @@ using System.Data.Entity; -using System.IO; using SQLite.CodeFirst.Utility; namespace SQLite.CodeFirst diff --git a/SQLite.CodeFirst/Public/DbInitializers/SqliteInitializerBase.cs b/SQLite.CodeFirst/Public/DbInitializers/SqliteInitializerBase.cs index a84cdf8..a6fe4d0 100644 --- a/SQLite.CodeFirst/Public/DbInitializers/SqliteInitializerBase.cs +++ b/SQLite.CodeFirst/Public/DbInitializers/SqliteInitializerBase.cs @@ -2,7 +2,6 @@ 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; diff --git a/SQLite.CodeFirst/Public/DbInitializers/SqliteMigrateDatabaseToLatestVersion.cs b/SQLite.CodeFirst/Public/DbInitializers/SqliteMigrateDatabaseToLatestVersion.cs new file mode 100644 index 0000000..6dcaa15 --- /dev/null +++ b/SQLite.CodeFirst/Public/DbInitializers/SqliteMigrateDatabaseToLatestVersion.cs @@ -0,0 +1,100 @@ +using System.Data.Entity; +using System.Data.Entity.Migrations; +using System.Data.Entity.ModelConfiguration.Conventions; +using System.Linq; + +namespace SQLite.CodeFirst +{ + public class SqliteMigrateDatabaseToLatestVersion : MigrateDatabaseToLatestVersion + where TContext : DbContext + where TMigrationsConfiguration : DbMigrationsConfiguration, new() + { + /// + /// Initializes a new instance of the SqliteMigrateDatabaseToLatestVersion class that will use + /// the connection information from a context constructed using the default constructor + /// or registered factory if applicable + /// + /// + /// The builder that defines the model for the context being created. + public SqliteMigrateDatabaseToLatestVersion(DbModelBuilder modelBuilder) + : base() + { + Initialize(modelBuilder); + } + + /// + /// Initializes a new instance of the SqliteMigrateDatabaseToLatestVersion class specifying whether to + /// use the connection information from the context that triggered initialization to perform the migration. + /// + /// + /// The builder that defines the model for the context being created. + /// If set to true the initializer is run using the connection information from the context that + /// triggered initialization. Otherwise, the connection information will be taken from a context constructed + /// using the default constructor or registered factory if applicable. + /// + public SqliteMigrateDatabaseToLatestVersion(DbModelBuilder modelBuilder, bool useSuppliedContext) + : base(useSuppliedContext) + { + Initialize(modelBuilder); + } + + /// + /// Initializes a new instance of the SqliteMigrateDatabaseToLatestVersion class specifying whether to + /// use the connection information from the context that triggered initialization to perform the migration. + /// Also allows specifying migrations configuration to use during initialization. + /// + /// + /// The builder that defines the model for the context being created. + /// If set to true the initializer is run using the connection information from the context that + /// triggered initialization. Otherwise, the connection information will be taken from a context constructed + /// using the default constructor or registered factory if applicable. + /// Migrations configuration to use during initialization. + public SqliteMigrateDatabaseToLatestVersion(DbModelBuilder modelBuilder, bool useSuppliedContext, TMigrationsConfiguration configuration) + : base(useSuppliedContext, configuration) + { + Initialize(modelBuilder); + } + + /// + /// Initializes a new instance of the SqliteMigrateDatabaseToLatestVersion class that will + /// use a specific connection string from the configuration file to connect to + /// the database to perform the migration. + /// + /// + /// The builder that defines the model for the context being created. + /// The name of the connection string to use for migration. + public SqliteMigrateDatabaseToLatestVersion(DbModelBuilder modelBuilder, string connectionStringName) + : base(connectionStringName) + { + Initialize(modelBuilder); + } + + + private static void Initialize(DbModelBuilder modelBuilder) + { + // 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.Conventions.Add(new IConvention[] + { + new AttributeToColumnAnnotationConvention("Autoincrement", (p, attributes) => true), + new AttributeToColumnAnnotationConvention("Collate", (p, attributes) => FormatCollationValue(attributes.Single())), + new AttributeToColumnAnnotationConvention("Unique", (p, attributes) => FormatUniqueValue(attributes.Single())), + new AttributeToColumnAnnotationConvention("SqlDefaultValue", (p, attributes) => attributes.Single().DefaultValue), + }); + } + + private static string FormatUniqueValue(UniqueAttribute uniqueAttribute) + { + return "OnConflict: " + uniqueAttribute.OnConflict; + } + + private static string FormatCollationValue(CollateAttribute collationAttribute) + { + var collationText = collationAttribute.Collation.ToString(); + if (!string.IsNullOrEmpty(collationAttribute.Function)) + collationText += ": " + collationAttribute.Function; + return collationText; + } + } +} diff --git a/SQLite.CodeFirst/Public/SqliteMigrationSqlGenerator.cs b/SQLite.CodeFirst/Public/SqliteMigrationSqlGenerator.cs new file mode 100644 index 0000000..a478c09 --- /dev/null +++ b/SQLite.CodeFirst/Public/SqliteMigrationSqlGenerator.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Data.Entity.Migrations.Model; +using System.Data.Entity.Migrations.Sql; +using System.Diagnostics; +using System.Text; +using SQLite.CodeFirst.Builder; + +namespace SQLite.CodeFirst +{ + /// + /// Converts provider-agnostic migration operations into database provider specific SQL commands for SQLite. + /// + /// + /// In the constructor of your migrations Configuration class, add: + /// SetSqlGenerator( "System.Data.SQLite", new SQLite.CodeFirst.SqliteMigrationSqlGenerator() ); + /// + public sealed class SqliteMigrationSqlGenerator : MigrationSqlGenerator + { + /// + /// Converts a set of migration operations into database provider specific SQL. + /// + /// The operations to be converted. + /// Token representing the version of the database being targeted. + /// A list of SQL statements to be executed to perform the migration operations. + public override IEnumerable Generate(IEnumerable migrationOperations, string providerManifestToken) + { + var migrationBuilder = new MigrationBuilder(providerManifestToken); + migrationBuilder.Generate(migrationOperations); +#if DEBUG + var text = new StringBuilder(); + foreach (var migrationStatement in migrationBuilder.MigrationStatements) + { + text.AppendLine(migrationStatement.Sql); + } + + Debug.WriteLine(text); +#endif + return migrationBuilder.MigrationStatements; + } + } +} \ No newline at end of file diff --git a/SQLite.CodeFirst/SQLite.CodeFirst.csproj b/SQLite.CodeFirst/SQLite.CodeFirst.csproj index 954af58..a374e83 100644 --- a/SQLite.CodeFirst/SQLite.CodeFirst.csproj +++ b/SQLite.CodeFirst/SQLite.CodeFirst.csproj @@ -1,7 +1,7 @@  - net40;net45;netstandard2.1 + net45;netstandard2.1 Marc Sallin @@ -21,6 +21,7 @@ + - +