Как мне сопоставить пользовательские типы в Linq с Sql?

2

У меня есть класс Customer, который содержит свойство MyProperty, которое имеет тип MyCustomType. Я хочу сохранить значение свойства в базе данных как текст. В дизайнере я задал тип "MyType" и тип данных сервера "varchar (10)". Когда я создаю проект, я получаю следующую ошибку:

DBML1005: Mapping between DbType 'varchar(10)' and Type 'MyType' in 
Column 'MyProperty' of Type 'Customer' is not supported.

Теперь это имеет смысл, поскольку Linq to Sql не имеет никакого способа узнать, как преобразовать мой пользовательский тип. Поэтому я предполагаю, что мне нужно реализовать какие-то методы Parse (string) и ToString() в MyCustomType, но я не могу найти никакой документации по этому вопросу.

Итак, как мне сопоставить пользовательские типы в Linq to Sql?

Теги:
linq-to-sql

3 ответа

3
Лучший ответ

Класс для varchar? Я не знаю каких-либо функциональных возможностей, которые поддерживают это в LINQ-to-SQL. Ваш лучший выбор может быть простым свойством (он может быть приватным, если вам нужно):

[Column(Name="ColumnName", DbType="varchar(10) NULL", CanBeNull=true)]
private string MyPropertyString {
    get { /* serialize MyProperty yourself */ }
    set { /* deserialize MyProperty yourself */ }
}
public MyCustomType MyProperty {get;set;}

(т.е. прочитайте или обновите свое фактическое свойство MyProperty)

Я также ожидаю, что вам придется оставить этот конструктор off и добавить его самостоятельно (и свойство MyProperty) в частичном классе.

  • 0
    Хороший обходной путь, он достигает того, что мне было нужно.
  • 0
    Спасибо, что удержали меня от сложного обходного пути. Это потрясающе
Показать ещё 1 комментарий
3

Расширяясь по шаблону доступа к частным/общедоступным данным выше, я сделал следующее.

1) Выберите тип данных БД, на который будет отображаться настраиваемый тип. В моем случае пользовательский объект реализовал шаблон перечисления типа безопасный, так что существует естественное отображение из пользовательского типа (перечисление констант) и типа интегрального типа DB (в моем случае tinyint). Я могу видеть базовый тип БД как строку, а затем использовать JavaScriptSerializer Class для шага 2.

2) Внедрите в своем классе возможность преобразования между настраиваемым типом и типом данных БД. Преобразование должно быть двунаправленным: CustomObject → Тип DB и DBType → CustomObject Для меня это реализовано:

2a) Порядковый номер перечисляемого типа в качестве члена моего класса нумерации типов. Это обрабатывает кастинг из моего пользовательского типа в int.

2b) Статический член, findByOrdinal(), который обрабатывает приведение из int в мой пользовательский тип.

3) Используйте Linq To SQL для создания связи между столбцом данных, данным настраиваемым типом данных и общедоступным именем. Это делается путем вставки имени пользовательского объекта в поле со списком типа свойства элемента данных.

4) Очистите и перестройте проект, чтобы обеспечить компиляцию чистой. Если процесс сборки жалуется, что пользовательский тип не существует в контексте файла <ProjectName> .designer.cs, найденного под файлом <ProjectName> .dbml. Проблемы регенерации обсуждаются ниже. Если вы выполните код в контексте БД, вы получите ошибку кастинга выше.

Linq to SQL создал несколько файлов частичного класса, когда он создал файл .designer.cs. Нам нужно будет добавить к этому частичному классу код, который не будет уничтожен, когда Linq to SQL восстанавливает код в <ProjectName> .designer.cs.

5) Создайте новый файл класса, который содержит исходный код для частичных классов, созданных Linq to SQL. Для этого щелкните правой кнопкой мыши на <ProjectName> .dbml в Solution Explore и выберите View Code из контекстного меню. Это создаст в проекте новый файл <ProjectName> .cs, который продолжит определение частичных классов, найденных в <ProjectName> .designer.cs

6) Вырезать выделенный код из <ProjectName> .designer.cs и вставить код в <ProjectName> .cs

Ссылка на SQL создала внутри <ProjectName> .designer.cs частичный класс, который сопоставляет таблицу с классом.

Нам нужно удалить найденный код доступа к данным <ProjectName> .designer.cs и создать нашу собственную версию этого доступа к данным в <ProjectName> .cs.

Я сделал это, выполнив прямое вырезание и вставку кода, созданного Linq, и изменил код на мои пупочки после того, как код был в <ProjectName> .cs. Операции cut, paste, mutate:

6a) Измените тип хранилища данных из MyCustomType на то, что легко сопоставляется с типом данных БД для этого столбца в базе данных. В моем случае я выбрал int как внутреннее хранилище данных для ввода tinyint из столбца DB.

6b) Измените методы getter и setter публично названного элемента данных, созданного Linq to SQL.

Подробнее о исходном коде Linq и окончательной, мутированной версии см. приведенный ниже код.

7) Удалите из <ProjectName> .designer.cs, используя элемент, добавленный на шаге 4, чтобы заставить компилятор временно принять присутствие настраиваемого типа в коде, создаваемом Ling.

8) Очистите и перестройте проект, чтобы обеспечить компиляцию чистой. Ничто в .designer.cs не должно зависеть от ссылки на сборку, которая определяет ваш собственный класс. таким образом Linq to SQL может свободно генерировать <ProjectName> .designer.cs по желанию, и это не повлияет на код, найденный в .cs.

Примечание. Класс С#, который сопоставляется с таблицей DB, обычно находится под полным контролем генератора кода Linq to SQL. Разделив определение этого класса между двумя исходными файлами, исходный код в двух файлах теперь должен быть скоординирован вручную. Это означает, что технический долг долгосрочного обслуживания между этими исходными файлами был принят.

9) Проверьте интерфейс. Код Getter/Setter сопоставляет мой настраиваемый тип (и) с базовым хранилищем данных С# (и оттуда к столбцу DB в таблице DB). Из-за этого код, потребляющий данные, может опираться на сильную типизацию из класса С#.

Вышеприведенные шаги более активны и не сразу очевидны. Я сделал это, потому что правило номер один с визуальными мастер-студиями (создатели кода для создания кода):

Не сражайтесь с мастером.

Генератор кода Linq to SQL генерирует много кода от моего имени. Этот подход позволяет мне претендовать на владение только теми небольшими частями в кодовой цепочке С#/Linq/SQL/DBColum, которые имеют дело с классом С#, который является моим настраиваемым типом. Если код Linq будет регенерирован, компилятор будет генерировать ошибки компиляции, но на этом этапе поддержка кода заключается в удалении сгенерированного кода Linq to SQL, который теперь является дубликатом моего кода в <ProjectName> .cs.

Не идеально, но он должен работать в долгосрочной перспективе.

Итоговый код:

    namespace TestRepository
{
    using System;
    using Framework; // Contains definition of the TestPropertyDataType class

    /*
     *******************************************************************************************************
     * See: http://stackoverflow.com/questions/1097090/how-do-i-map-custom-types-in-linq-to-sql
     * 
     * Expanding on the private/public interface mentioned here and using the fact that Linq auto generates a 
     * public/private pairing with the getter/setter code, I mutated the Linq gneerated code to 
     * quietly convert from type System.Byte to the custom type, TestPropertyDataType.
     * 
     * Original code as generated by Linq to SQL which I lifted from the file: TestRepository.designer.cs
     * *****************************************************************************************************
     * [global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.TEST_ACTION_PROPERTIES")]
     * public partial class TestActionPropertyMetadata : INotifyPropertyChanging, INotifyPropertyChanged
     * {
     *     // <Lots of Linq-generated code>
     *         
     *     private TestPropertyDataType _DataType;
     * 
     *     // more Linq-generated code>
     *     
     *     #region Extensibility Method Definitions
     *     partial void OnDataTypeChanging(TestPropertyDataType value);
     *     #endregion
     *     
     *     // <Lots more Linq-generated code>
     * 
     *     [global::System.Data.Linq.Mapping.ColumnAttribute(Name="DATA_TYPE", Storage="_DataType", DbType="TinyInt NOT NULL", CanBeNull=false)]
     *     public TestPropertyDataType DataType
     *     {
     *         get
     *         {
     *             return this._DataType;
     *         }
     *         set
     *         {
     *             if ((this._DataType != value))
     *             {
     *                 this.OnDataTypeChanging(value);
     *                 this.SendPropertyChanging();
     *                 this._DataType = value;
     *                 this.SendPropertyChanged("DataType");
     *                 this.OnDataTypeChanged();
     *             }
     *         }
     *     }
     * }
     * *****************************************************************************************************
     */
    public partial class TestActionPropertyMetadata
    {
        private byte _DataType;
        partial void OnDataTypeChanging(TestPropertyDataType value);

        [global::System.Data.Linq.Mapping.ColumnAttribute(Name = "DATA_TYPE", Storage = "_DataType", DbType = "TinyInt NOT NULL", CanBeNull = false)]
        public TestPropertyDataType DataType
        {
            get
            {
                return TestPropertyDataType.findByOrdinal(this._DataType);
            }
            set
            {
                if ((this.DataType != value))
                {
                    this.OnDataTypeChanging(value);
                    this.SendPropertyChanging();
                    this._DataType = (byte)value.Ordinal;
                    this.SendPropertyChanged("DataType");
                    this.OnDataTypeChanged();
                }
            }
        }
    }
}
  • 0
    Этот подход не работает для меня. Я делаю все то же самое, и когда я выполняю простой db.datatable.first() он выдает мне System.InvalidCastException: 'Не удалось преобразовать тип' <BASETYPE> 'в тип' <CUSTOMTYPE> '.'
1

Правильный подход (из MSDN):

Если класс реализует Parse() и ToString(), вы можете сопоставить объект с любым типом текста SQL (CHAR, NCHAR, VARCHAR, NVARCHAR, TEXT, NTEXT, XML). Объект хранится в базе данных, отправляя значение, возвращаемое ToString(), в столбец сопоставленной базы данных. Объект восстанавливается путем вызова Parse() в строке, возвращаемой базой данных.

  • 1
    Правильный ответ, жаль, что они решили поддерживать только текстовые типы на стороне SQL.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню