LinuxDoku / migratordotnet

Automatically exported from code.google.com/p/migratordotnet
0 stars 0 forks source link

Create Fluent Interface #18

Open GoogleCodeExporter opened 8 years ago

GoogleCodeExporter commented 8 years ago
A number of users have asked for a more Fluent Interface. It might be easier to 
understand how to get 
started?

Design and create a Fluent Interface

Something like:
Table t = new Table("table_name");
t.AddColumn("id").OfType(DbType.Int32).WithProperty(ColumnProperty.PrimaryKeyWit
hIdentity);
t.AddColumn("other_id").OfType(DbType.Int32);
t.AddConstraint("FK_Table_Other_Table").FromColumn("other_id").ToTable("other_ta
ble").ToColumn("id");

Database.AddTable(t);

Would probably need to better model Table, Column, Constraint, etc.
This would likely be a pretty big change. We would need to think about modeling 
all the pieces and then 
refactoring the internals to deal with those objects.

Original issue reported on code.google.com by geoffl...@gmail.com on 7 Jun 2008 at 2:11

GoogleCodeExporter commented 8 years ago
First off, we would want to change the whole deal together like so:

Database.AddTable("Table_Name")
    .AddColumn().OfType().WithProperty()
    .AddForeignKey().FromColumn("someid").ToPrimaryColumn("primaryid");

something along those lines. you had it close. the t. is repetitive.

For this we could create Helper classes that would allow us to chain everything 
together. this way it wouldn't be too drastic of a change. We would create 
specific "Options" classes/interfaces:

IColumnOptions, IConstraintOptions, ITableOptions, IForeignKeyOptions etc...

I have little experience with fluent interfaces but this is my understanding of 
how 
to accomplish a fluent interface with minimal grief. A real good place to look 
for 
ideas on a fluent interface is ayende's rhino mocks: https://rhino-
tools.svn.sourceforge.net/svnroot/rhino-tools/trunk/rhino-mocks/Rhino.Mocks/

Therefore, ColumnOptions would return a class that can chain together all 
options 
for columns (WithProperty, OfType etc..) including the options necessary to 
continue 
the next step in the chain (ITableOptions, IConstraintOptions) something along 
those 
lines.

Let's create a branch called fluent-interface-refactoring and start playing 
with 
something there. I would definately like to see this feature implemented.

Original comment by dko...@gmail.com on 13 Jun 2008 at 3:38

GoogleCodeExporter commented 8 years ago

Original comment by dko...@gmail.com on 13 Jun 2008 at 3:38

GoogleCodeExporter commented 8 years ago
heres another link by martin fowler for more info:
http://www.martinfowler.com/bliki/FluentInterface.html

Original comment by dko...@gmail.com on 13 Jun 2008 at 3:40

GoogleCodeExporter commented 8 years ago
This from hammet is something word to take a look at . 
Taken from his blog at: 

http://hammett.castleproject.org/?p=290 a patch for the migrations tool that 
castle 
was maintaining.

SchemaBuilder builder = new SchemaBuilder(Schema);

TableBuilder company = builder.AddTable("Company",
        Columns.PrimaryKey<int>("Id").Identity(),
        Columns.Simple<string>("Name", 25)
    );

builder.AddTable("Project",
        Columns.PrimaryKey<int>("Id").Identity(),
        Columns.Simple<string>("Name", 25),
        Columns.ForeignKey("CompanyID", company)
    );

builder.AddTable("User",
        Columns.PrimaryKey<int>("Id").Identity(),
        Columns.Simple<string>("`Key`", 15).MakeUnique(),
        Columns.Simple<string>("Name", 25),
        Columns.ForeignKey("CompanyID", company)
    );

Original comment by gustavo.ringel@gmail.com on 13 Jun 2008 at 4:00

GoogleCodeExporter commented 8 years ago
I like that.

The only thing is, I've always thought that passing an array of columns in the 
AddTable method to be clunky. I like it more from your first comment. I think 
that 
the addtable and addcolumn should be 2 seperate methods.

what do you think?

Original comment by dko...@gmail.com on 13 Jun 2008 at 11:41

GoogleCodeExporter commented 8 years ago
dkode8,
Well, I think I just had a bit of an epiphany - so yes, you could be right. 

I was thinking of having to return the Domain Objects from those methods. I 
don't
think that would be possible. (AddColumn would return a Column, so how would 
you ever
get back to the Table to add another column?) I think we need a Mediator/Builder
returned from each of those methods. It would need to maintain almost a state 
machine
to know last table, last column, etc so that if you did AddColumn AddColumn it 
would
know that you were adding to the column stack so that OfType would then go to 
the
second Column.

I'm setting this fore Milestone-9 - that would be a good release for this?

Original comment by geoffl...@gmail.com on 13 Jun 2008 at 1:48

GoogleCodeExporter commented 8 years ago
that sounds good. I will create a branch for this tonight or tomorrow and start 
to 
play with some functionality.

Original comment by dko...@gmail.com on 13 Jun 2008 at 11:53

GoogleCodeExporter commented 8 years ago
upon further inspection I think you were correct Geoff.

This is going to require a major refactoring of the TransformationProvider base 
class. The current impl, executes each migration right away in isolation.

We would need to refactor the providers to construct the query over time and 
end it 
with a .Go(); or Execute();

Ideas?

Original comment by dko...@gmail.com on 15 Jun 2008 at 12:53

GoogleCodeExporter commented 8 years ago
I don't think you should start with Database.XXX
Start with SchemaBuilder like Gustavo said. Createa whole different class 
completely independent of Database.

SchemaBuild can be sort of a stack-based approach where you keep a reference to 
the last table object, last 
column object, etc internally. Every method call return the SchemaBuilder 
itself.

And then in the end you can do Database.Apply(schemaBuild) or something. That 
method will translate the 
SchemaBuilder into straight TransformationProvider calls.

Does that make sense? I haven't tried any of this, just in my head. I can 
always give this a go as well.

Original comment by geoffl...@gmail.com on 15 Jun 2008 at 3:40

GoogleCodeExporter commented 8 years ago
i've written a couple of tests and created the SchemaBuilder in the fluent 
interface 
branch.

One thing that flows kind of nice is restricting chainings available by use of 
an 
interface. If we decide on what steps can be taken when, then we can restrict 
the 
flow to other steps. This makes the fluent interface a little easier to work 
with.

Anyways, the start of it is on the branch take a gander and write a couple of 
tests 
if anyone wants to. I will have more time throughout the week to work on it.

Original comment by dko...@gmail.com on 18 Jun 2008 at 2:49

GoogleCodeExporter commented 8 years ago
I had an opportunity tonight to make a couple of additional changes to the 
branch.

After diving deeper into the foreignkey functionality and foreignkey 
constraints I 
found that I needed to create a "FluentColumn" decorator to warp Column due to 
the 
fact that constraints are executed on the fly when invoked via 
TransformationProvider. This way we can store the action to perform until the 
SchemaBuilder is passed to transformation provider. I will commit the most 
recent 
changes briefly.

The fluent class SchemaBuilder is pretty much there. minor refactorings needed. 
The 
last piece of the puzzle is just creating a means for TransformationProvider to 
consume the SchemaBuilder instances and translate them into the original 
commands. 
It feels like there could a pattern to aid in the translation from 
SchemaBuilder to 
TransformationProvider. Perhaps meditator pattern or something similar.

Original comment by dko...@gmail.com on 30 Jun 2008 at 2:41

GoogleCodeExporter commented 8 years ago
Hey all,

I have been looking through migrator.net sources out of curiousity.

I have a suggestion. might be far off, but i think it could work, without adding
domain objects such as Table, Column, etc:

   Each method in the provider, will instantiate a continuation object with 'this'
   (the provider) and return it.

   Create a set of such interfaces that expose only part of the provider interface, for
   each "state" that was discussed. For example, after addColumn there is no logic
   in adding table, so the interface that addColumn might return shouldn't expose
   those methods of the provider.

   ColumnContinuation : IColumnContinuation
     - instance of the provider
     - methods that exposed from the provider after creating a column.

Original comment by dot...@gmail.com on 12 Jul 2008 at 11:02

GoogleCodeExporter commented 8 years ago
this is already in the works in the method that you describe. To see the 
progress 
please take a look at the fluent interface branch here:

https://migratordotnet.googlecode.com/svn/branches/fluent.interface

if you would like to submit any patches for adding functionality to this it 
would be 
apprecated. I haven't had a lot of time to work on it in the last two weeks. 
Thanks!

Sean

Original comment by dko...@gmail.com on 14 Jul 2008 at 12:17

GoogleCodeExporter commented 8 years ago
I've looked at the code, however I didn't realize what each interface should 
have.
Is there any spec. as to what operations are allowed after each fluent action?

Original comment by dip...@gmail.com on 16 Jul 2008 at 11:27

GoogleCodeExporter commented 8 years ago
At the moment there are no specs as to what each interface should be allowed to 
do.

What I was doing was looking at the existing implementation and go strictly off 
of 
that. ie. Database.AddTable("", new Column(), new Column);
Database.AddForeignKey

etc...

At the moment, the RenameTable/DeleteTable interfaces have to be created. I 
didn't 
get to that part yet. As well as the ability to pass the SchemaBuilder instance 
to 
the TransformationProvider at hand to execute the pre-built fluent interface 
actions. In some form of:

Database.ExecuteSchemaBuilder(schemaBuilder);

Other than that, I got it half way there for now.

Original comment by dko...@gmail.com on 16 Jul 2008 at 11:54

GoogleCodeExporter commented 8 years ago
Hi dkode8,

I have applied the changes and made the structure that would enable
creating such a schema expression in the provider.

The expressions and creating expressions are implemented using Visitor,
and I may have changed field naming for clarity.

I have not written tests for it, I'd like to know what you guys think.

Thanks.

Original comment by dip...@gmail.com on 17 Jul 2008 at 10:36

Attachments:

GoogleCodeExporter commented 8 years ago
dipidi,

thank you for taking the time to do this.

I have committed your patch along with some additional modifications in 
revision 101.

I didn't modify any of your changes, just cleaned some stuff up and moved the 
classes into the Migrator.Framework assembly. The ISchemaBuilderExpression 
visitor 
works perfectly for applying all of the db actions, as well as avoid the need 
to 
modify TransformationProvider.

The tests are still failing, but only because the Columns collection was 
removed 
from SchemaBuilder as it is no longer needed as you stated in the code.

I am going to test the fluent interface during the week on a recent project, if 
all 
is well after a couple of days I think we should merge the changes back into 
the 
trunk if there are no objections.

on a side note, could you use tabs instead of spaces in your patches in the 
future? 
no biggie, just trying to keep the formatting straight.

thanks again for the patch!

Sean

Original comment by dko...@gmail.com on 20 Jul 2008 at 3:44

GoogleCodeExporter commented 8 years ago
oh, one other minor thing i forgot to mention that gave me trouble. when you 
create 
the patch next time, could you base it out of the Migrator root where 
Migrator.sln 
is? It was looking for migrator/branches/fluent.interface on my local machine 
to 
apply the patch. this can be avoiding if you generate the patch at the project 
root.

thanks!

Sean

Original comment by dko...@gmail.com on 20 Jul 2008 at 3:47

GoogleCodeExporter commented 8 years ago
ok, started testing this out, it seems as there is a problem with the 
AddColumnExpression. I am going to verify against SqlExpress, as I have only 
tested 
with MySql at the moment.

Does anyone know how to turn on logging for migrator? i forget how to do this

Original comment by dko...@gmail.com on 23 Jul 2008 at 2:12

GoogleCodeExporter commented 8 years ago
Hi Sean,

I've finally had some time to run some tests against SqlServer.
It seems the problem was when adding a table which generated an 
AddTableExpression,
but it would allow the provider to call AddTable without any columns - which
generates illegal SQL statement (on most DBMS its illegal).

I have attached a fix to this. Generally it deals with the case when the
user adds a table, the next column she will add will be inserted into the
AddTableExpression, such that a new table query is always executed with
the first column added. The rest of the cols will be added independently with
an AddColumnExpression of their own.

On a side note, public virtual void AddTable(string name, params Column[] 
columns)
will allow a table to be added with an empty Column[] array.. which will 
generate
the same bad SQL.

To make the patch I have used Tabs, and based the patch by the solution root. 
Please
let me know if it wasn't made properly.

Have a nice weekend :)

