diff --git a/samples/.gitignore b/samples/.gitignore
new file mode 100644
index 0000000..52afd30
--- /dev/null
+++ b/samples/.gitignore
@@ -0,0 +1,349 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
\ No newline at end of file
diff --git a/samples/AssociationsSample/AssociationsSample.csproj b/samples/AssociationsSample/AssociationsSample.csproj
new file mode 100644
index 0000000..c9aed3b
--- /dev/null
+++ b/samples/AssociationsSample/AssociationsSample.csproj
@@ -0,0 +1,35 @@
+
+
+
+ Exe
+ netcoreapp2.0
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ TextTemplatingFileGenerator
+ Northwind.generated.cs
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Northwind.tt
+
+
+
+
diff --git a/samples/AssociationsSample/Data/Northwind.sqlite b/samples/AssociationsSample/Data/Northwind.sqlite
new file mode 100644
index 0000000..52862af
Binary files /dev/null and b/samples/AssociationsSample/Data/Northwind.sqlite differ
diff --git a/samples/AssociationsSample/Models/Northwind.Associations.cs b/samples/AssociationsSample/Models/Northwind.Associations.cs
new file mode 100644
index 0000000..9157cda
--- /dev/null
+++ b/samples/AssociationsSample/Models/Northwind.Associations.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using LinqToDB.Mapping;
+
+namespace AssociationsSample.Models
+{
+ public partial class EmployeeTerritory
+ {
+ [Association(ThisKey = nameof(EmployeeID), OtherKey = nameof(Models.Employee.EmployeeID), CanBeNull = false)]
+ public Employee Employee { get; set; }
+
+ [Association(ThisKey = nameof(TerritoryID), OtherKey = nameof(Models.Territory.TerritoryID), CanBeNull = false)]
+ public Territory Territory { get; set; }
+ }
+
+ public partial class Order
+ {
+ [Association(ThisKey = nameof(EmployeeID), OtherKey = nameof(Models.Employee.EmployeeID), CanBeNull = true)]
+ public Employee Employee { get; set; }
+
+ [Association(ThisKey = nameof(OrderID), OtherKey = nameof(OrderDetail.OrderID))]
+ public IEnumerable Details { get; set; }
+
+ [Association(ExpressionPredicate = nameof(DetailsWithBigDiscountFilter))]
+ public IEnumerable DetailsWithBigDiscount { get; set; }
+
+ private static Expression> DetailsWithBigDiscountFilter()
+ {
+ return (order, detail) => order.OrderID == detail.OrderID && detail.Discount > 0.06;
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/AssociationsSample/Models/Northwind.generated.cs b/samples/AssociationsSample/Models/Northwind.generated.cs
new file mode 100644
index 0000000..6d5a535
--- /dev/null
+++ b/samples/AssociationsSample/Models/Northwind.generated.cs
@@ -0,0 +1,411 @@
+//---------------------------------------------------------------------------------------------------
+//
+// This code was generated by T4Model template for T4 (https://github.com/linq2db/linq2db).
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//---------------------------------------------------------------------------------------------------
+using System;
+using System.Linq;
+
+using LinqToDB;
+using LinqToDB.Mapping;
+
+namespace AssociationsSample.Models
+{
+ ///
+ /// Database : Northwind
+ /// Data Source : Northwind
+ /// Server Version : 3.19.3
+ ///
+ public partial class NorthwindDB : LinqToDB.Data.DataConnection
+ {
+ public ITable AlphabeticalListOfProducts { get { return this.GetTable(); } }
+ public ITable Categories { get { return this.GetTable(); } }
+ public ITable CurrentProductLists { get { return this.GetTable(); } }
+ public ITable Customers { get { return this.GetTable(); } }
+ public ITable CustomerAndSuppliersByCities { get { return this.GetTable(); } }
+ public ITable CustomerCustomerDemoes { get { return this.GetTable(); } }
+ public ITable CustomerDemographics { get { return this.GetTable(); } }
+ public ITable Employees { get { return this.GetTable(); } }
+ public ITable EmployeeTerritories { get { return this.GetTable(); } }
+ public ITable Orders { get { return this.GetTable(); } }
+ public ITable OrderDetails { get { return this.GetTable(); } }
+ public ITable OrderDetailsExtendeds { get { return this.GetTable(); } }
+ public ITable OrdersQries { get { return this.GetTable(); } }
+ public ITable OrderSubtotals { get { return this.GetTable(); } }
+ public ITable Products { get { return this.GetTable(); } }
+ public ITable ProductsAboveAveragePrices { get { return this.GetTable(); } }
+ public ITable ProductsByCategories { get { return this.GetTable(); } }
+ public ITable Regions { get { return this.GetTable(); } }
+ public ITable Shippers { get { return this.GetTable(); } }
+ public ITable SummaryOfSalesByQuarters { get { return this.GetTable(); } }
+ public ITable SummaryOfSalesByYears { get { return this.GetTable(); } }
+ public ITable Suppliers { get { return this.GetTable(); } }
+ public ITable Territories { get { return this.GetTable(); } }
+
+ public void InitMappingSchema()
+ {
+ }
+
+ public NorthwindDB()
+ {
+ InitDataContext();
+ InitMappingSchema();
+ }
+
+ public NorthwindDB(string configuration)
+ : base(configuration)
+ {
+ InitDataContext();
+ InitMappingSchema();
+ }
+
+ partial void InitDataContext();
+ }
+
+ [Table("Alphabetical list of products", IsView=true)]
+ public partial class AlphabeticalListOfProduct
+ {
+ [Column, NotNull ] public int ProductID { get; set; } // int
+ [Column, NotNull ] public string ProductName { get; set; } // varchar(40)
+ [Column, Nullable] public int? SupplierID { get; set; } // int
+ [Column, Nullable] public int? CategoryID { get; set; } // int
+ [Column, Nullable] public string QuantityPerUnit { get; set; } // varchar(20)
+ [Column, Nullable] public double? UnitPrice { get; set; } // float
+ [Column, Nullable] public int? UnitsInStock { get; set; } // int
+ [Column, Nullable] public int? UnitsOnOrder { get; set; } // int
+ [Column, Nullable] public int? ReorderLevel { get; set; } // int
+ [Column, NotNull ] public int Discontinued { get; set; } // int
+ [Column, NotNull ] public string CategoryName { get; set; } // varchar(15)
+ }
+
+ [Table("Categories")]
+ public partial class Category
+ {
+ [PrimaryKey, NotNull ] public int CategoryID { get; set; } // int
+ [Column, NotNull ] public string CategoryName { get; set; } // varchar(15)
+ [Column, Nullable] public string Description { get; set; } // text(max)
+ [Column, Nullable] public byte[] Picture { get; set; } // blob
+ }
+
+ [Table("Current Product List", IsView=true)]
+ public partial class CurrentProductList
+ {
+ [Column, NotNull] public int ProductID { get; set; } // int
+ [Column, NotNull] public string ProductName { get; set; } // varchar(40)
+ }
+
+ [Table("Customers")]
+ public partial class Customer
+ {
+ [PrimaryKey, NotNull ] public string CustomerID { get; set; } // varchar(5)
+ [Column, NotNull ] public string CompanyName { get; set; } // varchar(40)
+ [Column, Nullable] public string ContactName { get; set; } // varchar(30)
+ [Column, Nullable] public string ContactTitle { get; set; } // varchar(30)
+ [Column, Nullable] public string Address { get; set; } // varchar(60)
+ [Column, Nullable] public string City { get; set; } // varchar(15)
+ [Column, Nullable] public string Region { get; set; } // varchar(15)
+ [Column, Nullable] public string PostalCode { get; set; } // varchar(10)
+ [Column, Nullable] public string Country { get; set; } // varchar(15)
+ [Column, Nullable] public string Phone { get; set; } // varchar(24)
+ [Column, Nullable] public string Fax { get; set; } // varchar(24)
+ }
+
+ [Table("Customer and Suppliers by City", IsView=true)]
+ public partial class CustomerAndSuppliersByCity
+ {
+ [Column, Nullable] public string City { get; set; } // varchar(15)
+ [Column, NotNull ] public string CompanyName { get; set; } // varchar(40)
+ [Column, Nullable] public string ContactName { get; set; } // varchar(30)
+ [Column, Nullable] public object Relationship { get; set; }
+ }
+
+ [Table("CustomerCustomerDemo")]
+ public partial class CustomerCustomerDemo
+ {
+ [PrimaryKey(0), NotNull] public string CustomerID { get; set; } // varchar(5)
+ [PrimaryKey(1), NotNull] public string CustomerTypeID { get; set; } // varchar(10)
+ }
+
+ [Table("CustomerDemographics")]
+ public partial class CustomerDemographic
+ {
+ [PrimaryKey, NotNull ] public string CustomerTypeID { get; set; } // varchar(10)
+ [Column, Nullable] public string CustomerDesc { get; set; } // text(max)
+ }
+
+ [Table("Employees")]
+ public partial class Employee
+ {
+ [PrimaryKey, NotNull ] public int EmployeeID { get; set; } // int
+ [Column, NotNull ] public string LastName { get; set; } // varchar(20)
+ [Column, NotNull ] public string FirstName { get; set; } // varchar(10)
+ [Column, Nullable] public string Title { get; set; } // varchar(30)
+ [Column, Nullable] public string TitleOfCourtesy { get; set; } // varchar(25)
+ [Column(SkipOnInsert=true, SkipOnUpdate=true), Nullable] public DateTime? BirthDate { get; set; } // timestamp
+ [Column(SkipOnInsert=true, SkipOnUpdate=true), Nullable] public DateTime? HireDate { get; set; } // timestamp
+ [Column, Nullable] public string Address { get; set; } // varchar(60)
+ [Column, Nullable] public string City { get; set; } // varchar(15)
+ [Column, Nullable] public string Region { get; set; } // varchar(15)
+ [Column, Nullable] public string PostalCode { get; set; } // varchar(10)
+ [Column, Nullable] public string Country { get; set; } // varchar(15)
+ [Column, Nullable] public string HomePhone { get; set; } // varchar(24)
+ [Column, Nullable] public string Extension { get; set; } // varchar(4)
+ [Column, Nullable] public byte[] Photo { get; set; } // blob
+ [Column, Nullable] public string Notes { get; set; } // text(max)
+ [Column, Nullable] public int? ReportsTo { get; set; } // int
+ [Column, Nullable] public string PhotoPath { get; set; } // varchar(255)
+ }
+
+ [Table("EmployeeTerritories")]
+ public partial class EmployeeTerritory
+ {
+ [PrimaryKey(0), NotNull] public int EmployeeID { get; set; } // int
+ [PrimaryKey(1), NotNull] public string TerritoryID { get; set; } // varchar(20)
+ }
+
+ [Table("Orders")]
+ public partial class Order
+ {
+ [PrimaryKey, NotNull ] public int OrderID { get; set; } // int
+ [Column, Nullable] public string CustomerID { get; set; } // varchar(5)
+ [Column, Nullable] public int? EmployeeID { get; set; } // int
+ [Column(SkipOnInsert=true, SkipOnUpdate=true), Nullable] public DateTime? OrderDate { get; set; } // timestamp
+ [Column(SkipOnInsert=true, SkipOnUpdate=true), Nullable] public DateTime? RequiredDate { get; set; } // timestamp
+ [Column(SkipOnInsert=true, SkipOnUpdate=true), Nullable] public DateTime? ShippedDate { get; set; } // timestamp
+ [Column, Nullable] public int? ShipVia { get; set; } // int
+ [Column, Nullable] public double? Freight { get; set; } // float
+ [Column, Nullable] public string ShipName { get; set; } // varchar(40)
+ [Column, Nullable] public string ShipAddress { get; set; } // varchar(60)
+ [Column, Nullable] public string ShipCity { get; set; } // varchar(15)
+ [Column, Nullable] public string ShipRegion { get; set; } // varchar(15)
+ [Column, Nullable] public string ShipPostalCode { get; set; } // varchar(10)
+ [Column, Nullable] public string ShipCountry { get; set; } // varchar(15)
+ }
+
+ [Table("Order Details")]
+ public partial class OrderDetail
+ {
+ [PrimaryKey(0), NotNull ] public int OrderID { get; set; } // int
+ [PrimaryKey(1), NotNull ] public int ProductID { get; set; } // int
+ [Column, Nullable] public double? UnitPrice { get; set; } // float
+ [Column, Nullable] public int? Quantity { get; set; } // int
+ [Column, Nullable] public double? Discount { get; set; } // float
+ }
+
+ [Table("Order Details Extended", IsView=true)]
+ public partial class OrderDetailsExtended
+ {
+ [Column, NotNull ] public int OrderID { get; set; } // int
+ [Column, NotNull ] public int ProductID { get; set; } // int
+ [Column, NotNull ] public string ProductName { get; set; } // varchar(40)
+ [Column, Nullable] public double? UnitPrice { get; set; } // float
+ [Column, Nullable] public int? Quantity { get; set; } // int
+ [Column, Nullable] public double? Discount { get; set; } // float
+ [Column, Nullable] public object ExtendedPrice { get; set; }
+ }
+
+ [Table("Orders Qry", IsView=true)]
+ public partial class OrdersQry
+ {
+ [Column, NotNull ] public int OrderID { get; set; } // int
+ [Column, Nullable] public string CustomerID { get; set; } // varchar(5)
+ [Column, Nullable] public int? EmployeeID { get; set; } // int
+ [Column(SkipOnInsert=true, SkipOnUpdate=true), Nullable] public DateTime? OrderDate { get; set; } // timestamp
+ [Column(SkipOnInsert=true, SkipOnUpdate=true), Nullable] public DateTime? RequiredDate { get; set; } // timestamp
+ [Column(SkipOnInsert=true, SkipOnUpdate=true), Nullable] public DateTime? ShippedDate { get; set; } // timestamp
+ [Column, Nullable] public int? ShipVia { get; set; } // int
+ [Column, Nullable] public double? Freight { get; set; } // float
+ [Column, Nullable] public string ShipName { get; set; } // varchar(40)
+ [Column, Nullable] public string ShipAddress { get; set; } // varchar(60)
+ [Column, Nullable] public string ShipCity { get; set; } // varchar(15)
+ [Column, Nullable] public string ShipRegion { get; set; } // varchar(15)
+ [Column, Nullable] public string ShipPostalCode { get; set; } // varchar(10)
+ [Column, Nullable] public string ShipCountry { get; set; } // varchar(15)
+ [Column, NotNull ] public string CompanyName { get; set; } // varchar(40)
+ [Column, Nullable] public string Address { get; set; } // varchar(60)
+ [Column, Nullable] public string City { get; set; } // varchar(15)
+ [Column, Nullable] public string Region { get; set; } // varchar(15)
+ [Column, Nullable] public string PostalCode { get; set; } // varchar(10)
+ [Column, Nullable] public string Country { get; set; } // varchar(15)
+ }
+
+ [Table("Order Subtotals", IsView=true)]
+ public partial class OrderSubtotal
+ {
+ [Column, NotNull ] public int OrderID { get; set; } // int
+ [Column, Nullable] public object Subtotal { get; set; }
+ }
+
+ [Table("Products")]
+ public partial class Product
+ {
+ [PrimaryKey, NotNull ] public int ProductID { get; set; } // int
+ [Column, NotNull ] public string ProductName { get; set; } // varchar(40)
+ [Column, Nullable] public int? SupplierID { get; set; } // int
+ [Column, Nullable] public int? CategoryID { get; set; } // int
+ [Column, Nullable] public string QuantityPerUnit { get; set; } // varchar(20)
+ [Column, Nullable] public double? UnitPrice { get; set; } // float
+ [Column, Nullable] public int? UnitsInStock { get; set; } // int
+ [Column, Nullable] public int? UnitsOnOrder { get; set; } // int
+ [Column, Nullable] public int? ReorderLevel { get; set; } // int
+ [Column, NotNull ] public int Discontinued { get; set; } // int
+ }
+
+ [Table("Products Above Average Price", IsView=true)]
+ public partial class ProductsAboveAveragePrice
+ {
+ [Column, NotNull ] public string ProductName { get; set; } // varchar(40)
+ [Column, Nullable] public double? UnitPrice { get; set; } // float
+ }
+
+ [Table("Products by Category", IsView=true)]
+ public partial class ProductsByCategory
+ {
+ [Column, NotNull ] public string CategoryName { get; set; } // varchar(15)
+ [Column, NotNull ] public string ProductName { get; set; } // varchar(40)
+ [Column, Nullable] public string QuantityPerUnit { get; set; } // varchar(20)
+ [Column, Nullable] public int? UnitsInStock { get; set; } // int
+ [Column, NotNull ] public int Discontinued { get; set; } // int
+ }
+
+ [Table("Region")]
+ public partial class Region
+ {
+ [PrimaryKey, NotNull] public int RegionID { get; set; } // int
+ [Column, NotNull] public string RegionDescription { get; set; } // varchar(50)
+ }
+
+ [Table("Shippers")]
+ public partial class Shipper
+ {
+ [PrimaryKey, NotNull ] public int ShipperID { get; set; } // int
+ [Column, NotNull ] public string CompanyName { get; set; } // varchar(40)
+ [Column, Nullable] public string Phone { get; set; } // varchar(24)
+ }
+
+ [Table("Summary of Sales by Quarter", IsView=true)]
+ public partial class SummaryOfSalesByQuarter
+ {
+ [Column(SkipOnInsert=true, SkipOnUpdate=true), Nullable] public DateTime? ShippedDate { get; set; } // timestamp
+ [Column, NotNull ] public int OrderID { get; set; } // int
+ [Column, Nullable] public object Subtotal { get; set; }
+ }
+
+ [Table("Summary of Sales by Year", IsView=true)]
+ public partial class SummaryOfSalesByYear
+ {
+ [Column(SkipOnInsert=true, SkipOnUpdate=true), Nullable] public DateTime? ShippedDate { get; set; } // timestamp
+ [Column, NotNull ] public int OrderID { get; set; } // int
+ [Column, Nullable] public object Subtotal { get; set; }
+ }
+
+ [Table("Suppliers")]
+ public partial class Supplier
+ {
+ [PrimaryKey, NotNull ] public int SupplierID { get; set; } // int
+ [Column, NotNull ] public string CompanyName { get; set; } // varchar(40)
+ [Column, Nullable] public string ContactName { get; set; } // varchar(30)
+ [Column, Nullable] public string ContactTitle { get; set; } // varchar(30)
+ [Column, Nullable] public string Address { get; set; } // varchar(60)
+ [Column, Nullable] public string City { get; set; } // varchar(15)
+ [Column, Nullable] public string Region { get; set; } // varchar(15)
+ [Column, Nullable] public string PostalCode { get; set; } // varchar(10)
+ [Column, Nullable] public string Country { get; set; } // varchar(15)
+ [Column, Nullable] public string Phone { get; set; } // varchar(24)
+ [Column, Nullable] public string Fax { get; set; } // varchar(24)
+ [Column, Nullable] public string HomePage { get; set; } // text(max)
+ }
+
+ [Table("Territories")]
+ public partial class Territory
+ {
+ [PrimaryKey, NotNull] public string TerritoryID { get; set; } // varchar(20)
+ [Column, NotNull] public string TerritoryDescription { get; set; } // varchar(50)
+ [Column, NotNull] public int RegionID { get; set; } // int
+ }
+
+ public static partial class TableExtensions
+ {
+ public static Category Find(this ITable table, int CategoryID)
+ {
+ return table.FirstOrDefault(t =>
+ t.CategoryID == CategoryID);
+ }
+
+ public static Customer Find(this ITable table, string CustomerID)
+ {
+ return table.FirstOrDefault(t =>
+ t.CustomerID == CustomerID);
+ }
+
+ public static CustomerCustomerDemo Find(this ITable table, string CustomerID, string CustomerTypeID)
+ {
+ return table.FirstOrDefault(t =>
+ t.CustomerID == CustomerID &&
+ t.CustomerTypeID == CustomerTypeID);
+ }
+
+ public static CustomerDemographic Find(this ITable table, string CustomerTypeID)
+ {
+ return table.FirstOrDefault(t =>
+ t.CustomerTypeID == CustomerTypeID);
+ }
+
+ public static Employee Find(this ITable table, int EmployeeID)
+ {
+ return table.FirstOrDefault(t =>
+ t.EmployeeID == EmployeeID);
+ }
+
+ public static EmployeeTerritory Find(this ITable table, int EmployeeID, string TerritoryID)
+ {
+ return table.FirstOrDefault(t =>
+ t.EmployeeID == EmployeeID &&
+ t.TerritoryID == TerritoryID);
+ }
+
+ public static Order Find(this ITable table, int OrderID)
+ {
+ return table.FirstOrDefault(t =>
+ t.OrderID == OrderID);
+ }
+
+ public static OrderDetail Find(this ITable table, int OrderID, int ProductID)
+ {
+ return table.FirstOrDefault(t =>
+ t.OrderID == OrderID &&
+ t.ProductID == ProductID);
+ }
+
+ public static Product Find(this ITable table, int ProductID)
+ {
+ return table.FirstOrDefault(t =>
+ t.ProductID == ProductID);
+ }
+
+ public static Region Find(this ITable table, int RegionID)
+ {
+ return table.FirstOrDefault(t =>
+ t.RegionID == RegionID);
+ }
+
+ public static Shipper Find(this ITable table, int ShipperID)
+ {
+ return table.FirstOrDefault(t =>
+ t.ShipperID == ShipperID);
+ }
+
+ public static Supplier Find(this ITable table, int SupplierID)
+ {
+ return table.FirstOrDefault(t =>
+ t.SupplierID == SupplierID);
+ }
+
+ public static Territory Find(this ITable table, string TerritoryID)
+ {
+ return table.FirstOrDefault(t =>
+ t.TerritoryID == TerritoryID);
+ }
+ }
+}
diff --git a/samples/AssociationsSample/Models/Northwind.tt b/samples/AssociationsSample/Models/Northwind.tt
new file mode 100644
index 0000000..d83f18d
--- /dev/null
+++ b/samples/AssociationsSample/Models/Northwind.tt
@@ -0,0 +1,15 @@
+<#@ template language="C#" debug="True" hostSpecific="True" #>
+<#@ output extension=".generated.cs" #>
+<#@ include file="$(LinqToDBT4SQLiteTemplatesDirectory)LinqToDB.SQLite.Tools.ttinclude" #>
+<#@ include file="$(LinqToDBT4SQLiteTemplatesDirectory)PluralizationService.ttinclude" #>
+<#
+
+ // Follow the link to examine more options: https://github.com/linq2db/linq2db/tree/master/Source/LinqToDB.Templates#t4-models
+
+ NamespaceName = "AssociationsSample.Models";
+
+ var path = Host.ResolvePath(@"..\Data");
+ LoadSQLiteMetadata(path, "Northwind.sqlite");
+
+ GenerateModel();
+#>
diff --git a/samples/AssociationsSample/Models/NorthwindExtensions.cs b/samples/AssociationsSample/Models/NorthwindExtensions.cs
new file mode 100644
index 0000000..2c4f743
--- /dev/null
+++ b/samples/AssociationsSample/Models/NorthwindExtensions.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using LinqToDB;
+using LinqToDB.Mapping;
+
+namespace AssociationsSample.Models
+{
+ public static class NorthwindExtensions
+ {
+ [Association(ThisKey = nameof(Order.EmployeeID), OtherKey = nameof(Models.Employee.EmployeeID), CanBeNull = true)]
+ public static Employee Employee(this Order order)
+ {
+ throw new InvalidOperationException("Used only as Association helper");
+ }
+
+ [Association(ThisKey = nameof(Order.OrderID), OtherKey = nameof(OrderDetail.OrderID))]
+ public static IEnumerable Details(this Order order)
+ {
+ throw new InvalidOperationException("Used only as Association helper");
+ }
+
+ [Association(ThisKey = nameof(Order.OrderID), OtherKey = nameof(OrderDetail.OrderID))]
+ public static IQueryable DetailsQuery(this Order order, IDataContext db)
+ {
+ return db.GetTable().Where(d => d.OrderID == order.OrderID);
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/AssociationsSample/Program.cs b/samples/AssociationsSample/Program.cs
new file mode 100644
index 0000000..15c87e6
--- /dev/null
+++ b/samples/AssociationsSample/Program.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using AssociationsSample.Models;
+using LinqToDB;
+using LinqToDB.Configuration;
+using LinqToDB.Data;
+
+namespace AssociationsSample
+{
+ class Program
+ {
+ class ConnectionStringSettings : IConnectionStringSettings
+ {
+ public string ConnectionString { get; set; }
+ public string Name { get; set; }
+ public string ProviderName { get; set; }
+ public bool IsGlobal { get; }
+ }
+
+ public class MySettings : ILinqToDBSettings
+ {
+ public IEnumerable DataProviders => Enumerable.Empty();
+
+ public string DefaultConfiguration => "Northiwnd";
+ public string DefaultDataProvider => "System.Data.SQLite";
+
+ public IEnumerable ConnectionStrings
+ {
+ get
+ {
+ yield return
+ new ConnectionStringSettings
+ {
+ Name = "Northwind",
+ ProviderName = "System.Data.SQLite",
+ ConnectionString = @"Data Source=..\..\..\Data\Northwind.sqlite"
+ };
+ }
+ }
+ }
+
+ public static void RetrieveOrderDetails()
+ {
+ using (var db = new NorthwindDB())
+ {
+ var query = from order in db.Orders
+ where order.Details.Any(d => d.Discount > 0.06)
+ select new
+ {
+ order.EmployeeID,
+ MaxDiscount = order.Details.Max(d => d.Discount)
+ };
+
+ query = query.Take(10);
+
+ foreach (var r in query)
+ {
+ Console.WriteLine(r);
+ }
+ }
+ }
+
+ public static void RetrieveOrderDetailsWithBigDiscount()
+ {
+ using (var db = new NorthwindDB())
+ {
+ var query = from order in db.Orders
+ where order.DetailsWithBigDiscount.Any()
+ select new
+ {
+ order.EmployeeID,
+ MaxDiscount = order.DetailsWithBigDiscount.Max(d => d.Discount)
+ };
+
+ query = query.Take(10);
+
+ foreach (var r in query)
+ {
+ Console.WriteLine(r);
+ }
+ }
+ }
+
+ public static void RetrieveOrderInformation()
+ {
+ using (var db = new NorthwindDB())
+ {
+ var query = from order in db.Orders
+ where order.Employee.Address.StartsWith("B")
+ select new
+ {
+ order.OrderID,
+ order.OrderDate,
+ order.Employee.Address,
+ };
+
+ query = query.Take(10);
+
+ foreach (var r in query)
+ {
+ Console.WriteLine(r);
+ }
+ }
+ }
+
+ static void Main(string[] args)
+ {
+ DataConnection.DefaultSettings = new MySettings();
+ DataConnection.TurnTraceSwitchOn();
+ DataConnection.WriteTraceLine = (s, _) =>
+ {
+ Console.WriteLine(s);
+ };
+
+ RetrieveOrderInformation();
+ RetrieveTerritoryLinks();
+ RetrieveOrderDetails();
+ RetrieveOrderDetailsWithBigDiscount();
+ Console.ReadLine();
+ }
+
+ private static void RetrieveTerritoryLinks()
+ {
+ using (var db = new NorthwindDB())
+ {
+ var query = from et in db.EmployeeTerritories
+ where et.Territory.TerritoryDescription.StartsWith("B")
+ select new
+ {
+ et.Employee.EmployeeID,
+ et.Employee.BirthDate,
+ Territory = et.Territory.TerritoryDescription.Trim(),
+ et.Employee.Address,
+ };
+
+ var result = query.ToArray();
+
+ foreach (var employee in result)
+ {
+ Console.WriteLine(employee);
+ }
+ }
+ }
+ }
+}
diff --git a/samples/LinqToDBSamples.sln b/samples/LinqToDBSamples.sln
new file mode 100644
index 0000000..26407b0
--- /dev/null
+++ b/samples/LinqToDBSamples.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27703.2042
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssociationsSample", "AssociationsSample\AssociationsSample.csproj", "{759C48AB-F4A3-463C-8C4E-344DD315A96E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {759C48AB-F4A3-463C-8C4E-344DD315A96E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {759C48AB-F4A3-463C-8C4E-344DD315A96E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {759C48AB-F4A3-463C-8C4E-344DD315A96E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {759C48AB-F4A3-463C-8C4E-344DD315A96E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {319B8FA3-9C12-4912-9865-2FA55AEE2067}
+ EndGlobalSection
+EndGlobal
diff --git a/source/articles/model/associations.md b/source/articles/model/associations.md
new file mode 100644
index 0000000..01c065a
--- /dev/null
+++ b/source/articles/model/associations.md
@@ -0,0 +1,163 @@
+---
+title: Associations in LINQ To DB
+author: sdanyliv
+---
+# Associations in LINQ To DB
+
+Association defines how entities relate to each other. It can be representation of foreign key constraint or custom query which can be automaticaly handled by `LINQ To DB`.
+
+Associations is powerful mechanism in `LINQ To DB`, not so limited as in vanilla frameworks. Since `LINQ To DB` do not track changes we can express any idea using associations to simplify writing of LINQ queies.
+
+## In this article
+
+[One-to-one associations](#one-to-one-associations)
+
+[One-to-many associations](#one-to-many-associations)
+
+[Associations as extension methods](#associations-as-extension-methods)
+
+## One-To-One Associations
+
+It is simple relation between two entities and can be defined using `AssociationAttribute`.
+In example below we show how to define optional one-to-one association between `Order` and `Employee` entities.
+
+```cs
+public partial class Order
+{
+ [Association(
+ ThisKey = nameof(EmployeeID),
+ OtherKey = nameof(Models.Employee.EmployeeID),
+ CanBeNull = true)]
+ public Employee Employee { get; set; }
+}
+
+```
+
+```cs
+var query = from order in db.Orders
+ where order.Employee.Address.StartsWith("B")
+ select new
+ {
+ order.OrderID,
+ order.OrderDate,
+ order.Employee.Address,
+ };
+```
+
+`ThisKey` and `OtherKey` contain comma-separated names of members of appropriate entities, used in join condition. `CanBeNull = true` means that relation is optional and forces engine to join entities using `LEFT JOIN` instead of `INNER JOIN` for non-optional relations.
+
+## One-To-Many Associations
+
+This association simplifies queries from `Main` entity to `Related` entities. Usually it is a property with `IEnumerable` type, which can be also of `List` or `T[]` type.
+
+```cs
+public partial class Order
+{
+ [Association(
+ ThisKey = nameof(OrderID),
+ OtherKey = nameof(OrderDetail.OrderID))]
+ public IEnumerable Details { get; set; }
+}
+```
+
+It helps to write queries like that:
+
+```cs
+ var query = from order in db.Orders
+ where order.Details.Any(d => d.Discount > 0.06)
+ select new
+ {
+ order.EmployeeID,
+ MaxDicount = order.Details.Max(d => d.Discount)
+ };
+
+```
+
+Except joins on specified set of key-fields, you can also define custom join condition. You need to define static function which returns `Expression`-typed join predicate. It should be expression function with two parameters `Main` and `Related` and return `bool` result.
+
+```cs
+public partial class Order
+{
+ [Association(ExpressionPredicate = nameof(DetailsWithBigDiscountFilter))]
+ public IEnumerable DetailsWithBigDiscount { get; set; }
+
+ private static Expression> DetailsWithBigDiscountFilter()
+ {
+ return (order, detail) => order.OrderID == detail.OrderID && detail.Discount > 0.06;
+ }
+}
+```
+
+In this example `ThisKey` and `OtherKey` were replaced with custom predicate, but if they are defined, predicate will be combined with keys using `AND` condition.
+
+Now query from previous example could be simplified using new association:
+
+```cs
+var query = from order in db.Orders
+ where order.DetailsWithBigDiscount.Any()
+ select new
+ {
+ order.EmployeeID,
+ MaxDiscount = order.DetailsWithBigDiscount.Max(d => d.Discount)
+ };
+```
+
+## Associations as Extension Methods
+
+There are situations when changing model is not allowed or it is located in separate library, so you cannot add assiciations to model class itself.
+For this case `LINQ To DB` has ability to define associations as extension methods.
+Previous examples can be rewritten to use extension methods:
+
+```cs
+public static class NorthwindExtensions
+{
+ [Association(
+ ThisKey = nameof(Order.EmployeeID),
+ OtherKey = nameof(Models.Employee.EmployeeID),
+ CanBeNull = true)]
+ public static Employee Employee(this Order order)
+ {
+ throw new InvalidOperationException("Called outside of query");
+ }
+
+ [Association(
+ ThisKey = nameof(Order.OrderID),
+ OtherKey = nameof(OrderDetail.OrderID))]
+ public static IEnumerable Details(this Order order)
+ {
+ throw new InvalidOperationException("Called outside of query");
+ }
+}
+```
+
+```cs
+var query = from order in db.Orders
+ where order.Details().Any(d => d.Discount > 0.06)
+ select new
+ {
+ order.EmployeeID,
+ MaxDicount = order.Details().Max(d => d.Discount)
+ };
+```
+
+As we can see from extension methods implemenation - they do not support execution and should be used only as declarative methods in linq query.
+But there are cases when it is required to query details exactly from materialized object.
+
+For such cases you can define additional parameter in extension method of `IDataContext`-based type, e.g. `IDataContext`, `DataConnection` or `NotrthwindDB` from this article.
+
+Extension method should return `IQueryable` interface.
+
+```cs
+public static class NorthwindExtensions
+{
+ [Association(
+ ThisKey = nameof(Order.OrderID),
+ OtherKey = nameof(OrderDetail.OrderID))]
+ public static IQueryable DetailsQuery(this Order order, IDataContext db)
+ {
+ return db.GetTable().Where(d => d.OrderID == order.OrderID);
+ }
+}
+```
+
+The same techinque can be applied to entity methods. (**TODO:clarify what it means**)
diff --git a/source/articles/model/toc.yml b/source/articles/model/toc.yml
new file mode 100644
index 0000000..3f468f9
--- /dev/null
+++ b/source/articles/model/toc.yml
@@ -0,0 +1,2 @@
+- name: Associations
+ href: associations.md
\ No newline at end of file
diff --git a/source/articles/toc.yml b/source/articles/toc.yml
index fe95708..f7b80b3 100644
--- a/source/articles/toc.yml
+++ b/source/articles/toc.yml
@@ -4,6 +4,8 @@
href: get-started/toc.yml
- name: General topics
href: general/toc.yml
+- name: Database Model
+ href: model/toc.yml
- name: SQL
href: sql/toc.yml
- name: T4 Templates