Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Adding associations using Oak.DynamicModel

Amir Rajan edited this page · 52 revisions

Links

General Usage

DynamicModel allows you to extend behavior of a class dynamically. This mechanism is used by Oak's Associations Module. A DynamicModel MUST be defined as follows:

public class Blog : DynamicModel
{
    //parameterless contructor is optional
    public Blog() : this(new { }) { }

    public Blog(object dto) : base(dto) { }
}

Once you have defined the basic DynamicModel, you can add associations by adding the following method:

public class Blog : DynamicModel
{
    //parameterless contructor is optional
    public Blog() : this(new { }) { }

    public Blog(object dto) : base(dto) { }

    //adding this method to your DynamicModel gives it association capabilities
    IEnumerable<dynamic> Associates()
    {
        yield return //you associations will go here.
    }
}

By adding the IEnumerable<dynamic> Associates() method to your DynamicModel, it will get methods specific to the associations you declare.

Associations

Here is a list of Associations supported by Oak:

HasMany


A blog has many comments.

Schema

Here is the schema for Blogs and Comments using Oak.Seed:

Seed seed = new Seed();
seed.PurgeDb();

seed.CreateTable("Blogs", new dynamic[] {
    new { Id = "int", Identity = true, PrimaryKey = true },
    new { Title = "nvarchar(255)" },
    new { Body = "nvarchar(max)" }
}).ExecuteNonQuery();

seed.CreateTable("Comments", new dynamic[] {
    new { Id = "int", Identity = true, PrimaryKey = true },
    new { BlogId = "int", ForeignKey = "Blogs(Id)" },
    new { Text = "nvarchar(1000)" }
}).ExecuteNonQuery();

Declaration

For example, if Blogs HasMany Comments:

public class Blog : DynamicModel
{
    Comments comments = new Comments();
    public Blog() : this(new { }) { }

    public Blog(object entity) : base(dto) { }

    public IEnumerable<dynamic> Associates()
    {
        yield return new HasMany(comments);
    }
}

Methods Available

Let's assume that a blog with Id 1 has a few comments:

Blogs blogs = new Blogs();
dynamic blog = blogs.Single(1); //retrieve a blog with id 1 using the dynamic repository
var comments = new Comments(); //dynamic repository declared for comments
blog.Comments(); //HasMany added a method Comments()
blog.CommentsIds(); //HasMany added a method CommentIds() 
var newComment = blog.Comments().New(); //this method will create a comment associated with this blog
comments.Insert(newComment);
var anotherNewComment = 
    blog.Comments().New(new { Text = "Some Value" }); //same thing except it sets properties too
comments.Insert(anotherNewComment);
var oneMoreComment = 
    blog.NewComment(new { Text = "Some Value });
comments.Insert(oneMoreComment);

Sql and Overrides

HasMany has a number of overrides, to control how the association queries for data. The overrides are provided as auto properties.

Here are the overrides for HasMany:

  • MethodName: changes the name of the association method

Sample usage:

public IEnumerable<dynamic> Associates()
{
    yield return new HasMany(comments)
    {
        MethodName = "TheComments"
    };
}

//instead of 
blog.Comments()

//the method will now be called
blog.TheComments()

Here is the sql that will be generated for HasMany:

select {childTable}.* 
from {childTable} 
where {foreignKey} in ({inClause})

Overrides to change what columns and values are used:

  • ForeignKey: changes the {foreignKey}
  • TableName: changes the {childTable}
  • PropertyContainingIdValue: the name of the property on the class that contains the id value {inClause}

Sample usage:

Given Schema:
- Blogs table with columns: BlogId, Name
- tbl_Comments table with columns: Id, Text, fk_BlogId

This alteration of the association will retrieve successfully.

//association method on Blog : DynamicModel
public IEnumerable<dynamic> Associates()
{
    yield return new HasMany(comments)
    { 
        ForeignKey = "fk_BlogId",
        ChildTable = "tbl_Comments",
        PropertyContainingIdValue = "BlogId"
    };
}

BelongsTo


A comment belongs to a blog.

Schema

Here is the schema for Blogs and Comments using Oak.Seed:

seed.CreateTable("Blogs", new dynamic[] {
    new { Id = "int", Identity = true, PrimaryKey = true },
    new { Title = "nvarchar(255)" },
    new { Body = "nvarchar(max)" }
}).ExecuteNonQuery();

seed.CreateTable("Comments", new dynamic[] {
     new { Id = "int", Identity = true, PrimaryKey = true },
     new { BlogId = "int", ForeignKey = "Blogs(Id)" },
     new { Text = "nvarchar(1000)" }
}).ExecuteNonQuery();

Declaration

public class Comment : DynamicModel
{
    Blogs blogs = new Blogs();
    public Comment(object dto) : base(dto) { }

    public IEnumerable<dynamic> Associates()
    {
        yield return new BelongsTo(blogs);
    }
}

Methods Available

Comments comments = new Comments();
var comment = comments.Single(/*some id*/);
var blog = comment.Blog(); //Blog method is added to Comment

Sql and Overrides

BelongsTo has a number of overrides, to control how the association queries for data. The overrides are provided as auto properties.

Here are the overrides for BelongsTo:

  • MethodName: changes the name of the association method

Sample usage:

public IEnumerable<dynamic> Associates()
{
    yield return new BelongsTo(blogs)
    {
        MethodName = "TheBlog"
    };
}

//instead of 
comment.Blog()

//the method will now be called
comment.TheBlog()

Here is the sql that will be generated for BelongsTo:

select * from {fromTable} 
where {primaryKey}
in ({inClause})

Overrides to change what columns and values are used:

  • IdColumnOfParentTable: changes the {primaryKey}
  • PropertyContainingIdValue: the name of the property on the class that contains the id value {inClause}
  • Repository.TableName: maps to the {fromTable}, the TableName is supplied by the repository passed into the constructor.

Sample usage:

Given Schema:
tbl_Blogs with columns: BlogId, Name
Comments table with columns: Id, Text, RelatedToBlogId

This alteration of the association will retrieve successfully.

//repository reference with table name override
public Blogs : DynamicRepository
{
    public Blogs() : base(tableName: "tbl_Blog") { }
}

//in Comment : DynamicModel
Blogs blogs = new Blogs();

public IEnumerable<dynamic> Associates()
{
    yield return new BelongsTo(blogs) 
    { 
        IdColumnOfParentTable = "BlogId",
        PropertyContainingIdValue = "RelatedToBlogId"
    };
}

HasManyAndBelongsTo


A student has many courses, courses have many students.

Schema

Here is the schema for Students and Courses using Oak.Seed:

seed.CreateTable("Students", new dynamic[] { 
    seed.Id(),
    new { Name = "nvarchar(255)" }
}).ExecuteNonQuery();

seed.CreateTable("Courses", new dynamic[] { 
    seed.Id(),
    new { Name = "nvarchar(255)" }
}).ExecuteNonQuery();

//==================================================================================
//the cross reference table's name is both the tables paired up ***alphabetically***
//==================================================================================
seed.CreateTable("CoursesStudents", new dynamic[] { 
    seed.Id(),
    new { CourseId = "int" },
    new { StudentId = "int" }
}).ExecuteNonQuery();

Declaration

public class Student : DynamicModel
{
    Students students = new Students();
    Courses courses = new Courses();

    public User(object entity) : base(dto) { }

    public IEnumerable<dynamic> Associates()
    {
        yield return new HasManyAndBelongsTo(repository: courses, references: students);
    }
}

public class Course : DynamicModel
{
    Students students = new Students();
    Courses courses = new Courses();

    public Course(object entity) : base(dto) { }

    public IEnumerable<dynamic> Associates()
    {
        yield return new HasManyAndBelongsTo(repository: students, references: courses);
    }
}

Methods Available

Let's assume a student with an Id 1 has a few courses:

Students students = new Students();
dynamic student = students.Single(1);
student.Courses();
student.CourseIds();
student.Courses().First().Student(); //reference back to the student that the course belongs to

Let's assume a course with an Id 1 has a few students:

Courses courses = new Courses();
dynamic course = courses.Single(1);
course.Students();
course.StudentIds();
course.Students().First().Course(); //reference back to the course that the student belongs to

Sql and Overrides

HasManyAndBelongsTo has a number of overrides, to control how the association queries for data. The overrides are provided as auto properties.

Here are the overrides for HasManyAndBelongsTo:

  • MethodName: changes the name of the association method

Sample usage:

public IEnumerable<dynamic> Associates()
{
    yield return new HasManyAndBelongsTo(repository: students, references: courses)
    {
        Named = "TheStudents"
    };
}

//instead of 
course.Students()

//the method will now be called
course.TheStudents()

Here is the sql that will be generated for HasManyAndBelongsTo:

select {toTable}.*, {xRefTable}.{xRefFromColumn}
from {xRefTable}
inner join {toTable}
on {xRefTable}.{xRefToColumn} = {toTable}.{toTableColumn}
where {xRefTable}.{xRefFromColumn} in ({inClause})

Overrides to change what columns and values are used:

  • XRefTable: changes the {xRefTable}
  • XRefToColumn: changes the {xRefToColumn}
  • XRefFromColumn: changes the {xRefFromColumn}
  • ToTableColumn: changes the {toTableColumn}
  • PropertyContainingIdValue: changes the value provided to the {inClause}
  • Repository.TableName: changes the {toTable}

HasManyThrough


A user has many games through a library.

Schema

Here is the schema for Users, Games and Library using Oak.Seed:

seed.CreateTable("Users", new dynamic[] {
    new { Id = "int", Identity = true, PrimaryKey = true },
    new { Email = "nvarchar(255)" }
}).ExecuteNonQuery();

seed.CreateTable("Games", new dynamic[] {
    new { Id = "int", Identity = true, PrimaryKey = true },
    new { Title = "nvarchar(255)" }
}).ExecuteNonQuery();

seed.CreateTable("Library", new dynamic[] {
    new { Id = "int", Identity = true, PrimaryKey = true },
    new { UserId = "int", ForeignKey = "Users(Id)" },
    new { GameId = "int", ForeignKey = "Games(Id)" },
}).ExecuteNonQuery();

Declaration

public class User : DynamicModel
{
    Games games = new Games();
    Library library = new Library();

    public User(object entity) : base(dto) { }

    public IEnumerable<dynamic> Associates()
    {
        yield return new HasManyThrough(repository: games, through: library);
    }
}

Methods Available

Let's assume that a blog with Id 1 has a few comments:

Users users = new Users();
dynamic user = users.Single(1);
user.Games();
user.GameIds();

Sql and Overrides

HasManyThrough has a number of overrides, to control how the association queries for data. The overrides are provided as auto properties.

Here are the overrides for HasManyThrough:

  • MethodName: changes the name of the association method

Sample usage:

public IEnumerable<dynamic> Associates()
{
    yield return new HasManyThrough(repository: games, through: library)
    {
        MethodName = "TheGames"
    };
}

//instead of 
user.Games()

//the method will now be called
course.TheGames()

Here is the sql that will be generated for HasManyThrough:

select {toTable}.*, {xRefTable}.{xRefFromColumn}
from {xRefTable}
inner join {toTable}
on {xRefTable}.{xRefToColumn} = {toTable}.{toTableColumn}
where {xRefTable}.{xRefFromColumn} in ({inClause})

Overrides to change what columns and values are used:

  • Repository.TableName: changes the {toTable} value, the first parameter in the constructor for HasManyThrough
  • through.TableName: changes the {xRefTable} value, the second parameter in the constructor for HasManyThrough
  • XRefToColumn: changes the {xRefToColumn} value
  • XRefFromColumn: changes the {xRefFromColumn} value
  • ToTableColumn: changes the {toTableColumn} value
  • PropertyContainingIdValue: where to retrieve the value for the {inClause}

HasOne


An author has one profile.

Schema

Here is the schema for Authors, and Profiles using Oak.Seed:

seed.CreateTable("Authors", new dynamic[] 
{
    new { Id = "int", Identity = true, PrimaryKey = true },
    new { Name = "nvarchar(255)" }
}).ExecuteNonQuery();

seed.CreateTable("Profiles", new dynamic[] 
{
    new { Id = "int", Identity = true, PrimaryKey = true },
    new { AuthorId = "int", ForeignKey = "Authors(Id)" },
    new { Email = "nvarchar(255)" }
}).ExecuteNonQuery();

Declaration

public class Author : DynamicModel
{
    Profiles profiles = new Profiles();
    public Author(object dto) : base(dto) { }

    public IEnumerable<dynamic> Associates()
    {
        yield return new HasOne(profiles);
    }
}

Methods Available

Authors authors = new Authors();
dynamic author = authors.Single(/* some id */);
author.Profile();

Sql and Overrides

HasOne has a number of overrides, to control how the association queries for data. The overrides are provided as auto properties.

Here are the overrides for HasOne:

  • MethodName: changes the name of the association method

Sample usage:

public IEnumerable<dynamic> Associates()
{
    yield return new HasOne(profile)
    {
        MethodName = "TheProfile"
    };
}

//instead of 
author.Profile()

//the method will now be called
author.TheProfile()

Here is the sql that will be generated for HasOne:

select * from {fromTable} 
where {foreignKey}
in ({inClause})

Overrides to change what columns and values are used:

  • Repository.TableName: changes the {fromTable}
  • ForeignKey: changes the {foreignKey}
  • Repository.PrimaryKeyField: the name of the property on the class that contains the id value {inClause}

HasOneThrough


A customer has one supplier through a distribution channel.

Schema

Here is the schema for Customers, Supplies, and DistributionChannels using Oak.Seed:

seed.CreateTable("Customers", new dynamic[] { 
    new { Id = "int", Identity = true, PrimaryKey = true },
    new { Name = "nvarchar(255)" }
}).ExecuteNonQuery();

seed.CreateTable("Suppliers", new dynamic[] { 
    new { Id = "int", Identity = true, PrimaryKey = true },
    new { Name = "nvarchar(255)" }
}).ExecuteNonQuery();

seed.CreateTable("DistributionChannels", new dynamic[] { 
    new { Id = "int", Identity = true, PrimaryKey = true },
    new { CustomerId = "int", ForeignKey = "Customers(Id)" },
    new { SupplierId = "int", ForeignKey = "Suppliers(Id)" }
}).ExecuteNonQuery();

Declaration

public class Customer : DynamicModel
{
    Suppliers suppliers = new Suppliers();
    DistributionChannels distributionChannel = new DistributionChannels();

    public Customer(object dto) : base(dto) { }

    public IEnumerable<dynamic> Associates()
    {
        yield return new HasOneThrough(suppliers, through: distributionChannel);
    }
}

Methods Available

Customers customers = new Customers();
dynamic customer = customers.Single(/* some id */);
customer.Supplier();

Sql and Overrides

HasOneThrough has a number of overrides, to control how the association queries for data. The overrides are provided as auto properties.

Here are the overrides for HasOneThrough:

  • MethodName: changes the name of the association method

Sample usage:

public IEnumerable<dynamic> Associates()
{
    yield return new HasOneThrough(suppliers, through: distributionChannel)
    {
        MethodName = "TheSuppliers"
    };
}

//instead of 
user.Suppliers()

//the method will now be called
course.TheSuppliers()

Here is the sql that will be generated for HasOneThrough:

//returns the first row (or null) of:
select {toTable}.*, {xRefTable}.{xRefFromColumn}
from {xRefTable}
inner join {toTable}
on {xRefTable}.{xRefToColumn} = {toTable}.{toTableColumn}
where {xRefTable}.{xRefFromColumn} in ({inClause})

Overrides to change what columns and values are used:

  • Repository.TableName: changes the {toTable} value, the first parameter in the constructor for HasOneThrough
  • through.TableName: changes the {xRefTable} value, the second parameter in the constructor for HasOneThrough
  • XRefToColumn: changes the {xRefToColumn} value
  • XRefFromColumn: changes the {xRefFromColumn} value
  • ToTableColumn: changes the {toTableColumn} value
  • PropertyContainingIdValue: where to retrieve the value for the {inClause}

Eager Loading

Eager loading is provided on all enumerable that exist on a dynamic model. Eager loading is performed through the Include method. Here are some examples of how to eager load.

Eager loading an association where a Blog has many Comments:

dynamic blogs = new Blogs(); //this is a class of type DynamicRepository
dynamic allBlogs = blogs.All().Include("Comments"); //at this point all comments for all blogs have been eagerloaded

Eager loading an assocation where a Comment belongs to a Blog:

dynamic comments = new Comments(); //this is a class of type DynamicRepository
dynamic allComments = comments.All().Include("Blog"); //at this point Blogs have been eager loaded into comment references

Eager loading multiple associations. In this example a Screencast has many Presenters and Tags:

dynamic screencasts = new Screencasts();
dynamic allScreencasts = screencasts.All().Include("Presenters", "Tags");
Something went wrong with that request. Please try again.