Original comment by dip...@gmail.com on 25 Jul 2008 at 10:12

Attachments:

GoogleCodeExporter commented 8 years ago
cool deal.

i had a hdd failure yesterday so I am re-installing vs2005 now. I will apply 
this 
patch later this afternoon. thanks!

Sean

Original comment by dko...@gmail.com on 27 Jul 2008 at 1:59

GoogleCodeExporter commented 8 years ago
What is the status of merging the fluent interface branch into the trunk?

Original comment by troygoode on 12 Aug 2008 at 6:13

GoogleCodeExporter commented 8 years ago
hi guys has anyone thought about taking advantage of some syntactic sugar of 
object 
and collection initializers? I think they read very well but might be a bit 
long 
winded for some and also having to move to .NET 3.5 and VS2008 might be a bit 
of a 
sore point for some, just a thought! :)

eg:-

Table table = new Table()
{
   Name = "Customer",
   Columns = 
   {
      new Column()
      {
         Name = "Id",
         Type = DbType.Int32,
         ColumnProperty = ColumnProperty.PrimaryKey
      }
   }
}

Original comment by flux...@gmail.com on 27 Aug 2008 at 9:56

GoogleCodeExporter commented 8 years ago
[deleted comment]
GoogleCodeExporter commented 8 years ago
Oh, here is a sample from the tests (this works as is):

_schemaBuilder
   .AddTable("PrimaryKeyTable")
   .AddColumn("PrimaryKeyColumn").OfType(DbType.Int32).AsPrimaryKey

   .AddTable("SomeTable")
   .AddColumn("SomeColumn").OfType(DbType.Int32)
   .AddColumn("MyColumnThatIsForeignKey").OfType(DbType.Int32).AsForeignKey
   .ReferencedTo("PrimaryKeyTable", "PrimaryKeyColumn")
   .WithConstraint(ForeignKeyConstraint.NoAction);

Original comment by dot...@gmail.com on 8 Sep 2008 at 6:01

GoogleCodeExporter commented 8 years ago
Hey all,
I had the time to properly run the tests, for now under sqlserver 2005.

Several Issues:

1.
through a provider when a column is pulled and i want to look at its
properties, not all of them were set -- specifically default value wasn't.
i've wrote down the fetch but there is a need to parse each RDBMS' way of
expressing default values, like ((13)) for 13, and N'abc' for a string 'abc'.

pulling column size isn't specified there, and i left it that way for now.

2.
some existing misconsiderations with the existing fluent interface, fixed a bug
and converted AsForeignKey() to just a property - AsForeignKey. Added 
AsPrimaryKey too.

3.
Created a separate test for the fluid interface. the test is dirty in the sense
that it actually uses a concrete provider and does insertions to the database 
(takes
them back though) through a transaction.

any feedback would be appreciated, also.. anyone has an idea how to parse the 
default
values for each column? from what i know, every RDBMS might have its own format.

Dotan

Original comment by dot...@gmail.com on 8 Sep 2008 at 6:08

Attachments:

GoogleCodeExporter commented 8 years ago
I'm not quite sure how far along with this you guys are, but personally I think 
a 
fluent interface should read more like:

_schemaBuilder
   .AddTable(t => t.Name("PrimaryKeyTable")
                  .AddColumn(c => c.Name("PrimaryKeyColumn")
                                  .OfType(DbType.Int32)
                                  .AsPrimaryKey)
                  )
   .AddTable(t => t.Name("SomeTable")
                  .AddColumn(c => c.Name("SomeColumn")
                                  .OfType(DbType.Int32)
                  )
                  .AddColumn(c => c.Name("MyColumnThatIsForeignKey")
                                  .OfType(DbType.Int32)
                                  .AsForeignKey(fk => fk.ReferencedTo
("PrimaryKeyTable", "PrimaryKeyColumn")
                                                      .WithConstraint
(ForeignKeyConstraint.NoAction)
                                  )
                  )

    );

So we are using lambda function that are applied to builders to generate the 
objects. BTW I've just started using migratordotnet in the last couple of days 
and I 
think is full of creamy goodness. If I get a chance I'll check out the fluent 
interface branch.

