diff --git a/src/EFCore.MySql/Query/Internal/MySqlMemberTranslatorProvider.cs b/src/EFCore.MySql/Query/Internal/MySqlMemberTranslatorProvider.cs index 252151ccf..595298545 100644 --- a/src/EFCore.MySql/Query/Internal/MySqlMemberTranslatorProvider.cs +++ b/src/EFCore.MySql/Query/Internal/MySqlMemberTranslatorProvider.cs @@ -3,12 +3,15 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Storage; namespace Pomelo.EntityFrameworkCore.MySql.Query.Internal { public class MySqlMemberTranslatorProvider : RelationalMemberTranslatorProvider { - public MySqlMemberTranslatorProvider([NotNull] RelationalMemberTranslatorProviderDependencies dependencies) + public MySqlMemberTranslatorProvider( + [NotNull] RelationalMemberTranslatorProviderDependencies dependencies, + IRelationalTypeMappingSource typeMappingSource) : base(dependencies) { var sqlExpressionFactory = (MySqlSqlExpressionFactory)dependencies.SqlExpressionFactory; @@ -17,7 +20,7 @@ public MySqlMemberTranslatorProvider([NotNull] RelationalMemberTranslatorProvide new IMemberTranslator[] { new MySqlDateTimeMemberTranslator(sqlExpressionFactory), new MySqlStringMemberTranslator(sqlExpressionFactory), - new MySqlTimeSpanMemberTranslator(sqlExpressionFactory), + new MySqlTimeSpanMemberTranslator(sqlExpressionFactory, typeMappingSource), }); } } diff --git a/src/EFCore.MySql/Query/Internal/MySqlTimeSpanMemberTranslator.cs b/src/EFCore.MySql/Query/Internal/MySqlTimeSpanMemberTranslator.cs index b0020812f..f40c0e755 100644 --- a/src/EFCore.MySql/Query/Internal/MySqlTimeSpanMemberTranslator.cs +++ b/src/EFCore.MySql/Query/Internal/MySqlTimeSpanMemberTranslator.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage; namespace Pomelo.EntityFrameworkCore.MySql.Query.Internal { @@ -21,11 +22,27 @@ public class MySqlTimeSpanMemberTranslator : IMemberTranslator { nameof(TimeSpan.Seconds), ("second", 1) }, { nameof(TimeSpan.Milliseconds), ("microsecond", 1000) }, }; + + private static readonly Dictionary _totalTimePartMapping + = new Dictionary + { + { nameof(TimeSpan.TotalDays), 24 * 60 * 60 }, + { nameof(TimeSpan.TotalHours), 60 * 60 }, + { nameof(TimeSpan.TotalMinutes), 60 }, + { nameof(TimeSpan.TotalSeconds), 1 }, + { nameof(TimeSpan.TotalMilliseconds), 0.001 }, + { nameof(TimeSpan.TotalNanoseconds), 0.001 * 0.001 * 0.001 }, + }; + private readonly MySqlSqlExpressionFactory _sqlExpressionFactory; + private readonly IRelationalTypeMappingSource _typeMappingSource; - public MySqlTimeSpanMemberTranslator(MySqlSqlExpressionFactory sqlExpressionFactory) + public MySqlTimeSpanMemberTranslator( + MySqlSqlExpressionFactory sqlExpressionFactory, + IRelationalTypeMappingSource typeMappingSource) { _sqlExpressionFactory = sqlExpressionFactory; + _typeMappingSource = typeMappingSource; } public virtual SqlExpression Translate( @@ -37,13 +54,14 @@ public virtual SqlExpression Translate( var declaringType = member.DeclaringType; var memberName = member.Name; - if (declaringType == typeof(TimeSpan) && - _datePartMapping.TryGetValue(memberName, out var datePart)) + if (declaringType == typeof(TimeSpan)) { - var extract = _sqlExpressionFactory.NullableFunction( - "EXTRACT", - new[] - { + if (_datePartMapping.TryGetValue(memberName, out var datePart)) + { + var extract = _sqlExpressionFactory.NullableFunction( + "EXTRACT", + new[] + { _sqlExpressionFactory.ComplexFunctionArgument( new [] { _sqlExpressionFactory.Fragment($"{datePart.Part} FROM"), @@ -51,18 +69,36 @@ public virtual SqlExpression Translate( }, " ", typeof(string)) - }, - returnType, - false); + }, + returnType, + false); - if (datePart.Divisor != 1) - { - return _sqlExpressionFactory.MySqlIntegerDivide( - extract, - _sqlExpressionFactory.Constant(datePart.Divisor)); + if (datePart.Divisor != 1) + { + return _sqlExpressionFactory.MySqlIntegerDivide( + extract, + _sqlExpressionFactory.Constant(datePart.Divisor)); + } + + return extract; } + else if (_totalTimePartMapping.TryGetValue(memberName, out var multiplicator) == true) + { + var convertToSecondsExpression = _sqlExpressionFactory.NullableFunction( + name: "TIME_TO_SEC", + arguments: new[] { instance }, + returnType: typeof(int), + typeMapping: _typeMappingSource.FindMapping(typeof(int)) + ); - return extract; + var divideExpression = _sqlExpressionFactory.Divide( + left: convertToSecondsExpression, + right: _sqlExpressionFactory.Constant(multiplicator), + typeMapping: _typeMappingSource.FindMapping(typeof(double)) + ); + + return divideExpression; + } } return null; diff --git a/test/EFCore.MySql.IntegrationTests/Tests/Models/ExpressionTest.cs b/test/EFCore.MySql.IntegrationTests/Tests/Models/ExpressionTest.cs index 0393977ac..c9a9186d3 100644 --- a/test/EFCore.MySql.IntegrationTests/Tests/Models/ExpressionTest.cs +++ b/test/EFCore.MySql.IntegrationTests/Tests/Models/ExpressionTest.cs @@ -437,8 +437,8 @@ public async Task MySqlToStringConvertTranslator() public async Task MySqlCastTranslator() { var result = await _db.DataTypesSimple.Select(m => new { - IntToUlong = (ulong) m.TypeInt, - IntToDecimal = (decimal) m.TypeInt, + IntToUlong = (ulong)m.TypeInt, + IntToDecimal = (decimal)m.TypeInt, IntToString = "test" + m.TypeInt, } ).FirstOrDefaultAsync(); @@ -446,6 +446,53 @@ public async Task MySqlCastTranslator() Assert.NotNull(result); } + [Fact] + public async Task MySqlTimeStampTotalsTranslator() + { + var result = await _db.DataTypesSimple.Select(m => + new { + m.Id, + m.TypeTimeSpan.TotalNanoseconds, + m.TypeTimeSpan.TotalMilliseconds, + m.TypeTimeSpan.TotalSeconds, + m.TypeTimeSpan.TotalMinutes, + m.TypeTimeSpan.TotalHours, + m.TypeTimeSpan.TotalDays, + }).FirstOrDefaultAsync(m => m.Id == _simple.Id); + + Assert.Equal(_simple.TypeTimeSpan.TotalNanoseconds, result.TotalNanoseconds); + Assert.Equal(_simple.TypeTimeSpan.TotalMilliseconds, result.TotalMilliseconds); + Assert.Equal(_simple.TypeTimeSpan.TotalSeconds, result.TotalSeconds); + Assert.Equal(_simple.TypeTimeSpan.TotalMinutes, result.TotalMinutes); + Assert.Equal(_simple.TypeTimeSpan.TotalHours, result.TotalHours); + Assert.Equal(_simple.TypeTimeSpan.TotalDays, result.TotalDays); + } + + [Fact] + public async Task MySqlTimeStampTotalsCalculationTranslator() + { + var custom = new TimeSpan(1, 2, 3, 4); + + var result = await _db.DataTypesSimple + .Select(x => custom) + .Select(c => + new { + c.TotalNanoseconds, + c.TotalMilliseconds, + c.TotalSeconds, + c.TotalMinutes, + c.TotalHours, + c.TotalDays, + }).FirstOrDefaultAsync(); + + Assert.Equal(custom.TotalNanoseconds, result.TotalNanoseconds); + Assert.Equal(custom.TotalMilliseconds, result.TotalMilliseconds); + Assert.Equal(custom.TotalSeconds, result.TotalSeconds); + Assert.Equal(custom.TotalMinutes, result.TotalMinutes, tolerance: 0.00001); + Assert.Equal(custom.TotalHours, result.TotalHours, tolerance: 0.00001); + Assert.Equal(custom.TotalDays, result.TotalDays, tolerance: 0.00001); + } + } }