dotnet-cf
5.0.0
See the version list below for details.
dotnet tool install --global dotnet-cf --version 5.0.0
dotnet new tool-manifest # if you are setting up this repo dotnet tool install --local dotnet-cf --version 5.0.0
#tool dotnet:?package=dotnet-cf&version=5.0.0
nuke :add-package dotnet-cf --version 5.0.0
CodeFirstDbGenerator
Motivation: allow to create migrations and update database without installing Entity Framework, for libraries like Dapper.
Db Supported:
- Sql Server (CodeFirstDbGenerator.SqlServer)
- Sqlite (CodeFirstDbGenerator.Sqlite)
- Or write your own library
Languages supported:
- C#
Installation
Install packages : CodeFirstDbGenerator (Migration base class)
install-package CodeFirstDbGenerator
install-package CodeFirstDbGenerator.SqlServer
And CodeFirstDbGenerator.Tools (Visual Studio Package Manager Console)
install-package CodeFirstDbGenerator.Tools
.. or dotnet-cf (dotnet tool)
dotnet tool install --global dotnet-cf
Tip: To uninstall a previous version of the tool and list the tools
dotnet tool uninstall -g dotnet-cf
dotnet tool list -g
Or add package references to project
<PackageReference Include="CodeFirstDbGenerator" Version="5.0.0" />
<PackageReference Include="CodeFirstDbGenerator.SqlServer" Version="5.0.0" />
<PackageReference Include="CodeFirstDbGenerator.Tools" Version="5.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Recommendation : Create a class Library .NET 5 or .NET 6 for Migrations
Generate entities from existing/ updated database with Entity Generator (dotnet tool)
Create a DbModel
public class ApplicationDbModel : DbModel
{
public ApplicationDbModel(DbModelOptions<ApplicationDbModel> options)
: base(options)
{ }
public override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>();
modelBuilder.Entity<Author>();
modelBuilder.Entity<Post>();
}
}
CF autodetects data annotations, primary keys, foreign keys and relations by default. Also Fluent Api can be used.
Data Annotations Attributes
- Key: for primary key (identity if int, short or long). For composite primary key use fluent api.
- Table: to define table name
- Column: to define column name and type name
- DatabaseGenerated + identity option: for a column identity
- StringLength or MaxLength: to define string length (exeample "navarchar(100)")
- ForeignKey: to specify the property name
// [Table("tbl_Companies")] allows to define the name of the table
public class Company
{
// [Key] or auto discovered if property name equals ClassName + Id (One Key attribute per class)
public int CompanyId { get; set; } // key
[StringLength(100)]
public string Name { get; set; } //required
[Required]
//[Column("MyPostalCode")] allows to rename the column
public string PostalCode { get; set; } // required with data annotations
[Column(TypeName ="ntext")] // allows to change the type
public string? Address { get; set; }
[MaxLength(50)] // or [StringLength(50)]
public string? City { get; set; } // not required
[Timestamp]
public byte[] RowVersion { get; set; }
public List<Employee> Employees { get; set; } = new();
}
public class Employee
{
[Key] // or not identity [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int EmployeeId { get; set; } // recommendation: make Key unique, dont use names like "Id" for all primary keys
public string FirstName { get; set; }
public string LastName { get; set; }
public int CompanyId { get; set; } // foreign key auto detected
public Company Company { get; set; }
[NotMapped] // ignored
public string FullName
{
get { return $"{FirstName} {LastName}"; }
}
}
Primary Key
Auto detected if property name equals "Class name + Id"
public class Author
{
public int AuthorId { get; set; }
}
Relations
Foreign key. Auto detected
if property equals "Principal" class name + Id
public class Author
{
public int AuthorId { get; set; }
}
public class Post
{
public int PostId { get; set; }
public int AuthorId { get; set; }
}
Else use Foreign key Attribute
public class Author
{
public int AuthorId { get; set; }
}
public class Post
{
public int PostId { get; set; }
[ForeignKey(nameof(TheAuthor))]
public int TheAuthorId { get; set; }
public Author TheAuthor { get; set; }
}
Or
public class Post
{
public int PostId { get; set; }
public int TheAuthorId { get; set; }
[ForeignKey(nameof(TheAuthorId))]
public Author TheAuthor { get; set; }
}
Many to Many
relations : a table "PostCategory" is created
public class Post
{
public int PostId { get; set; }
// etc.
public List<Category> Categories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public List<Post> Posts { get; set; }
}
Fluent Api
Allows to define composite key for example
modelBuilder.Entity<Category>().HasKey(x => new { x.CategoryId1, x.CategoryId2 });
Methods
- ToTable : allows to set the table name
- HasKey: to define the primary key
- HasForeignKey: to add a foreign key
- HasIndex: to add index
- HasColumnName: to change the column name
- HasColumnType: to change the column type
- IsRequired
- IsUnique
- HasDefaultValue
- etc.
Samples :
modelBuilder.Entity<Author>().ToTable("MyAuthors");
modelBuilder.Entity<Category>().Property(x => x.Name).HasColumnName("CategoryName");
modelBuilder.Entity<Post>().Property(x => x.Content).HasColumnType("text");
modelBuilder.Entity<Post>().HasForeignKey(x => x.TheAuthorId, "Authors", "AuthorId", onDelete: ReferentialStrategy.Cascade);
modelBuilder.Entity<Post>().HasIndex(x => x.Title);
Configuration
3 choices:
- With ServiceCollection extensions
- Or override
OnConfiguring
DbModel method - Or override
Migrator
andDatabase
DbModel properties
ServiceCollection
SqlServer
services.AddDbModel<ApplicationDbModel>(options =>options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
Add a constructor with DbModelOptions like this
public class ApplicationDbModel : DbModel
{
public ApplicationDbModel(DbModelOptions<ApplicationDbModel> options)
: base(options)
{ }
}
Tip : Add verbose to show sql generated
services.AddDbModel<ApplicationDbModel>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).WithVerbose());
With Sqlite
services.AddDbModel<SqliteDbModel>(options => options.UseSqlite(@"Data Source=C:\Db\Sample.db; Cache = Shared")); // dont use relative path
OnConfiguring
public class ApplicationDbModel : DbModel
{
public override void OnConfiguring(DbModelOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=Sample;Trusted_Connection=True;MultipleActiveResultSets=true");
}
// etc.
}
Add Migration
3 choices :
- Package Manager Console (require CodeFirstDbGenerator.Tools)
- dotnet cf (require dotnet-cf tool)
- Code (not recommended) : do not forget Migration and DbModel attributes
Package Manager Console
. First select the project with the DbModel in the Package Manager Console then
Add-Migration InitialCreate
Option | Description |
---|---|
-Project | The name of the project |
-StartupProject | The name of the Startup project |
-DbModel | The name of the DbModel to use if the project has more than one DbModel |
Sample
Add-Migration InitialCreate -StartupProject SampleWeb -DbModel ApplicationDbModel
A migration (20220119222431_InitialCreate for example) and a model snapshot (ApplicationDbModelSnapshot for example) are generated in a directory Migrations.
... Or dotnet cf tool
dotnet cf migrations add InitialCreate -a path/to/assembly.dll
Its possible to generate a migration and define a SQL Query for example
[DbModel(typeof(ApplicationDbModel))]
[Migration("20220119180814_AddGetCompanySP")]
public class AddGetCompanySP : Migration
{
protected override void Down(MigrationBuilder migrationBuilder)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(@"
CREATE PROC usp_GetCompany
@CompanyId int
AS
BEGIN
SELECT *
FROM Companies
WHERE CompanyId = @CompanyId
END
GO
");
}
Update Database
2 choices :
- Package Manager Console (require CodeFirstDbGenerator.Tools)
- dotnet cf (require dotnet-cf tool)
Package Manager Console
Update-Database
Option | Description |
---|---|
-Project | The name of the project |
-StartupProject | The name of the Startup project |
-Migration | The migration id. Used to Rollback |
-DbModel | The name of the DbModel to use if the project has more than one DbModel |
-ConnectionString | To define another connection string to use |
Sample to rollback
to a migration
Update-Database -Migration 20220119222431_InitialCreate
... Or dotnet cf tool
dotnet cf database update -a path/to/assembly.dll
With a library that contains migrations and a startup assembly (Application Web Asp.Net Core) for example :
dotnet cf database update -a path/to/assembly.dll -s path/to/startup-assembly.dll
Tip: create a bash file to execute multiple commands. Example test.sh
and use GIT Bash 'sh test.sh'
echo '> Sample1'
dotnet cf database update -a "C:\Samples\Sample1\bin\Debug\net5.0\Sample1.dll"
echo '> Sample2'
dotnet cf database update -a "C:\Samples\Sample2\bin\Debug\net6.0\Sample2.dll"
Migrations History
By default a table "__CFMigrationsHistory" is created.
Sample to use JsonMigrationsHistory
:
services.AddDbModel<ApplicationDbModel>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
.WithDropDatabase()
.UseHistory(new JsonMigrationsHistory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CodeFirstDbGenerator\\SampleDb__MigrationsHistory.json")));
});
Its possible to create a custom MigrationsHistory. Just implement IMigrationsHistory
and change the history.
Tip configure the host builder factory for a wpf app for example
Install packages
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
</ItemGroup>
Add a CreateHostBuilder
function to the entry point of the application
using CodeFirstDbGenerator;
using CodeFirstDbGenerator.SqlServer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Sample.Data;
using System;
using System.Windows;
namespace WpfDi
{
public partial class App : Application
{
private IHost host;
public App()
{
host = CreateHostBuilder().Build();
}
private void Application_Startup(object sender, StartupEventArgs e)
{
host.Start();
var shell = host.Services.GetRequiredService<MainWindow>();
shell.Show();
}
public static IHostBuilder CreateHostBuilder() =>
Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddScoped<MainWindow>();
services.AddDbModel<MyDbModel>(options => options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=TestWpfDi;Trusted_Connection=True;"));
});
}
}
Create a library for a Database
Create a libray for a database is easy.
- Create a Migrator that inherits from Migrator base class
- Create a TypeTranslator. The service receives a clr type (string, int, short, etc.) and returns the sql type (varchar(255), integer, tinyint, etc.) with length. Use the column, string length and max length annotations to resolve the column type.
- Create a DatabaseCreator that inherits from DatabaseCreator base class and ensures database created
- etc.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
This package has no dependencies.