diff --git a/source/articles/FAQ.md b/source/articles/FAQ.md
index 7c3d31d..dedeb6d 100644
--- a/source/articles/FAQ.md
+++ b/source/articles/FAQ.md
@@ -30,43 +30,43 @@ Configure connection on creation/open (SQL Server and SQLite examples):
```cs
public class MySqlServerDb : DataConnection // or DataContext
{
- public MySqlServerDb(connectionString) : base(
- new DataOptions()
- .UseSqlServer(connectionString)
- .UseBeforeConnectionOpened(connection =>
- {
- connection.AccessToken = "..token here..";
- }))
- {
- }
+ public MySqlServerDb(connectionString) : base(
+ new DataOptions()
+ .UseSqlServer(connectionString)
+ .UseBeforeConnectionOpened(connection =>
+ {
+ connection.AccessToken = "..token here..";
+ }))
+ {
+ }
}
public class MySQLiteDb : DataConnection // or DataContext
{
- public MySQLiteDb(connectionString) : base(
- new DataOptions()
- .UseSQLite(connectionString)
- .UseAfterConnectionOpened(
- connection =>
- {
- using var cmd = connection.CreateCommand();
- cmd.CommandText = $"PRAGMA KEY '{key}'";
- cmd.ExecuteNonQuery();
- },
- // optionally add async version to use non-blocking calls from async execution path
- async (connection, cancellationToken) =>
- {
- using var cmd = connection.CreateCommand();
- cmd.CommandText = $"PRAGMA KEY '{key}'";
- await cmd.ExecuteNonQueryAsync(cancellationToken);
- }))
- {
- }
+ public MySQLiteDb(connectionString) : base(
+ new DataOptions()
+ .UseSQLite(connectionString)
+ .UseAfterConnectionOpened(
+ connection =>
+ {
+ using var cmd = connection.CreateCommand();
+ cmd.CommandText = $"PRAGMA KEY '{key}'";
+ cmd.ExecuteNonQuery();
+ },
+ // optionally add async version to use non-blocking calls from async execution path
+ async (connection, cancellationToken) =>
+ {
+ using var cmd = connection.CreateCommand();
+ cmd.CommandText = $"PRAGMA KEY '{key}'";
+ await cmd.ExecuteNonQueryAsync(cancellationToken);
+ }))
+ {
+ }
}
using (var db = new MySqlServerDb())
{
- // queries here will get pre-configured connection
+ // queries here will get pre-configured connection
}
```
@@ -119,12 +119,12 @@ For .NET Framework you just need to add assembly bindings redirect to your confi
```xml
-
+
-
-
+
+
-
+
```
@@ -141,27 +141,27 @@ AssemblyLoadContext.Default.Resolving += OnAssemblyResolve;
Assembly OnAssemblyResolve(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName)
{
- try
- {
- // you need to unsubscribe here to avoid StackOverflowException,
- // as LoadFromAssemblyName will go in recursion here otherwise
- AssemblyLoadContext.Default.Resolving -= OnAssemblyResolve;
- // return resolved assembly for cases when it can be resolved
- return assemblyLoadContext.LoadFromAssemblyName(assemblyName);
- }
- catch
- {
- // on failue - check if it failed to load our types assembly
- // and explicitly return it
- if (assemblyName.Name == "Microsoft.SqlServer.Types")
- return typeof(SqlGeography).Assembly;
- // if it failed to load some other assembly - just pass exception as-is
- throw;
- }
- finally
- {
- // don't forget to restore our load handler
- AssemblyLoadContext.Default.Resolving += OnAssemblyResolve;
- }
+ try
+ {
+ // you need to unsubscribe here to avoid StackOverflowException,
+ // as LoadFromAssemblyName will go in recursion here otherwise
+ AssemblyLoadContext.Default.Resolving -= OnAssemblyResolve;
+ // return resolved assembly for cases when it can be resolved
+ return assemblyLoadContext.LoadFromAssemblyName(assemblyName);
+ }
+ catch
+ {
+ // on failue - check if it failed to load our types assembly
+ // and explicitly return it
+ if (assemblyName.Name == "Microsoft.SqlServer.Types")
+ return typeof(SqlGeography).Assembly;
+ // if it failed to load some other assembly - just pass exception as-is
+ throw;
+ }
+ finally
+ {
+ // don't forget to restore our load handler
+ AssemblyLoadContext.Default.Resolving += OnAssemblyResolve;
+ }
}
```
diff --git a/source/articles/how-to/teach-linq2db-convert-custom-net-code-to-sql.md b/source/articles/how-to/teach-linq2db-convert-custom-net-code-to-sql.md
index 4244964..471d944 100644
--- a/source/articles/how-to/teach-linq2db-convert-custom-net-code-to-sql.md
+++ b/source/articles/how-to/teach-linq2db-convert-custom-net-code-to-sql.md
@@ -1,29 +1,19 @@
# How to teach LINQ to DB to convert custom .NET methods and objects to SQL
-You may run into a situation where LINQ to DB does not know how to convert some .NET method, property or object to SQL. But that is not a problem because LINQ to DB likes to learn. Just teach it :). In one of our previous blog posts we wrote about [Using this MapValueAttribute to control mapping with linq2db](xref:using-mapvalue-attribute-to-control-mapping.md). In this article we will go a little bit deeper.
-There are multiple ways to teach LINQ to DB how to convert custom properties and methods into SQL, but the primary ones are:
-
-
--
-
-[LinqToDB.Sql.ExpressionAttribute](#sqlexpression) and [LinqToDB.Sql.FunctionAttribute](#sqlfunction-attribute)
-
--
+You may run into a situation where LINQ to DB does not know how to convert some .NET method, property or object to SQL. But that is not a problem because LINQ to DB likes to learn. Just teach it :). In another article we wrote about [Using this MapValueAttribute to control mapping with Linq To DB](xref:using-mapvalue-attribute-to-control-mapping.md). In this article we will go a little bit deeper.
-[LinqToDB.ExpressionMethodAttribute](#expressionmethod)
-
--
-
-[LinqToDB.Linq.Expressions.MapMember()](#mapmember) method
-
-
+There are multiple ways to teach LINQ to DB how to convert custom properties and methods into SQL, but the primary ones are:
-[LinqToDB.Mapping.MappingSchema.SetValueToSqlConverter()](#setvaluetosqlconverter) method
-
+- [LinqToDB.Sql.ExpressionAttribute](#sqlexpression) and [LinqToDB.Sql.FunctionAttribute](#sqlfunction-attribute)
+- [LinqToDB.ExpressionMethodAttribute](#expressionmethod)
+- [LinqToDB.Linq.Expressions.MapMember()](#mapmember) method
+- [LinqToDB.Mapping.MappingSchema.SetValueToSqlConverter()](#setvaluetosqlconverter) method
Let's see how to use each of these methods.
-### Sql.Expression
-Let's say you love SQL's BETWEEN operator and you find out that LINQ to DB does not have `Between()` method out of the box, so you have to write something like this:
+## Sql.Expression
+
+Let's say you love SQL's BETWEEN operator and you find out that LINQ to DB does not have `Between()` method (only as example, as we have it, check `Sql.Between` API) out of the box, so you have to write something like this:
```cs
var query = db.Customers.Where(c => c.ID >= 1000 && c.ID <= 2000);
@@ -46,11 +36,11 @@ Let's test it:
[Test]
public void SqlExpressionAttributeTest()
{
- using (var db = new DataModel())
- {
- db.InlineParameters = true; // inlined parameters can be helpful when debugging
- db.Customers.Where(c => c.DateOfBirth.Between(new DateTime(2000, 1, 1), new DateTime(2000, 12, 31))).ToList();
- }
+ using (var db = new DataModel())
+ {
+ db.InlineParameters = true; // inlined parameters can be helpful when debugging
+ db.Customers.Where(c => c.DateOfBirth.Between(new DateTime(2000, 1, 1), new DateTime(2000, 12, 31))).ToList();
+ }
}
```
@@ -58,52 +48,53 @@ The SQL generated for SQL Server 2012 is:
```sql
SELECT
- [t1].[ID],
- [t1].[DateOfBirth],
- [t1].[FirstName],
- [t1].[LastName],
- [t1].[Email]
+ [t1].[ID],
+ [t1].[DateOfBirth],
+ [t1].[FirstName],
+ [t1].[LastName],
+ [t1].[Email]
FROM
- [dbo].[Customer] [t1]
+ [dbo].[Customer] [t1]
WHERE
- [t1].[DateOfBirth] BETWEEN '2000-01-01' AND '2000-12-31'
+ [t1].[DateOfBirth] BETWEEN '2000-01-01' AND '2000-12-31'
```
Notice the use of the `Sql.ExpressionAttribute.PreferServerSide` property set to true. `PreferServerSide = true` tells LINQ to DB to convert the method to SQL if possible, and if it's not possible for some reason - then to execute the method locally.
-There is another similar property – `ServerSideOnly`. If it's set to True, LINQ to DB will throw an exception if it can't convert a method to SQL. It can be set to true when you can't, don't need or don't want to write a client-side implementation.
+There is another similar property - `ServerSideOnly`. If it's set to True, LINQ to DB will throw an exception if it can't convert a method to SQL. It can be set to true when you can't, don't need or don't want to write a client-side implementation.
You may have a valid question: When can't LINQ to DB generate SQL? How is that possible if we show LINQ to DB what we want to generate? Here is a simple example:
```cs
var q =
- from c in db.Customers
- select
- SomeServerSideOnlyMethod(SomeLocalApplicationMethod(c.ID));
+ from c in db.Customers
+ select
+ SomeServerSideOnlyMethod(SomeLocalApplicationMethod(c.ID));
```
Let's say `SomeServerSideOnlyMethod()` is a method with the `Sql.Expression` attribute and `ServerSideOnly = true`, and `SomeLocalApplicationMethod()` is an ordinary .NET method that can only be executed locally.
Since `SomeLocalApplicationMethod()` must be executed locally, LINQ to DB has to first read the `Customer.ID` field values from the table to pass them to `SomeLocalApplicationMethod()` on the client side. From this moment the query, including the call to `SomeServerSideOnlyMethod()`, will have to be executed locally. But considering that `SomeServerSideOnlyMethod()` is marked as `ServerSideOnly = true`, LINQ to DB will throw an exception.
-### Sql.Function attribute
+## Sql.Function attribute
+
Presume that we are using SQL Server and we want to check if a string contains a representation of a numeric value. SQL Server has the `IsNumeric()` function, but LINQ to DB does not support it out of the box. It's easy to fix:
```cs
[Sql.Function("IsNumeric", ServerSideOnly = true)]
public static bool IsNumeric(string s)
{
- throw new InvalidOperationException();
+ throw new InvalidOperationException();
}
[Test]
public void SqlFunctionAttributeTest()
{
- using (var db = new DataModel())
- {
- db.InlineParameters = true;
- db.Customers.Where(c => SqlFunctions.IsNumeric(c.LastName)).ToList();
- }
+ using (var db = new DataModel())
+ {
+ db.InlineParameters = true;
+ db.Customers.Where(c => SqlFunctions.IsNumeric(c.LastName)).ToList();
+ }
}
```
@@ -111,20 +102,21 @@ The generated SQL:
```sql
SELECT
- [t1].[ID],
- [t1].[DateOfBirth],
- [t1].[FirstName],
- [t1].[LastName],
- [t1].[Email]
+ [t1].[ID],
+ [t1].[DateOfBirth],
+ [t1].[FirstName],
+ [t1].[LastName],
+ [t1].[Email]
FROM
- [dbo].[Customer] [t1]
+ [dbo].[Customer] [t1]
WHERE
- IsNumeric([t1].[LastName]) = 1
+ IsNumeric([t1].[LastName]) = 1
```
Please note, that you may omit specifying the function name in the attribute explicitly - in this case the method name (that the attribute is applied to) will be used as a function name.
-### ExpressionMethod
+## ExpressionMethod
+
Let us now examine the next attribute - `LinqToDB.ExpressionMethodAttribute`, a very powerful one. The `ExpressionMethodAttribute` allows specifying an expression that LINQ to DB will translate into SQL.
For those of us who are fans of the SQL's `IN` operator, let's show how we can make LINQ to DB support it:
@@ -133,14 +125,14 @@ For those of us who are fans of the SQL's `IN` operator, let's show how we can m
[ExpressionMethod("InImpl")]
public static bool In(this T item, IEnumerable items)
{
- return items.Contains(item); // this code will run if we execute the method locally
+ return items.Contains(item); // this code will run if we execute the method locally
}
public static Expression, bool>> InImpl()
{
- // LINQ to DB will translate this expression into SQL
- // (it knows out of the box how to translate Contains()
- return (item, items) => items.Contains(item);
+ // LINQ to DB will translate this expression into SQL
+ // (it knows out of the box how to translate Contains()
+ return (item, items) => items.Contains(item);
}
```
@@ -162,10 +154,10 @@ The test:
[Test]
public void InTest()
{
- using (var db = new DataModel())
- {
- var customers = db.Customers.Where(c => c.FirstName.In(new[] {"Pavel", "John", "Jack"})).ToList();
- }
+ using (var db = new DataModel())
+ {
+ var customers = db.Customers.Where(c => c.FirstName.In(new[] {"Pavel", "John", "Jack"})).ToList();
+ }
}
```
@@ -173,15 +165,15 @@ This will generate the following SQL:
```sql
SELECT
- [t1].[ID],
- [t1].[DateOfBirth],
- [t1].[FirstName],
- [t1].[LastName],
- [t1].[Email]
+ [t1].[ID],
+ [t1].[DateOfBirth],
+ [t1].[FirstName],
+ [t1].[LastName],
+ [t1].[Email]
FROM
- [dbo].[Customer] [t1]
+ [dbo].[Customer] [t1]
WHERE
- [t1].[FirstName] IN (N'Pavel', N'John', N'Jack')
+ [t1].[FirstName] IN (N'Pavel', N'John', N'Jack')
```
Another example, showing that `ExpressionMethod` can be applied to properties:
@@ -189,16 +181,16 @@ Another example, showing that `ExpressionMethod` can be applied to properties:
```cs
public partial class Issue
{
- [ExpressionMethod("GetAgeExpression")]
- public double AgeInDays
- {
- get { return (DateTime.Now - CreatedOn).TotalDays; }
- }
-
- private static Expression> GetAgeExpression()
- {
- return issue => (Sql.CurrentTimestamp - issue.CreatedOn).TotalDays;
- }
+ [ExpressionMethod("GetAgeExpression")]
+ public double AgeInDays
+ {
+ get { return (DateTime.Now - CreatedOn).TotalDays; }
+ }
+
+ private static Expression> GetAgeExpression()
+ {
+ return issue => (Sql.CurrentTimestamp - issue.CreatedOn).TotalDays;
+ }
}
```
@@ -208,10 +200,10 @@ Test:
[Test]
public void ExpressionMethodTest2()
{
- using (var db = new DataModel())
- {
- var issues = db.Issues.Where(issue => issue.AgeInDays > 30).ToList();
- }
+ using (var db = new DataModel())
+ {
+ var issues = db.Issues.Where(issue => issue.AgeInDays > 30).ToList();
+ }
}
```
@@ -219,20 +211,21 @@ The generated SQL:
```sql
SELECT
- [t1].[ID],
- [t1].[Subject],
- [t1].[Description],
- [t1].[Status],
- [t1].[CreatedOn]
+ [t1].[ID],
+ [t1].[Subject],
+ [t1].[Description],
+ [t1].[Status],
+ [t1].[CreatedOn]
FROM
- [dbo].[Issue] [t1]
+ [dbo].[Issue] [t1]
WHERE
- DateDiff(Day, [t1].[CreatedOn], CURRENT_TIMESTAMP) > 30
+ DateDiff(Day, [t1].[CreatedOn], CURRENT_TIMESTAMP) > 30
```
You can find more examples of ExpressionMethod usage (including a possible `LeftJoin()` implementation that may be of interest to you) here: [ExpressionTests.cs](https://github.com/linq2db/linq2db/blob/master/Tests/Linq/Linq/ExpressionsTests.cs)
-### MapMember()
+## MapMember()
+
The next method we will discuss is the `LinqToDB.Linq.Expressions.MapMember()` method (having numerous overloads). It allows you to specify how to convert existing methods and properties. Basically, you provide the original method or property and the corresponding `Expression` that will be used by LINQ to DB instead of the original method. Internally LINQ to DB uses `MapMember()` to map hundreds of standard .NET framework methods and properties.
For example, we would like to make LINQ to DB support the `String.IsNullOrWhitespace()` method and we can't add the `ExpressionMethod` attribute to `IsNullOrWhitespace()` because it's a framework's method and we can't change it.
@@ -242,19 +235,19 @@ The `MapMember()` method comes to the rescue!
```cs
public partial class DataModel
{
- static DataModel()
- {
- LinqToDB.Linq.Expressions.MapMember((string s) => string.IsNullOrWhiteSpace(s), s => s == null || s.TrimEnd() == string.Empty);
- }
+ static DataModel()
+ {
+ LinqToDB.Linq.Expressions.MapMember((string s) => string.IsNullOrWhiteSpace(s), s => s == null || s.TrimEnd() == string.Empty);
+ }
}
[Test]
public void MapMemberTest()
{
- using (var db = new DataModel())
- {
- var customers = db.Customers.Where(c => string.IsNullOrWhiteSpace(c.Email)).ToList();
- }
+ using (var db = new DataModel())
+ {
+ var customers = db.Customers.Where(c => string.IsNullOrWhiteSpace(c.Email)).ToList();
+ }
}
```
@@ -262,52 +255,45 @@ The generated SQL:
```sql
SELECT
- [t1].[ID],
- [t1].[DateOfBirth],
- [t1].[FirstName],
- [t1].[LastName],
- [t1].[Email]
+ [t1].[ID],
+ [t1].[DateOfBirth],
+ [t1].[FirstName],
+ [t1].[LastName],
+ [t1].[Email]
FROM
- [dbo].[Customer] [t1]
+ [dbo].[Customer] [t1]
WHERE
- [t1].[Email] IS NULL OR RTrim([t1].[Email]) = N''
+ [t1].[Email] IS NULL OR RTrim([t1].[Email]) = N''
```
-### SetValueToSqlConverter()
+## SetValueToSqlConverter()
The last method we will examine is `LinqToDB.Mapping.MappingSchema.SetValueToSqlConverter()`. It is used to control exactly how a value will be converted to SQL. The two primary use cases for this method are:
-
--
-
-When adding support for a new database provider. For example, when working with the `Boolean` data type in Informix RDBMS, `t` represents the logical value TRUE and `f` represents FALSE. Here is how this is implemented in LinqToDB as a part of its Informix support:
+1. When adding support for a new database provider. For example, when working with the `Boolean` data type in Informix RDBMS, `t` represents the logical value TRUE and `f` represents FALSE. Here is how this is implemented in LinqToDB as a part of its Informix support:
```cs
public class InformixMappingSchema : MappingSchema
{
- protected InformixMappingSchema(string configuration) : base(configuration)
- {
- SetValueToSqlConverter(typeof(bool), (sb,dt,v) => sb.Append("'").Append((bool)v ? 't' : 'f').Append("'"));
- }
+ protected InformixMappingSchema(string configuration) : base(configuration)
+ {
+ SetValueToSqlConverter(typeof(bool), (sb,dt,v) => sb.Append("'").Append((bool)v ? 't' : 'f').Append("'"));
+ }
}
```
-
-
--
-When adding support for a new data type. For example, here is how to teach LINQ to DB to consider the `SqlDecimal.IsNull` property and correctly convert `SqlDecimal` objects to SQL:
+2. When adding support for a new data type. For example, here is how to teach LINQ to DB to consider the `SqlDecimal.IsNull` property and correctly convert `SqlDecimal` objects to SQL:
```cs
MappingSchema.Default.SetValueToSqlConverter(
- typeof(SqlDecimal),
- (sb, dt, v) =>
- {
- var value = (SqlDecimal)v;
-
- if (value.IsNull)
- sb.Append("NULL");
- else
- sb.Append(v);
- });
+ typeof(SqlDecimal),
+ (sb, dt, v) =>
+ {
+ var value = (SqlDecimal)v;
+
+ if (value.IsNull)
+ sb.Append("NULL");
+ else
+ sb.Append(v);
+ });
```
-
diff --git a/source/articles/how-to/toc.yml b/source/articles/how-to/toc.yml
new file mode 100644
index 0000000..a0c0623
--- /dev/null
+++ b/source/articles/how-to/toc.yml
@@ -0,0 +1,4 @@
+- name: Using MapValueAttribute
+ href: using-mapvalue-attribute-to-control-mapping.md
+- name: Setup Custom Conversions
+ href: teach-linq2db-convert-custom-net-code-to-sql.md
diff --git a/source/articles/how-to/using-mapvalue-attribute-to-control-mapping.md b/source/articles/how-to/using-mapvalue-attribute-to-control-mapping.md
index a0d0efd..2e1da42 100644
--- a/source/articles/how-to/using-mapvalue-attribute-to-control-mapping.md
+++ b/source/articles/how-to/using-mapvalue-attribute-to-control-mapping.md
@@ -1,18 +1,19 @@
-# Using MapValueAttribute to control mapping with linq2db
-One of the primary functions of [linq2db](https://github.com/linq2db) is mapping between a database and classes/properties in your data model. Linq2db does a great job here straight out of the box, but often it is desirable to tune this process. The most frequent example of where you may need this is with enumerations.
+# Using MapValueAttribute to control mapping with Linq To DB
+
+One of the primary functions of [Linq To DB](https://github.com/linq2db) is mapping between a database and classes/properties in your data model. Linq2db does a great job here straight out of the box, but often it is desirable to tune this process. The most frequent example of where you may need this is with enumerations.
Let's say you have a table called Issue. Each issue has a status, which can be one of the predefined values: Open, InProgress, Resolved, Closed. Let's assume we use a CHAR(1) field to keep a status value ('O' = Open, 'R' = Resolved, etc.).
-If we [generate our data model](https://github.com/linq2db/t4models) without any tuning, we may get something like this:
+If we [scaf](https://www.nuget.org/packages/linq2db.cli)[fold](https://www.nuget.org/packages/linq2db.t4models) our data model without any tuning, we may get something like this:
```cs
[Table(Schema="dbo", Name="Issue")]
public partial class Issue
{
- [PrimaryKey, Identity ] public int ID { get; set; } // int
- [Column, NotNull ] public string Subject { get; set; } // varchar(8000)
- [Column, Nullable] public string Description { get; set; } // varchar(max)
- [Column, NotNull ] public char Status { get; set; } // char(1)
+ [PrimaryKey, Identity ] public int ID { get; set; } // int
+ [Column, NotNull ] public string Subject { get; set; } // varchar(8000)
+ [Column, Nullable] public string Description { get; set; } // varchar(max)
+ [Column, NotNull ] public char Status { get; set; } // char(1)
}
```
@@ -21,23 +22,17 @@ As you understand, it's not convenient to use char values when working with Stat
```cs
public enum IssueStatus
{
- Open,
- InProgress,
- Resolved,
- Closed
+ Open,
+ InProgress,
+ Resolved,
+ Closed
}
```
In order to replace the Status type with an enumeration and teach linq2db how to do the mapping, we need to complete two simple steps:
-
--
-
-Use the `MapValue` attribute to explain to linq2db how to map between the enumeration and char values in the database table
--
-
-Change the `Status` property type from char to `IssueStatus`.
-
+1. Use the `MapValue` attribute to explain to linq2db how to map between the enumeration and char values in the database table<
+1. Change the `Status` property type from char to `IssueStatus`.
The first step is accomplished like this:
@@ -46,10 +41,10 @@ using LinqToDB.Mapping;
public enum IssueStatus
{
- [MapValue('O')] Open,
- [MapValue('I')] InProgress,
- [MapValue('R')] Resolved,
- [MapValue('C')] Closed
+ [MapValue('O')] Open,
+ [MapValue('I')] InProgress,
+ [MapValue('R')] Resolved,
+ [MapValue('C')] Closed
}
```
@@ -59,14 +54,18 @@ If you generate your data model with the help of a T4 template, add the followin
```cs
Tables["Issue"].Columns["Status"].Type = "IssueStatus";
+```
+
Our data model class will look like this now:
+
+```cs
[Table(Schema="dbo", Name="Issue")]
public partial class Issue
{
- [PrimaryKey, Identity ] public int ID { get; set; } // int
- [Column, NotNull ] public string Subject { get; set; } // varchar(8000)
- [Column, Nullable] public string Description { get; set; } // varchar(max)
- [Column, NotNull ] public IssueStatus Status { get; set; } // char(1)
+ [PrimaryKey, Identity ] public int ID { get; set; } // int
+ [Column, NotNull ] public string Subject { get; set; } // varchar(8000)
+ [Column, Nullable] public string Description { get; set; } // varchar(max)
+ [Column, NotNull ] public IssueStatus Status { get; set; } // char(1)
}
```
@@ -83,14 +82,14 @@ This will generate the following query (for SQL Server):
```sql
SELECT
- [t1].[ID],
- [t1].[Subject],
- [t1].[Description],
- [t1].[Status]
+ [t1].[ID],
+ [t1].[Subject],
+ [t1].[Description],
+ [t1].[Status]
FROM
- [dbo].[Issue] [t1]
+ [dbo].[Issue] [t1]
WHERE
- [t1].[Status] = N'O'
+ [t1].[Status] = N'O'
```
Note that if you used the `int` datatype for the `Status` column instead of char, then you could declare your enumeration like this:
@@ -98,10 +97,10 @@ Note that if you used the `int` datatype for the `Status` column instead of char
```cs
public enum IssueStatus
{
- Open = 1,
- InProgress = 2,
- Resolved = 3,
- Closed = 4
+ Open = 1,
+ InProgress = 2,
+ Resolved = 3,
+ Closed = 4
}
```
@@ -112,26 +111,26 @@ Sometimes we may need to map multiple values in a database table to the same val
```cs
public enum Gender
{
- [MapValue(null)]
- Undefined,
+ [MapValue(null)]
+ Undefined,
- [MapValue("M", IsDefault = true)]
- [MapValue("Male")]
- Male,
+ [MapValue("M", IsDefault = true)]
+ [MapValue("Male")]
+ Male,
- [MapValue("F", IsDefault = true)]
- [MapValue("Female")]
- Female
+ [MapValue("F", IsDefault = true)]
+ [MapValue("Female")]
+ Female
}
using (var db = new DataModel())
{
- db.People.Insert(() => new Person
- {
- FirstName = "Herbert",
- LastName = "Wells",
- Gender = Gender.Male
- });
+ db.People.Insert(() => new Person
+ {
+ FirstName = "Herbert",
+ LastName = "Wells",
+ Gender = Gender.Male
+ });
}
```
@@ -140,19 +139,19 @@ Generated SQL (for SQL Server):
```sql
INSERT INTO [dbo].[Person]
(
- [FirstName],
- [LastName],
- [Gender]
+ [FirstName],
+ [LastName],
+ [Gender]
)
VALUES
(
- N'Herbert',
- N'Wells',
- N'M'
+ N'Herbert',
+ N'Wells',
+ N'M'
)
```
-As you can see, `Gender.Male` has been mapped to ‘M' (because it is marked with the IsDefault property set to true).
+As you can see, `Gender.Male` has been mapped to 'M' (because it is marked with the IsDefault property set to true).
There may be a situation when you need to get values specified by the `MapValue` attribute. There are different ways to accomplish this. You can write an extension method and use reflection inside, you can use a very powerful `ConvertTo` class from linq2db, or you can use `MappingSchema.Default.EnumToValue()` method (also from linq2db), etc. `ConvertTo` can be used like this:
@@ -164,7 +163,7 @@ Often developers create a separate table to store possible values and use a fore
```sql
ALTER TABLE
- dbo.Issue
+ dbo.Issue
ADD CONSTRAINT
- CK_IssueStatus CHECK (Status IN ('O', 'I', 'R', 'C'))
+ CK_IssueStatus CHECK (Status IN ('O', 'I', 'R', 'C'))
```
diff --git a/source/articles/sql/CTE.md b/source/articles/sql/CTE.md
index a5eccfc..dc7a3c8 100644
--- a/source/articles/sql/CTE.md
+++ b/source/articles/sql/CTE.md
@@ -16,17 +16,17 @@ CTE in `LINQ To DB` implements `IQueryable` and any `IQueryable` can be converte
```cs
var employeeSubordinatesReport =
- from e in db.Employee
- select new
- {
- e.EmployeeID,
- e.LastName,
- e.FirstName,
- NumberOfSubordinates = db.Employee
- .Where(e2 => e2.ReportsTo == e.ReportsTo)
- .Count(),
- e.ReportsTo
- };
+ from e in db.Employee
+ select new
+ {
+ e.EmployeeID,
+ e.LastName,
+ e.FirstName,
+ NumberOfSubordinates = db.Employee
+ .Where(e2 => e2.ReportsTo == e.ReportsTo)
+ .Count(),
+ e.ReportsTo
+ };
// define CTE named EmployeeSubordinatesReport
// employeeSubordinatesReport sub-query used as CTE body
@@ -38,17 +38,17 @@ The variable `employeeSubordinatesReportCte` can now be reused in other parts of
```cs
var result =
- from employee in employeeSubordinatesReportCte
- from manager in employeeSubordinatesReportCte
- .LeftJoin(manager => employee.ReportsTo == manager.EmployeeID)
- select new
- {
- employee.LastName,
- employee.FirstName,
- employee.NumberOfSubordinates,
- ManagerLastName = manager.LastName,
- ManagerFirstName = manager.FirstName,
- ManagerNumberOfSubordinates = manager.NumberOfSubordinates
+ from employee in employeeSubordinatesReportCte
+ from manager in employeeSubordinatesReportCte
+ .LeftJoin(manager => employee.ReportsTo == manager.EmployeeID)
+ select new
+ {
+ employee.LastName,
+ employee.FirstName,
+ employee.NumberOfSubordinates,
+ ManagerLastName = manager.LastName,
+ ManagerFirstName = manager.FirstName,
+ ManagerNumberOfSubordinates = manager.NumberOfSubordinates
};
```
@@ -57,42 +57,42 @@ You are not limited in the number of CTEs, defined in a query, and they may ref
```sql
WITH [EmployeeSubordinatesReport]
(
- [ReportsTo],
- [EmployeeID],
- [LastName],
- [FirstName],
- [NumberOfSubordinates]
+ [ReportsTo],
+ [EmployeeID],
+ [LastName],
+ [FirstName],
+ [NumberOfSubordinates]
)
AS
(
- SELECT
- [t2].[ReportsTo],
- [t2].[EmployeeID],
- [t2].[LastName],
- [t2].[FirstName],
- (
- SELECT
+ SELECT
+ [t2].[ReportsTo],
+ [t2].[EmployeeID],
+ [t2].[LastName],
+ [t2].[FirstName],
+ (
+ SELECT
Count(*)
- FROM
+ FROM
[Employees] [t1]
- WHERE
+ WHERE
[t1].[ReportsTo] IS NULL AND [t2].[ReportsTo] IS NULL OR
[t1].[ReportsTo] = [t2].[ReportsTo]
- ) as [c1]
- FROM
+ ) as [c1]
+ FROM
[Employees] [t2]
)
SELECT
- [t3].[LastName] as [LastName1],
- [t3].[FirstName] as [FirstName1],
- [t3].[NumberOfSubordinates],
- [manager].[LastName] as [LastName2],
- [manager].[FirstName] as [FirstName2],
- [manager].[NumberOfSubordinates] as [NumberOfSubordinates1]
+ [t3].[LastName] as [LastName1],
+ [t3].[FirstName] as [FirstName1],
+ [t3].[NumberOfSubordinates],
+ [manager].[LastName] as [LastName2],
+ [manager].[FirstName] as [FirstName2],
+ [manager].[NumberOfSubordinates] as [NumberOfSubordinates1]
FROM
- [EmployeeSubordinatesReport] [t3]
- LEFT JOIN [EmployeeSubordinatesReport] [manager]
- ON [t3].[ReportsTo] = [manager].[EmployeeID]
+ [EmployeeSubordinatesReport] [t3]
+ LEFT JOIN [EmployeeSubordinatesReport] [manager]
+ ON [t3].[ReportsTo] = [manager].[EmployeeID]
```
## Defining recursive CTE
@@ -109,52 +109,52 @@ The following example shows how to define a CTE to calculate the employee level
// defining class for representing Recursive CTE
class EmployeeHierarchyCTE
{
- public int EmployeeID;
- public string LastName;
- public string FirstName;
- public int? ReportsTo;
- public int HierarchyLevel;
+ public int EmployeeID;
+ public string LastName;
+ public string FirstName;
+ public int? ReportsTo;
+ public int HierarchyLevel;
}
using (var db = new NorthwindDB(context))
{
- var employeeHierarchyCte = db.GetCte(employeeHierarchy =>
- {
- return
- (
- from e in db.Employee
- where e.ReportsTo == null
- select new EmployeeHierarchyCTE
- {
- EmployeeID = e.EmployeeID,
- LastName = e.LastName,
- FirstName = e.FirstName,
- ReportsTo = e.ReportsTo,
- HierarchyLevel = 1
- }
- )
- .Concat
- (
- from e in db.Employee
- from eh in employeeHierarchy
- .InnerJoin(eh => e.ReportsTo == eh.EmployeeID)
- select new EmployeeHierarchyCTE
- {
- EmployeeID = e.EmployeeID,
- LastName = e.LastName,
- FirstName = e.FirstName,
- ReportsTo = e.ReportsTo,
- HierarchyLevel = eh.HierarchyLevel + 1
- }
- );
- });
-
- var result =
- from eh in employeeHierarchyCte
- orderby eh.HierarchyLevel, eh.LastName, eh.FirstName
- select eh;
-
- var data = result.ToArray();
+ var employeeHierarchyCte = db.GetCte(employeeHierarchy =>
+ {
+ return
+ (
+ from e in db.Employee
+ where e.ReportsTo == null
+ select new EmployeeHierarchyCTE
+ {
+ EmployeeID = e.EmployeeID,
+ LastName = e.LastName,
+ FirstName = e.FirstName,
+ ReportsTo = e.ReportsTo,
+ HierarchyLevel = 1
+ }
+ )
+ .Concat
+ (
+ from e in db.Employee
+ from eh in employeeHierarchy
+ .InnerJoin(eh => e.ReportsTo == eh.EmployeeID)
+ select new EmployeeHierarchyCTE
+ {
+ EmployeeID = e.EmployeeID,
+ LastName = e.LastName,
+ FirstName = e.FirstName,
+ ReportsTo = e.ReportsTo,
+ HierarchyLevel = eh.HierarchyLevel + 1
+ }
+ );
+ });
+
+ var result =
+ from eh in employeeHierarchyCte
+ orderby eh.HierarchyLevel, eh.LastName, eh.FirstName
+ select eh;
+
+ var data = result.ToArray();
}
```
@@ -163,48 +163,48 @@ Resulting SQL:
```sql
WITH [employeeHierarchy]
(
- [EmployeeID],
- [LastName],
- [FirstName],
- [ReportsTo],
- [HierarchyLevel]
+ [EmployeeID],
+ [LastName],
+ [FirstName],
+ [ReportsTo],
+ [HierarchyLevel]
)
AS
(
- SELECT
- [t1].[EmployeeID],
- [t1].[LastName],
- [t1].[FirstName],
- [t1].[ReportsTo],
- 1 as [c1]
- FROM
- [Employees] [t1]
- WHERE
- [t1].[ReportsTo] IS NULL
- UNION ALL
- SELECT
- [t2].[EmployeeID],
- [t2].[LastName],
- [t2].[FirstName],
- [t2].[ReportsTo],
- [eh].[HierarchyLevel] + 1 as [c1]
- FROM
- [Employees] [t2]
- INNER JOIN [employeeHierarchy] [eh] ON [t2].[ReportsTo] = [eh].[EmployeeID]
+ SELECT
+ [t1].[EmployeeID],
+ [t1].[LastName],
+ [t1].[FirstName],
+ [t1].[ReportsTo],
+ 1 as [c1]
+ FROM
+ [Employees] [t1]
+ WHERE
+ [t1].[ReportsTo] IS NULL
+ UNION ALL
+ SELECT
+ [t2].[EmployeeID],
+ [t2].[LastName],
+ [t2].[FirstName],
+ [t2].[ReportsTo],
+ [eh].[HierarchyLevel] + 1 as [c1]
+ FROM
+ [Employees] [t2]
+ INNER JOIN [employeeHierarchy] [eh] ON [t2].[ReportsTo] = [eh].[EmployeeID]
)
SELECT
- [t3].[EmployeeID] as [EmployeeID2],
- [t3].[LastName] as [LastName2],
- [t3].[FirstName] as [FirstName2],
- [t3].[ReportsTo] as [ReportsTo2],
- [t3].[HierarchyLevel]
+ [t3].[EmployeeID] as [EmployeeID2],
+ [t3].[LastName] as [LastName2],
+ [t3].[FirstName] as [FirstName2],
+ [t3].[ReportsTo] as [ReportsTo2],
+ [t3].[HierarchyLevel]
FROM
- [employeeHierarchy] [t3]
+ [employeeHierarchy] [t3]
ORDER BY
- [t3].[HierarchyLevel],
- [t3].[LastName],
- [t3].[FirstName]
+ [t3].[HierarchyLevel],
+ [t3].[LastName],
+ [t3].[FirstName]
```
## Database engines that support CTE
diff --git a/source/articles/toc.yml b/source/articles/toc.yml
index 6668242..13dc032 100644
--- a/source/articles/toc.yml
+++ b/source/articles/toc.yml
@@ -6,6 +6,8 @@
href: general/toc.yml
- name: SQL
href: sql/toc.yml
+- name: How To
+ href: how-to/toc.yml
- name: CLI Scaffold Tool
href: CLI.md
- name: T4 Templates (Obsoleted)