Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Fluent API #533

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open

Added Fluent API #533

wants to merge 29 commits into from

Conversation

roygoode
Copy link

@roygoode roygoode commented Apr 3, 2017

The changes made in this pull request allow tables to be created using a Fluent API with attribute-free objects (aka POCOs). This allows for cleaner separation of layers and aids dependency injection by not requiring references to sqlite-net to decorate model classes with attributes.

Example usage:

connection.BuildMapping<SampleEntity>()
		  .TableName("ExampleTable")
		  .PrimaryKey(x => x.Key)
		  .CreateTable();

connection.BuildMapping<SampleEntity2>()
		  .TableName("Users")
		  .PrimaryKey(x => x.Id)
		  .AutoIncrement(x => x.Id)
		  .ColumnName(x => x.PasswordHash, "Password")
		  .Unique(x => x.Username)
		  .CreateTable();

connection.BuildMapping<SampleEntity3>()
		  .TableName("Contacts")
		  .Index("ix_full", x => x.Name, x => x.Email, x => x.Telephone)
		  .PrimaryKey(x => x.Id, true)
		  .MaxLength(x => x.Email, 255)
		  .NotNull(x => x.Email)
		  .Ignore(x => x.SecretMessage)
		  .CreateTable();

Based on the following example plain model classes...

public class SampleEntity
{
	public string Key { get; set; }

	public string Value { get; set; }
}

public class SampleEntity2
{
	public int Id { get; set; }

	public string Username { get; set; }

	public string PasswordHash { get; set; }
}

public class SampleEntity3
{
	public int Id { get; set; }

	public string Name { get; set; }

	public string Email { get; set; }

	public string Telephone { get; set; }

	public string SecretMessage { get; set; }
}

Note: CreateTable() must be called to trigger the creation of the mapping and resulting table.

The additional code recycles existing functionality for creating table mappings from attributes.

@RezaJooyandeh
Copy link

I was looking forward to this! Removing decoration from the classes and moving the sqlite dependency to where it is being used will be awesome 👍

@nberardi
Copy link

nberardi commented Aug 8, 2017

I made a change in #599 that is similar. That caches and allows custom building of TableMappings. I wonder if there is a way to combine the ideas. See the DefaultTableMapper. https://github.com/praeclarum/sqlite-net/pull/599/files?w=1#diff-e249207f2f9ac72b8e31b87d9be311e1R2523

The reason cached it was because I saw a lot of memory pressure, since most queries require the TableMapping. The reason I created the DefaultTableMapper was because in a couple cases I needed to do custom mappings against tables, where my object structure needed to have a hierarchy but my tables needed to be flat.

@praeclarum
Copy link
Owner

Hello!

I really love this but would like to change a few things too.

I think the builder should make TableMappings and those can be sent to CreateTable. This would make calling CreateTablesAsync more palatable.

So the syntax would be something like:

var mapping = TableMapping.Build<SampleEntity3>()
		  .TableName("Contacts")
		  .Index("ix_full", x => x.Name, x => x.Email, x => x.Telephone)
		  .PrimaryKey(x => x.Id, true)
		  .MaxLength(x => x.Email, 255)
		  .NotNull(x => x.Email)
		  .Ignore(x => x.SecretMessage)
		  .ToMapping();
connection.CreateTable (mapping);

@praeclarum
Copy link
Owner

Great work!

src/SQLite.cs Outdated
/// Adds a mapping to be used for indexes and naming for the given type.
/// </summary>
/// <param name="tableMapping">The table mapping.</param>
internal void AddMapping(TableMapping tableMapping)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this. Instead, we'll have the builder create a mapping and have CreateTable accept it.

src/SQLite.cs Outdated
}
}

public static class TableMappingFluentExtensions
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should not be public. I don't like introducing APIs unrelated to this library.

src/SQLite.cs Outdated

public class TableMappingBuilder<TEntity>
{
SQLiteConnection _sqlite;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not want a pointer back to the connection. I'm trying to keep Mappings separate from connections.

src/SQLite.cs Outdated
/// <returns>
/// The number of entries added to the database schema.
/// </returns>
public int CreateTable(CreateFlags createFlags = CreateFlags.None)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to remove this and instead introduce a CreateTable(TableMapping map) method that registers the mapping if needed but then hands off to the normal code.

@praeclarum
Copy link
Owner

Tracking issue is #422

@Eonasdan
Copy link

@praeclarum @roygoode what would it take to get this going? I started messing around with Roy's code based on your comments but this PR is from April has quite a bit of drift at this point.

@sunthx
Copy link

sunthx commented Sep 4, 2017

Looking forward to this feature

@roygoode
Copy link
Author

roygoode commented Jul 1, 2018

Apologies to all in the delay in getting these updates in. I’ve spent pretty much the last 12 months working on non-.NET projects, but I’m back on Xamarin now for a new project. I will re-implement the Fluent API with the latest version and as per @praeclarum’s great suggestions.

roygoode added 10 commits July 2, 2018 10:05
Merge in latest sqlite-net enhancements
This is necessary to allow for ColumnMapping and TableMapping to have derived classes in the next step of Fluent API development
Implements the original functionality of TableMapping.Column for building column definitions from classes decorated with attributes, to allow the functionality to be kept separate from the new Fluent API
Implements the original functionality of TableMapping for building table definitions from classes decorated with attributes, to allow the functionality to be kept separate from the new Fluent API
This provides a common API for indices created by the original attribute-based implementation and the new Fluent API implementation so they can be used interchangeably
This will be used by the Fluent API version(s) of the CreateTable method(s) to add or replace mappings created without using attributes
This will be used for parsing properties from expressions in the Fluent API
Allows building table mappings using a Fluent API
Call this method to create a new instance of TableMappingBuilder<T> to use the Fluent API to build table mappings
roygoode added 10 commits July 2, 2018 12:50
This will be used internally to create tables in the database based on given table mappings
CreateTable(), CreateTables(), CreateTableAsync(), CreateTablesAsync()
Used for creating tables based on the provided table mapping, generated from TableMappingBuilder
So you can use the new CreateTable(TableMapping…) methods with the original attribute-based implementation of table mappings if you really want to
Because this method is useful for supplying table mappings for query results, if you really need to specify column names without decorating those classes with attributes
Useful for querying the database with strongly-typed results for those that use the Fluent API to create table mappings
This implementation was missing from the Fluent API but was available via TableAttribute
TableQuery constructor that accepts TableMapping
Based on previous CreateTable tests
Between Fluent API and Attribute API
(Fixes bug that failed new unit tests for Fluent API)
# Conflicts:
#	src/SQLite.cs
@roygoode
Copy link
Author

roygoode commented Jul 2, 2018

As promised, I've updated to the latest source, and re-applied the new Fluent API changes, and also updated according to @praeclarum's reviews.

Here is an update to the example usage:

var entity1 = TableMapping.Build<SampleEntity>()
		  .TableName("ExampleTable")
		  .PrimaryKey(x => x.Key)
		  .ToMapping();

var entity2 = TableMapping.Build<SampleEntity2>()
		  .TableName("Users")
		  .PrimaryKey(x => x.Id)
		  .AutoIncrement(x => x.Id)
		  .ColumnName(x => x.PasswordHash, "Password")
		  .Unique(x => x.Username)
		  .ToMapping();

var entity3 = TableMapping.Build<SampleEntity3>()
		  .TableName("Contacts")
		  .Index("ix_full", x => x.Name, x => x.Email, x => x.Telephone)
		  .PrimaryKey(x => x.Id, true)
		  .MaxLength(x => x.Email, 255)
		  .NotNull(x => x.Email)
		  .Ignore(x => x.SecretMessage)
		  .ToMapping();

connection.CreateTable(entity1);
connection.CreateTable(entity2);
connection.CreateTable(entity3);

// OR

connection.CreateTables(CreateFlags.None, entity1, entity2, entity3);

Copy link
Author

@roygoode roygoode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have implemented all of the changes as requested in the latest commits

@roygoode
Copy link
Author

roygoode commented Jul 2, 2018

I have created a fresh pull request #727 with a cleaner branch as my master was tracking the original master from circa April 2017 and has created diffs that look like a mess. The new pull request should be much easier to merge.

@ghost
Copy link

ghost commented Jul 15, 2018

this will be very helpful if this will be merged in the soonest possible time

@rdvojmoc
Copy link

What is status on this pull request? This is a must have feature if you want to map third-party models.

@Mrsevic
Copy link

Mrsevic commented Nov 9, 2020

Is there any status update available?

@michaldobrodenka
Copy link

michaldobrodenka commented Nov 10, 2020

@Mrsevic I have merged and fixed current official and fluent api pull requests for testing, it seems ok. I'll try to use it in production.

But it's only in my fork (I'm not sure now if all the fixes are pushed to github)

@v-kravets v-kravets mentioned this pull request Jul 1, 2023
@lancecontreras
Copy link

lancecontreras commented Apr 1, 2024

Trying to build out of @roygoode's changes. Anyone almost close to making it work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants