Using multiple Properties for the same Column
See original GitHub issueWe use Linq2Db with Columns which are mapped to enums. The columns are Nvarchar and the Enum-string is written to the db. This works as expected. But sometimes we have to write a custom string to the column, which is not in represented in the enum or we just want to search for a custom string in the db. This can happen in fields, where the enum-values are just recommendations and not mandatory values.
Thats why we have 2 Properties in our POCO.
// Property to conveniently Access the Field in most of the cases.
public StatusEnum Status { get; set; }
// Property to write and query with string values.
public string StatusString
{
get { return Status.ToString(); }
set {Status = (StatusEnum)value; } // Pseudocode ;)
}
Both Properties map to the column “Status” in the database.
Everything works perfectly, mapping is done for Status Enum with Methods like
schema.SetConvertExpression<string, StatusEnum >(s => StatusEnumHelper.GetEnumValueFromText(s));
// for the other direction i use
schema.AddMetadataReader (...);
// and there the Method, that way i can use reflection to map the correct string value. (long story ;))
TAttribute[] LinqToDB.Metadata.IMetadataReader.GetAttributes<TAttribute>(Type type, MemberInfo memberInfo, bool inherit)
The second property “StatusString” gets set like a Calculated Field.
propertybuilder.HasSkipOnInsert(true);
propertybuilder.HasSkipOnUpdate(true);
That way only one the enum-Property is used in Insert and Update Statements.
But we have a Problem when we use it with TempTables. The Method CreateTempTable still thinks both Properties are Columns and tries to create 2 “Status” Columns. And doesn’t ignore the second property.
using (var tempTable = _dbContext.CreateTempTable("TempTable-Artikel", artikel)) // This row throws the Exception
{
_dbContext.LieferpartnerArtikel
.Merge()
.Using(tempTable)
.OnTargetKey()
.InsertWhenNotMatched()
.UpdateWhenMatched((target, source) => new LieferpartnerArtikel
{
...
}).Merge();
}
The Exception we get is: (TEXT9 is ‘Status’ in our case here):
Exception message:
Column names in each table must be unique. Column name 'TEXT9' in table 'TempTable-Artikel' is specified more than once.
Stack trace:
System.Data.SqlClient.SqlException (0x80131904): Column names in each table must be unique. Column name 'TEXT9' in table 'TempTable-Artikel' is specified more than once.
bei System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
bei System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
bei System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
bei System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
bei System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite)
bei System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry)
bei System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
bei LinqToDB.Data.DataConnection.ExecuteNonQuery(IDbCommand command) in d:\a\1\s\Source\LinqToDB\Data\DataConnection.cs:Zeile 1246.
bei LinqToDB.Data.DataConnection.ExecuteNonQuery() in d:\a\1\s\Source\LinqToDB\Data\DataConnection.cs:Zeile 1302.
bei LinqToDB.Data.DataConnection.QueryRunner.ExecuteNonQueryImpl(DataConnection dataConnection, PreparedQuery preparedQuery) in d:\a\1\s\Source\LinqToDB\Data\DataConnection.QueryRunner.cs:Zeile 292.
bei LinqToDB.Data.DataConnection.QueryRunner.ExecuteNonQuery() in d:\a\1\s\Source\LinqToDB\Data\DataConnection.QueryRunner.cs:Zeile 332.
bei LinqToDB.Linq.QueryRunner.NonQueryQuery(Query query, IDataContext dataContext, Expression expr, Object[] parameters, Object[] preambles) in d:\a\1\s\Source\LinqToDB\Linq\QueryRunner.cs:Zeile 973.
bei LinqToDB.Linq.QueryRunner.<>c__DisplayClass33_0.<SetNonQueryQuery>b__0(IDataContext db, Expression expr, Object[] ps, Object[] preambles) in d:\a\1\s\Source\LinqToDB\Linq\QueryRunner.cs:Zeile 966.
bei LinqToDB.Linq.QueryRunner.CreateTable`1.Query(IDataContext dataContext, String tableName, String serverName, String databaseName, String schemaName, String statementHeader, String statementFooter, DefaultNullable defaultNullable) in d:\a\1\s\Source\LinqToDB\Linq\QueryRunner.CreateTable.cs:Zeile 39.
bei LinqToDB.DataExtensions.CreateTable[T](IDataContext dataContext, String tableName, String databaseName, String schemaName, String statementHeader, String statementFooter, DefaultNullable defaultNullable, String serverName) in d:\a\1\s\Source\LinqToDB\DataExtensions.cs:Zeile 995.
bei LinqToDB.TempTable`1..ctor(IDataContext db, String tableName, IEnumerable`1 items, BulkCopyOptions options, String databaseName, String schemaName, String serverName) in d:\a\1\s\Source\LinqToDB\TempTable.cs:Zeile 102.
bei LinqToDB.DataExtensions.CreateTempTable[T](IDataContext db, String tableName, IEnumerable`1 items, BulkCopyOptions options, String databaseName, String schemaName, String serverName) in d:\a\1\s\Source\LinqToDB\TempTable.cs:Zeile 417.
So my Question is, is there any way to use two Properties, to have full flexibility when using it with an enum or to use it with a string, when needed. Or is there no other way then to exclude the second column from the mapping with propertybuilder.IsNotColumn();? Maybe you have a completely Different way how to do it?
Environment details
linq2db version: 3.0.1 Database Server: SQLServer Operating system: Windows 10 .NET Framework: .NET 4.7.2
Issue Analytics
- State:
- Created 3 years ago
- Comments:7 (5 by maintainers)
I can not give you complete solution right now. But check calculated properties, maybe it will work. Sample is here: https://github.com/linq2db/linq2db/blob/864899f8721ba98682c87bc9788bd438c87643a6/Tests/Linq/Linq/CalculatedColumnTests.cs#L25-L31
@sdanyliv We had to try different things, but in the end we made it work (a lot of trial and error 😉). The unique thing is that our ‘calculated column’ also has a setter. So in the end we still mapped both Properties to the same Column, and we still skip our second column on Inserts and Updates. The ‘propertybuilder.IsExpression’ you linked me was the misssing part we needed.
Now Lin2Db knows, that this is not another DB-Column and we relay it to the string-Column.
So now CreateTempTable doesn’t try to create this property as a db-column and everything else still works.
Thanks a lot! 🥳