Entity Framework 5 неправильный тип данных в запросе - программирование
Подтвердить что ты не робот

Entity Framework 5 неправильный тип данных в запросе

Мы используем EF 5.0 в качестве нашего ORM для выбора в нашем бизнес-решении, структурированном в n-слойном стиле со всем развязанным и красивым корнем композиции с ninject.

В последнее время мы создаем базу данных, которая использует разделение под ними, и у нас есть некоторые важные индексы на столбцах DATE.

Столбцы правильно объявлены на Sql Server 2008. Мы также добавили правильный тип данных в сопоставления EF с инструкцией HasColumnType("Date").

Тем не менее, при запросе таблицы через Linq to Entities параметры, которые мы фильтруем датами, создаются типа DateTime2, и даже столбцы передаются в DateTime2 в запросах, чтобы тип соответствовал параметрам.

У этого поведения есть несколько проблем. Прежде всего, если я говорю EF-движку, что столбец в базе данных DATE, почему он должен отличать его от DateTime2?

Во-вторых, это приведение базы данных игнорирует индексы, поэтому не использует разделение. У нас есть один год на физическое разбиение, и если я задам диапазон дат, скажем так, февраль 2013 года в марте 2013 года, сканирование должно происходить только на одном физическом разделе. Он работает корректно, если вручную использовать правильный тип данных DATE, но с приведением в DateTime2 все разделы будут отсканированы, что резко сократит производительность.

Теперь я уверен, что упускаю что-то, потому что было бы довольно глупо, что Microsoft ORM не работает на Microsoft Sql Server.

Мне не удалось найти документацию о том, как EF использует правильные типы данных в запросах, поэтому я прошу здесь. Любая помощь будет оценена.

Спасибо.

4b9b3361

Ответ 1

Я не считаю, что это возможно в Entity Framework. Это запрошенное улучшение, вероятно, сделает то, что вам нужно. Эта страница MSDN показывает сопоставление типов SQL Server и типов CLR. Обратите внимание, что date поддерживается и сопоставляется с DateTime, но поскольку несколько типов SQL сопоставляются с одним и тем же типом CLR, EF, очевидно, выбирает один тип SQL в качестве предпочтительного эквивалента типа CLR.

Можете ли вы обернуть свой код выбора в хранимой процедуре? Если это так, это будет разумным решением. Вы можете использовать DbSet {T}.SqlQuery для материализации объектов при выполнении sp.

Пример кода

Следующее короткое консольное приложение демонстрирует концепцию. Обратите внимание на то, что связанные объекты успешно загружены.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;

namespace ConsoleApplication1
{
    [Table("MyEntity")]    
    public class MyEntity
    {
        private Collection<MyRelatedEntity> relatedEntities;

        [Key]
        public virtual int MyEntityId { get; set; }

        [DataType(DataType.Date)]
        public virtual DateTime MyDate { get; set; }

        [InverseProperty("MyEntity")]
        public virtual ICollection<MyRelatedEntity> RelatedEntities
        {
            get
            {
                if (this.relatedEntities == null)
                {
                    this.relatedEntities = new Collection<MyRelatedEntity>();
                }

                return this.relatedEntities;
            }
        }

        public override string ToString()
        {
            return string.Format("Date: {0}; Related: {1}", this.MyDate, string.Join(", ", this.RelatedEntities.Select(q => q.SomeString).ToArray()));
        }
    }

    public class MyRelatedEntity
    {
        [Key]
        public virtual int MyRelatedEntityId { get; set; }

        public virtual int MyEntityId { get; set; }

        [ForeignKey("MyEntityId")]
        public virtual MyEntity MyEntity { get; set; }

        public virtual string SomeString { get;set;}
    }

    public class MyContext : DbContext
    {
        public DbSet<MyEntity> MyEntities
        {
            get { return this.Set<MyEntity>(); }
        }
    }

    class Program
    {
        const string SqlQuery = @"DECLARE @date date; SET @date = @dateIn; SELECT * FROM MyEntity WHERE MyDate > @date";

        static void Main(string[] args)
        {
            Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());