Keep up the good work!
Cheers
Gordon

Original comment by mail.gor...@gmail.com on 4 Jun 2009 at 3:39

GoogleCodeExporter commented 8 years ago
I'm working on an interface like rails' one:

//automatically adds id, based on IConvention instance
CreateTable("users" t=> {
    t.String("name").WithSize(128);
    t.Int32("age").NotNullable();
    t.Binary("photo");
    t.Int32("address_id").AutoForeignKey("addresses");
})

http://code.google.com/p/migratordotnet-fluent/

Original comment by zerova...@gmail.com on 5 Sep 2009 at 4:25

GoogleCodeExporter commented 8 years ago
Has anybody considered an interface like this?

public class CreateSystemUserTable : Migration {
    public class SystemUser {
        [PrimaryKey]
        public Guid SystemUserId { get; set; }
        [Length(50)]
        public string FirstName { get; set; }
        [Length(50)]
        public string LastName { get; set; }
        [Length(50)]
        public string EmailAddress { get; set; }
    }

    public override void Up() {
        Database.AddTable<SystemUser>();
    }

    public override void Down() {
        Database.RemoveTable<SystemUser>();
    }
}

public class AddEncryptedPasswordToSystemUser : Migration {
    public class SystemUser {
        [Length(250)]
        public string EncryptedPassword { get; set; }
    }

    public override void Up() {
        Database.AddColumn<SystemUser>();
    }

    public override void Down() {
        Database.RemoveColumn <SystemUser>();
    }
}

public class AddAdminUser : Migration {
    public class SystemUser {
        public Guid SystemUserId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string EmailAddress { get; set; }
        public string EncryptedPassword { get; set; }
    }
    public override void Up() {
        IEncryptionEngine engine = new AesEncryptionEngine();

        Database.Insert(new SystemUser {
            SystemUserId=Guid.NewGuid(),
            FirstName="Joe",
            LastName = "Smith",
            EmailAddress = "admin@system.com",
            EncryptedPassword=engine.Encrypt("pass")
        });
    }

    public override void Down() {
        Database.Delete<SystemUser>(u=>u.EmailAddress=="admin@system.com");
    }
}

--
I mean, fluent interfaces are cool and all that but there is so many magic 
strings in
your examples and they just look so very fragile. 
With the sample from comment 25 it could look something like this:
public class AddAdminUser : Migration {
    public class PrimaryKeyTable {
        [PrimaryKey(Identity=true)]
        public int PrimaryKeyColumn { get; set; }
    }

    public class SomeTable {
        [PrimaryKey(Identity=true)]
        public int SomeColumn { get; set; }

        [ForeignKey(typeof(PrimaryKeyTable), Constraint=ForeignKeyConstraint.NoAction)]
        public int MyColumnThatIsForeignKey { get; set; }
    }

    public override void Up() {
        Database.AddTable<PrimaryKeyTable>();
        Database.AddTable<SomeTable>();
    }

    public override void Down() {
        Database.RemoveTable<SomeTable>();
        Database.RemoveTable<PrimaryKeyTable>();
    }
}

Which is about the same number of characters as that example would be once put 
into
an actual migration (my rough attempt comes out to 592 chars this way vs 524 in
comment 25 after whitespace removal but comment 25 fails to specify the primary 
key
in the second table and if the keys are identity or not [could be hilo for 
instance];
my guess for adding those specs brings c25 up to 555 while this way could 
easily be
brought down to 541 chars without sacrificing any readability). 

Original comment by after.fa...@gmail.com on 9 Sep 2009 at 2:08