我有一个Customer类,它包含一个属性MyProperty,它是一个自定义类型MyCustomType。我想将数据库中的属性值保存为文本。在设计器中,我将Type设置为'MyType',将Server Data Type设置为'varchar(10)'。当我构建项目时,我收到以下错误:
DBML1005: Mapping between DbType 'varchar(10)' and Type 'MyType' in
Column 'MyProperty' of Type 'Customer' is not supported.
现在有意义,因为Linq to Sql无法知道如何转换我的自定义类型。所以我假设我必须在MyCustomType上实现某种Parse(string)和ToString()方法,但我找不到任何关于此的文档。
那么,如何将Linq中的自定义类型映射到Sql?
答案 0 :(得分:4)
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
财产)
我还希望您必须将关闭设计器,并将其自己(以及MyProperty
属性)添加到部分类中。
答案 1 :(得分:4)
扩展上面的私有/公共数据访问模式,我做了以下内容。
1)选择要映射自定义类型的DB数据类型。在我的例子中,自定义对象实现了类型安全的枚举模式,因此存在自定义类型(常量枚举)和数据库类型整数类型(在我的情况下为tinyint)的自然映射。我可以将基础数据库类型看作字符串,然后使用JavaScriptSerializer类进行第2步。
2)在您的类中实现在自定义类型和DB数据类型之间进行转换的能力。转换必须是双向的:CustomObject - > DB类型和DBType - > CustomObject 对我而言,它正在实施:
2a)枚举类型的序数值作为我的类型安全枚举类的成员。这会处理从我的自定义类型转换为int。
2b)一个静态成员findByOrdinal(),它处理从int到我的自定义类型的强制转换。
3)使用Linq To SQL在数据列,此自定义数据类型和公共名称之间创建链接。这是通过将自定义对象的名称粘贴到数据成员属性的“类型”组合框中来完成的。
4)清理并重建项目以确保编译清洁。如果构建过程抱怨在< ProjectName> .dbml文件下面的< ProjectName> .designer.cs文件的上下文中不存在自定义类型。下面讨论再生问题。如果在DB上下文中执行代码,则会出现上面的转换错误。
Linq to SQL在创建.designer.cs文件时创建了许多部分类文件。我们需要添加这个部分类代码,当Linq to SQL重新生成< ProjectName> .designer.cs中的代码时,这些代码不会被销毁。
5)创建一个新的类文件,其中包含由Linq to SQL创建的分部类的源代码。为此,请在Solution Explore中右键单击< ProjectName> .dbml,然后从上下文菜单中选择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后将代码更改为我的目标。剪切,粘贴,变异操作是:
6a)将数据存储类型从MyCustomType更改为可轻松映射到数据库中该列的DB数据类型的内容。在我的例子中,我选择int作为来自DB列的tinyint的内部数据存储。
6b)将Linq创建的公开命名数据成员的getter和setter方法改为SQL。
有关原始Linq代码和最终变异版本的详细信息,请参阅下面的代码。
7)从< ProjectName> .designer.cs中删除步骤4中添加的using语句,以使编译器暂时接受Ling-Generated代码中自定义类型的存在。
8)清理并重建项目以确保编译清洁。 .designer.cs中的任何内容都不应该依赖于对定义自定义类的程序集的引用。这样,Linq to SQL可以随意生成< ProjectName> .designer.cs,这不会影响.cs中的代码。
注意:映射到数据库表的C#类通常完全由Linq to SQL代码生成器控制。通过在两个源文件之间拆分此类的定义,现在必须手动协调两个文件中的源代码。这意味着这些源文件之间的长期维护技术债务已被接受。
9)测试界面。 Getter / Setter代码将我的自定义类型映射到底层C#数据存储(以及从那里到DB表上的DB列)。因此,使用数据的代码可以依赖于C#类的强类型。
上述步骤更为复杂,并不是立即明白的。我之所以这样做是因为规则第一,使用了visual studio向导(al代码生成向导):
不要与巫师作战。
Linq to SQL代码生成器代表我生成了大量代码。这种方法让我声称只拥有C#/ Linq / SQL / DBColum代码链中的那些小块,它们处理的是我的自定义类型的C#类。如果重新生成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();
}
}
}
}
}
答案 2 :(得分:1)
正确的方法(来自MSDN)是:
如果类实现了Parse()和ToString(),则可以将对象映射到任何SQL文本类型(CHAR,NCHAR,VARCHAR,NVARCHAR,TEXT,NTEXT,XML)。通过将ToString()返回的值发送到映射的数据库列,该对象存储在数据库中。通过在数据库返回的字符串上调用Parse()来重构对象。