            using (MyContext context = new MyContext())
            {
                context.MyEntities.Add(new MyEntity
                    {
                        MyDate = DateTime.Today.AddDays(-2),
                        RelatedEntities =
                        {
                            new MyRelatedEntity { SomeString = "Fish" },
                            new MyRelatedEntity { SomeString = "Haddock" }
                        }
                    });

                context.MyEntities.Add(new MyEntity
                {
                    MyDate = DateTime.Today.AddDays(1),
                    RelatedEntities =
                        {
                            new MyRelatedEntity { SomeString = "Sheep" },
                            new MyRelatedEntity { SomeString = "Cow" }
                        }
                });

                context.SaveChanges();
            }

            using (MyContext context = new MyContext())
            {
                IEnumerable<MyEntity> matches = context.MyEntities.SqlQuery(
                    SqlQuery,
                    new SqlParameter("@dateIn", DateTime.Today)).ToList();

                // The implicit ToString method call here invokes lazy-loading of the related entities.
                Console.WriteLine("Count: {0}; First: {1}.", matches.Count(), matches.First().ToString());
            }

            Console.Read();
        }
    }
}

Ответ 2

Диапазон типов DateTime в .NET и SQL-сервере отличается.

Диапазон данных .NET DateTime: 0000-Jan-01 до 9999-Dec-31 SQL DateTime: 1900-Jan-01, 2079-Jun-06

Чтобы соответствовать диапазону, EF преобразует ваш тип DateTime в SQL Server DateTime2, который имеет тот же диапазон, что и диапазон .NET DateTime.

Я думаю, что ваша проблема возникает только тогда, когда у вас есть свойство даты, которое не назначено и передано на сервер SQL через EF. Когда дата не назначена с определенным значением, по умолчанию используется DateTime.Min, который является 0000-Jan-01, и это вызывает преобразование в DateTime2.

Я думаю, вы можете либо сделать свойство DateTime nullable → DateTime? или напишите помощника, чтобы преобразовать DateTime.Min в соответствие с диапазоном SQL DateTime.

Надеюсь, это поможет.

Ответ 3

У меня нет решения. Я никогда не видел запроса LINQ-to-Entites с параметрами .NET DateTime, которые использовали тип параметра в SQL-запросе, отличном от datetime2(7). Я сомневаюсь, что вы можете избавиться от этого. Просто попробуйте объяснить, почему это так:

Предположим, что у вас есть объект с свойством SomeNumber типа int. Какой результат вы ожидаете от такого запроса:

....Where(e => e.SomeNumber >= 7.3)....

Возможно, все объекты, где SomeNumber 8 или больше. Если параметр (с плавающей запятой десятичный) 7.3 будет передан типу int, который хранится в базе данных, вам нужно решить, как округлить 7.3 - до 7 (приведет к неправильному результату) или к 8? Хорошо, можно сказать, потому что мой запрос говорит >=, и я знаю, что тип в БД - это целое число, округление до 8 должно быть правильным. Если бы я использовал <=, округление до 7 должно быть правильным. Если бы я использовал ==, о... Я вообще не должен округлять, или я знаю, что результат должен быть пустым, и я мог бы напрямую перевести это предложение Where на false. И != - true. Но параметр 7.0 является частным случаем. Etc....

Ну, дилемма в этом примере имеет простое решение: во-первых, решите на стороне клиента, что вы хотите, используя параметр int (7 или 8).

Решение с DateTime не так просто, потому что .NET не имеет типа Date. Запросы с параметрами DateTime всегда будут иметь форму...

DateTime dateTime = new DateTime(2013, 5, 13, 10, 30, 0);
....Where(e => e.SomeDateTime >= dateTime)....

... и если SomeDateTime хранится как Date в SQL Server, у вас снова возникает проблема округления. Должен ли я указывать на 2013.05.13 или 2013.05.14? Для запроса выше клиент наверняка ожидает все сущности с датой 14 и более поздней.

Хорошо, вы могли бы сделать это умным, например: если временная часть моего параметра DateTime - полночь, отбрасывается на часть даты. Если я использую >=, на следующий день и т.д. И т.д. Или вы всегда можете применить к datetime2(7). Тогда результат запроса всегда корректен и как ожидает (.NET) клиент. Правильно... но, возможно, с использованием субоптимального индекса.