it-source

엔티티 프레임워크 MariaDb 문자열 날짜 문제

criticalcode 2022. 12. 19. 21:24
반응형

엔티티 프레임워크 MariaDb 문자열 날짜 문제

EF core와 MariaDb 데이터베이스에 문제가 있는 것 같습니다.우선, 데이터베이스를 조금 변경하는 명백한 움직임은 할 수 없기 때문에, 그 옵션은 사용할 수 없습니다.

연락처 날짜가 보관되어 있는 "연락처" 테이블이 있습니다.이 연락처 날짜는 날짜만 포함하는 문자열 값 형식으로 보관됩니다. 즉, 2020년 8월 30일의 2020-08-30입니다.이 필드의 EF 코어 매핑은 다음과 같습니다.

entity.Property(e => e.ContactDate)
                    .IsRequired()
                    .HasColumnName("contactDate")
                    .HasColumnType("varchar(255)")

여기서 e.ContactDate는 DateTime 속성입니다.

코드의 속성을 사용하면 날짜/시간이 예상대로 작동하며 데이터뱅크에 저장된 날짜가 포함됩니다.만세!

그러나 이 문제는 해당 날짜에 쿼리할 때 발생합니다.이 쿼리가 지정되면:

SELECT `c7`.`contactDate`
    FROM `contacts` AS `c7`
    WHERE (`f`.`uuid` = `c7`.`fileUuid`) AND (`c7`.`numberOfContacts` > 0)
    ORDER BY `c7`.`contactDate`
    LIMIT 1) <= @__endDate_1) AND (`f`.`closingDate` IS NULL OR ((`f`.`closingDate` >= @__startDate_2)

및 startDate "endDate"로 됩니다.를 들어, 「」라고 하는 것은,@__endDate_1='2019-12-31 00:00:00'그리고 여기에 문제가 있습니다.MySql ® contactDate db endDate > endDate > 。사용하다SELECT "2020-02-04" >= "2020-02-04 00:00:00"이 0은 0입니다.

제가 할 수 있는 방법이 있나요?

  • 또는 모든 쿼리에서 contactDate를 타임스탬프가 있는 값으로 캐스팅하는 EF를 변경합니다.
  • 아니면 시간 값 없이 날짜 값을 날짜 값으로 캐스팅하는 EF를 변경하시겠습니까?

EF의 DateTime to Date 문자열 래퍼로 새로운 "Date" 클래스를 도입하는 세 번째 솔루션에 대해 어떻게 생각하십니까?

감사합니다!

와 함께

.HasColumnType("varchar(255)")

EF Core 내장 DateTime을 문자열 값 변환기로 활성화하고 있습니다.문서화된 것은 아니지만, 이 정보는DateTime명령어 때을 에(를) 설정합니다.string다음 형식(또는 유사한 형식)을 사용합니다.

"yyyy\\-MM\\-dd HH\\:mm\\:ss.FFFFFFF"

필요한 것은 이런 형식입니다.

"yyyy\\-MM\\-dd"

커스텀 값 컨버터를 설정하면 다음과 같이 할 수 있습니다.

entity.Property(e => e.ContactDate)
    .HasConversion(
        value => value.ToString("yyyy\\-MM\\-dd"),
        dbValue => DateTime.Parse(dbValue, CultureInfo.InvariantCulture)
    )
    // the rest is the same
    .IsRequired()
    .HasColumnName("contactDate")
    .HasColumnType("varchar(255)"); // or .IsUnicode(false).HasMaxLength(255)

이치노는 「」가 됩니다.@__endDate_1='2019-12-31'2번입니다.

글머리 기호 #1은 불가능합니다.

한 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★Date유형은 가능하지만, 많은 인프라 배관 코드가 필요하기 때문에 쉽지는 않습니다.노다 시간, 넷토폴로지관심 있으면 그 실장을 볼 수 있지만 IMHO는 (간단한) 커스텀 밸류 타입을 지원하는 것이 EF Core 우선 사항이 아니기 때문에 (현재는) 더 나은 지원을 받을 때까지 가치 변환기를 계속 사용하고 싶습니다.

@IvanStoev 답변은 요청대로, 예상대로 작동합니다.

동작하고 있는 콘솔 프로그램의 예를 다음에 나타냅니다.

using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Pomelo.EntityFrameworkCore.MySql.Storage;

namespace IssueConsoleTemplate
{
    public class Contact
    {
        public int ContactId { get; set; }
        public DateTime ContactDate { get; set; }
    }

    public class Context : DbContext
    {
        public virtual DbSet<Contact> Contacts { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseMySql("server=127.0.0.1;port=3306;user=root;password=;database=So63655418",
                    b => b
                        .ServerVersion(new ServerVersion("8.0.20-mysql")))
                .UseLoggerFactory(LoggerFactory.Create(b => b
                    .AddConsole()
                    .AddFilter(level => level >= LogLevel.Information)))
                .EnableSensitiveDataLogging()
                .EnableDetailedErrors();
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Contact>(
                entity =>
                {
                    entity.Property(e => e.ContactDate)
                        .HasColumnType("varchar(255)")
                        .HasConversion(
                            v => v.ToString(@"yyyy\-MM\-dd"),
                            v => DateTime.Parse(v, CultureInfo.InvariantCulture));

                    entity.HasData(
                        new Contact
                        {
                            ContactId = 1,
                            ContactDate = new DateTime(2020, 9, 1, 14, 42, 59, 123), // <-- time will be dropped
                        });
                });
        }
    }

    internal static class Program
    {
        private static void Main()
        {
            using var context = new Context();

            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();
            
            var dateParameter = new DateTime(2020, 9, 1, 21, 11, 1, 456); // <-- time will be dropped
            var contactWithExactDate = context.Contacts.SingleOrDefault(c => c.ContactDate == dateParameter);
            
            Debug.Assert(contactWithExactDate != null);
            Debug.Assert(contactWithExactDate.ContactDate == new DateTime(2020, 9, 1));
        }
    }
}

다음 SQL 문을 생성합니다.

warn: Microsoft.EntityFrameworkCore.Model.Validation[10400]
      Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data, this mode should only be enabled during development.

info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 3.1.8 initialized 'Context' using provider 'Pomelo.EntityFrameworkCore.MySql' with options: ServerVersion 8.0.20 MySql SensitiveDataLoggingEnabled DetailedErrorsEnabled

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (77ms) [Parameters=[], CommandType='Text', CommandTimeout='30']

      DROP DATABASE `So63655418`;

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (11ms) [Parameters=[], CommandType='Text', CommandTimeout='30']

      CREATE DATABASE `So63655418`;

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (70ms) [Parameters=[], CommandType='Text', CommandTimeout='30']

      CREATE TABLE `Contacts` (
          `ContactId` int NOT NULL AUTO_INCREMENT,
          `ContactDate` varchar(255) NOT NULL,
          CONSTRAINT `PK_Contacts` PRIMARY KEY (`ContactId`)
      );

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (11ms) [Parameters=[], CommandType='Text', CommandTimeout='30']

      INSERT INTO `Contacts` (`ContactId`, `ContactDate`)
      VALUES (1, '2020-09-01');

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (8ms) [Parameters=[@__dateParameter_0='2020-09-01' (Nullable = false) (Size = 255)], CommandType='Text', CommandTimeout='30']

      SELECT `c`.`ContactId`, `c`.`ContactDate`
      FROM `Contacts` AS `c`
      WHERE `c`.`ContactDate` = @__dateParameter_0
      LIMIT 2

프로그램은 어설션 없이 실행되며 로그에 기록된 SQL 문은 사용된 파라미터를 올바르게 나타냅니다.@__dateParameter_0='2020-09-01'C# 코드에 시간 값을 명시적으로 사용했는데도 말이죠.

당신이 작성한 대로 포맷을 데이터베이스에 남겼을 거예요.하지만 나는 질문을 바꿨을 것이다.이 경우 이 스레드에 표시된 것과 같은 날짜 형식의 캐스트:

MySQL DATETIME 필드의 문자열과 DATETIME 비교

SELECT * FROM contacts WHERE DATE(startTime) > '2020-08-30'

...또는 MariaDB 공식 문서: DATETIME 구문

답변을 기다리는 시간이 길었을 때 날짜 유형 접근 방식을 사용하여 특정 변환을 시도했습니다.기적적으로 EF는 저장된 모든 문자열 날짜를 쿼리에서 datetime 개체로 변환했습니다.정말 훌륭했고, 성공했어요!

내 날짜 개체의 코드입니다.

public struct Date: IComparable<Date>, IComparable<DateTime>, IComparable, IEquatable<Date>
    {
        
        /// <summary>
        /// Turns a DateTime into a Date
        /// </summary>
        /// <param name="dateTime"></param>
        public Date(DateTime dateTime)
        {
            _date = dateTime.Date;
        }

        private readonly DateTime _date;

        public static implicit operator Date(DateTime value) => new Date(value);
        public static implicit operator DateTime(Date value) => value._date;

        public int CompareTo(Date other) => _date.CompareTo(other._date);
        public int CompareTo(DateTime other) => _date.CompareTo(new Date(other));
        public int CompareTo(object obj) => _date.CompareTo(obj);
        public bool Equals(Date other) => _date.Equals(other._date);
        public override bool Equals(object obj) => obj is Date other && Equals(other);
        public override int GetHashCode() => _date.GetHashCode();
        
    }

EF로의 전환은 다음과 같습니다.

public static PropertyBuilder<Date?> HasDateConversion(this PropertyBuilder<Date?> propertyBuilder) =>
            propertyBuilder
                .HasConversion(date => 
                        date.HasValue
                        ? ParseDate(date.Value)
                        : null,
                    str =>
                        string.IsNullOrWhiteSpace(str) 
                            ? null
                            : (Date?)ParseString(str));

그 결과 선택한 모든 날짜가 다음과 같이 선택되었습니다.

SELECT * FROM `contacts` AS `c`
WHERE CAST(`c`.`contactDate` AS datetime(6)) < '2020-01-03 05:01:03.000000'
LIMIT 1

이렇게 해서 나의 문제는 해결되었다:) 만세!

언급URL : https://stackoverflow.com/questions/63655418/entity-framework-mariadb-date-as-string-problem

반응형