nullable false(hibernate如果设置了unique=true,还有必要设置nullable=false吗)

本文目录
- hibernate如果设置了unique=true,还有必要设置nullable=false吗
- 在entity framework中怎么调用存储过程
- hibernate中@Entity和@Table的区别
- hibernate里@Column(name = “COST_PRICE“, unique = true, nullable = false, precision = 10,scale = 2)
- .net datetime加什么意思
- C#中的是什么意思
hibernate如果设置了unique=true,还有必要设置nullable=false吗
这两个互相不可取代,不存在设置了一个就不需要另一个道理。
详细解释:
unique=true是指这个字段的值在这张表里不能重复,所有记录值都要唯一,就像主键那样。
nullable=false是这个字段在保存时必需有值,不能还是null值就调用save去保存入库。
这两个用法是不同的,需要看个人需要,互相不可取代,根据个人需要可以两个都设置也可以只设置其中一个。
hibernate常用方法介绍:
delete(Object entity) 删除指定的持久化实例
在程序中一般先用 Assert.notNull和 Assert.isTrue断言entity是否为空 和 entity的id是否大于0,否则事务回滚。再用get(Class entityClass,Serializable id,LockMode lockMode)加锁查询出持久化实例,一般用lockMode.update悲观锁,最后用delete(Object entity)来删除此实例。deleteAll(Collection entities) 删除集合内全部持久化实例entities必须为持久化实例,否则报数据库异常错误。
find(String queryString) 根据HQL查询字符串来返回实例集合find方法在执行时会先查找缓存,如果缓存找不到再查找数据库,如果再找不到就会返回null。
get(Class entityClass,Serializable id)根据主键加载特定持久化实例在程序中一般先用 Assert.isTrue断言id是否大于0,若大于0继续执行,若查到数据则返回实例,否则返回空
不同于load,load若有数据则返回实例,否则报出ObjectNotFoundEcception异常,相比来说get效率高些。save(Object entity) 保存新的实例,在程序中一般先用 Assert.notNull断言实体是否为空,在进行保存。
在entity framework中怎么调用存储过程
一、使用存储过程的必要性
我们知道EF通过元数据,即概念模型(Concept Model)、存储模型(Storage Model)和概念/存储映射(C/S Mapping),和状态追踪(State Tracking)机制可以为基于模型的操作自动生成SQL。对于一些简单的项目开发,这是非常理想的,因为他们完全可以不用关注数据存储层面的东西,你可以采用一些完全不具有数据库知识的开发者。但是理想总归是理想,对于企业级开发来说,我们需要的是对数据库层面数据的操作有自己的控制。在这方面,我们可以随便举两个典型的场景:
逻辑删除:对于一些重要的数据,我们可能需要让它们永久保存。当我们试图“删除”这些数据的时候,我们并不是将它们从数据表中移除(物理删除),而是为这条记录作一个已经被删除的标记;
并发处理:为了解决相同的数据在获取和提交这段时间内被另一个用户修改或者删除,我们往往SQL层面增加并发控制的逻辑。比较典型的做法是在每一个表中添加一个VersionNo这样的字段,你可以采用TimeStamp,也可以直接采用INT或者GUID。在执行Update或者Delete的SQL中判断之前获取的VersionNo是否和当前的一致。
让解决这些问题,就不能使用EF为我们自动生成的SQL,只有通过使用我们自定义的存储过程。
二、实现存储过程自动匹配的必要条件
本篇文章提供的存储过程自动映射机制是通过代码生成的方式完成的。说白了,就是读取原来的.edmx模型文件,通过分析在存储模型中使用的数据表,导入基于该表的CUD存储过程;然后再概念/存储映射节点中添加实体和这些存储过程的映射关系。那实现这样的代码生成,需要具有如下三个固定的映射规则。
数据表名-存储过程名:这个映射关系帮助我们通过存储模型中的实体名找到对应CUD三个存储过程(如果实体是数据表);
数据表列名-存储过程参数名:当存储过程被执行的时候,通过这个映射让概念模型实体某个属性值作为对应的参数;
存储过程参数名-版本:当进行参数赋值的时候,通过这个映射决定是使用Original或者Current版本。
在实际的开发过程中,这样的标准存储过程一般都是通过代码生成器生成的(在我的文章《创建代码生成器可以很简单:如何通过T4模板生成代码?》中有过相应的实现),它们具有这样的映射关系。
基于这三种映射关系,我定义了如下一个名为IProcedureNameConverter的接口。其中OperationKind是我自定义的一个表示CUD操作类型的枚举。
1: public interface IProcedureNameConverter
2: {
3: string GetProcedureName(string tableName, OperationKind operationKind);
4: string GetColumnName(string parameterName);
5: DataRowVersion GetVersion(string parameterName);
6: }
7:
8: public enum OperationKind
9: {
10: Insert,
11: Update,
12: Delete
13: }
按照我们当前项目采用的命名规范,我定义了如下一个默认的DefaultNameConverter。它体现的是这样的映射关系,比如有个数据表明为T_USER(大写,单词之间用“_”隔开,并以T_为前缀),它对应的CUD存储过程名分别为:P_USER_I、P_USER_U和P_USER_D(大写,以代表存储过程的P_为前缀,后缀_I/U/D表示CUD操作类型,中间为去除前缀的表名)。如果列名为USER_ID,参数名为p_user_id(小写,加p_前缀)。如果需要用Original值为参数赋值,需要将p_前缀改成o_前缀(o_user_id)。
1: public class DefaultNameConverter: IProcedureNameConverter
2: {
3: public string GetProcedureName(string tableName, OperationKind operationKind)
4: {
5: switch (operationKind)
6: {
7: case OperationKind.Insert:
8: return string.Format("P_{0}_I", tableName.Substring(2));
9: case OperationKind.Update:
10: return string.Format("P_{0}_U", tableName.Substring(2));
11: default:
12: return string.Format("P_{0}_D", tableName.Substring(2));
13: }
14: }
15:
16: public string GetColumnName(string parameterName)
17: {
18: return parameterName.Substring(2).ToUpper();
19: }
20:
21: public DataRowVersion GetVersion(string parameterName)
22: {
23: if(parameterName.StartsWith("o"))
24: {
25: return DataRowVersion.Original;
26: }
27: else
28: {
29: return DataRowVersion.Current;
30: }
31: }
32: }
三、通过T4生成新的.edmx模型
我们采用的基于T4的代码生成,了解EF的应该对T4不会感到陌生了。如果对代码生成感兴趣的话,可以看看我的文章《与VS集成的若干种代码生成解决方案》。这里利用借助于T4 ToolBox这个开源工具箱,并采用SQL Server SMO获取存储过程信息。所有涉及到的文本转化都实现在如下一个ProcedureMappingTemplate类型中,由于内容较多,具体实现就忽略了,有兴趣的朋友可能下载源代码。ProcedureMappingTemplate具有两个构造函数的参数分别表示:源.edmx文件,服务器和数据库名,存储过程的Schema(默认为dbo)和具体的ProcedureNameConverter(默认为DefaultNameConverter)。
1: public class ProcedureMappingTemplate: Template
2: {
3: public XmlDocument SourceModel { get; private set; }
4: public IProcedureNameConverter ProcedureNameConverter { get; private set; }
5: public Database Database { get; private set; }
6: public string Schema { get; private set; }
7:
8: public ProcedureMappingTemplate(string sourceModelFile, string serverName, string databaseName);
9: public ProcedureMappingTemplate(string sourceModelFile, string serverName, string databaseName,
10: IProcedureNameConverter procedureNameConverter, string schema);
11:
12: protected virtual XElement GenerateStorageModelNode();
13: protected virtual XElement GenerateMappingNode();
14: public override string TransformText()
15: {
16: XElement newStorageModelNode = this.GenerateStorageModelNode();
17: XElement newMappingNode = this.GenerateMappingNode();
18:
19: XmlNode storageModelNode = this.SourceModel.GetElementsByTagName("edmx:StorageModels");
20: storageModelNode.InnerXml = newStorageModelNode.Elements().ToArray().ToString();
21:
22: XmlNode mappingNode = this.SourceModel.GetElementsByTagName("edmx:Mappings");
23: mappingNode.InnerXml = newMappingNode.Elements().ToArray().ToString();
24:
25: this.WriteLine("《?xml version=\"1.0\" encoding=\"utf-8\"?》");
26: this.Write(this.SourceModel.DocumentElement.OuterXml.Replace("xmlns=\"\"",""));
27: return GenerationEnvironment.ToString();
28: }
29: }
在使用过程中,你只需要在tt模板中创建这个ProcedureMappingTemplate对象,调用Render方法即可。
1: 《#@ template debug="true" hostSpecific="true" #》
2: 《#@ output extension=".edmx" #》
3: 《#@ assembly name="Microsoft.SqlServer.ConnectionInfo" #》
4: 《#@ assembly name="Microsoft.SqlServer.Smo" #》
5: 《#@ assembly name="Microsoft.SqlServer.Management.Sdk.Sfc" #》
6: 《#@ assembly name="$(TargetDir)Artech.ProcedureMapping.dll" #》
7: 《#@ import namespace="Artech.ProcedureMapping" #》
8: 《#@ include file="T4Toolbox.tt" #》
9: 《#
10: new ProcedureMappingTemplate(this.Host.ResolvePath("UserModel.edmx"),".","EFExtensions").Render();
11: #》
四、看看生成出来的.emdx
通过上面创建的TT模板(你指定的数据库中一定要存在具有相应映射关系的存储过程),新的.edmx模型文件会作为该tt文件的依赖文件被生成出来。而这个新生成的.edmx具有存储过程映射信息。具体来说,下面是原始的.edmx文件(只保留元数据节点)。
1: 《?xml version="1.0" encoding="utf-8"?》
***隐藏网址***
3: 《!-- EF Runtime content --》
4: 《edmx:Runtime》
5: 《!-- SSDL content --》
6: 《edmx:StorageModels》
***隐藏网址***
8: 《EntityContainer Name="ArtechUserModelStoreContainer"》
9: 《EntitySet Name="T_USER" EntityType="Artech.UserModel.Store.T_USER" store:Type="Tables" Schema="dbo" /》
10: 《/EntityContainer》
11: 《EntityType Name="T_USER"》
12: 《Key》
13: 《PropertyRef Name="USER_ID" /》
14: 《/Key》
15: 《Property Name="USER_ID" Type="varchar" Nullable="false" MaxLength="50" /》
16: 《Property Name="USER_NAME" Type="nvarchar" Nullable="false" MaxLength="50" /》
17: 《/EntityType》
18: 《/Schema》
19: 《/edmx:StorageModels》
20: 《!-- CSDL content --》
21: 《edmx:ConceptualModels》
***隐藏网址***
23: 《EntityContainer Name="EFExtensionsEntities" annotation:LazyLoadingEnabled="true"》
24: 《EntitySet Name="Users" EntityType="Artech.UserModel.User" /》
25: 《/EntityContainer》
26: 《EntityType Name="User"》
27: 《Key》
28: 《PropertyRef Name="ID" /》
29: 《/Key》
30: 《Property Name="ID" Type="String" Nullable="false" MaxLength="50" Unicode="false" FixedLength="false" /》
31: 《Property Name="Name" Type="String" Nullable="false" MaxLength="50" Unicode="true" FixedLength="false" /》
32: 《/EntityType》
33: 《/Schema》
34: 《/edmx:ConceptualModels》
35: 《!-- C-S mapping content --》
36: 《edmx:Mappings》
***隐藏网址***
38: 《EntityContainerMapping StorageEntityContainer="ArtechUserModelStoreContainer" CdmEntityContainer="EFExtensionsEntities"》
39: 《EntitySetMapping Name="Users"》
40: 《EntityTypeMapping TypeName="Artech.UserModel.User"》
41: 《MappingFragment StoreEntitySet="T_USER"》
42: 《ScalarProperty Name="ID" ColumnName="USER_ID" /》
43: 《ScalarProperty Name="Name" ColumnName="USER_NAME" /》
44: 《/MappingFragment》
45: 《/EntityTypeMapping》
46: 《/EntitySetMapping》
47: 《/EntityContainerMapping》
48: 《/Mapping》
49: 《/edmx:Mappings》
50: 《/edmx:Runtime》
51: 《/edmx:Edmx》
这是新生成的.edmx文件中的XML。
hibernate中@Entity和@Table的区别
Java Persistence API定义了一种定义,可以将常规的普通Java对象(有时被称作POJO)映射到数据库。
这些普通Java对象被称作Entity Bean。
除了是用Java Persistence元数据将其映射到数据库外,Entity Bean与其他Java类没有任何区别。
事实上,创建一个Entity Bean对象相当于新建一条记录,删除一个Entity Bean会同时从数据库中删除对应记录,修改一个Entity Bean时,容器会自动将Entity Bean的状态和数据库同步。
Java Persistence API还定义了一种查询语言(JPQL),具有与SQL相类似的特征,只不过做了裁减,以便处理Java对象而非原始的关系表。
hibernate中@Entity和@Table的区别:
@Entity说明这个class是实体类,并且使用默认的orm规则,即class名即数据库表中表名,class字段名即表中的字段名
如果想改变这种默认的orm规则,就要使用@Table来改变class名与数据库中表名的映射规则,@Column来改变class中字段名与db中表的字段名的映射规则
@Entity注释指名这是一个实体Bean,@Table注释指定了Entity所要映射带数据库表,其中@Table.name()用来指定映射表的表名。
如果缺省@Table注释,系统默认采用类名作为映射表的表名。实体Bean的每个实例代表数据表中的一行数据,行中的一列对应实例中的一个属性。
@Column注释定义了将成员属性映射到关系表中的哪一列和该列的结构信息,属性如下:
1)name:映射的列名。如:映射tbl_user表的name列,可以在name属性的上面或getName方法上面加入;
2)unique:是否唯一;
3)nullable:是否允许为空;
4)length:对于字符型列,length属性指定列的最大字符长度;
5)insertable:是否允许插入;
6)updatetable:是否允许更新;
7)columnDefinition:定义建表时创建此列的DDL;
8)secondaryTable:从表名。如果此列不建在主表上(默认是主表),该属性定义该列所在从表的名字。
@Id注释指定表的主键,它可以有多种生成方式:
1)TABLE:容器指定用底层的数据表确保唯一;
2)SEQUENCE:使用数据库德SEQUENCE列莱保证唯一(Oracle数据库通过序列来生成唯一ID);
3)IDENTITY:使用数据库的IDENTITY列莱保证唯一;
4)AUTO:由容器挑选一个合适的方式来保证唯一;
5)NONE:容器不负责主键的生成,由程序来完成。
@GeneratedValue注释定义了标识字段生成方式。
@Temporal注释用来指定java.util.Date或java.util.Calender属性与数据库类型date、time或timestamp中的那一种类型进行映射。
@Temporal(value=TemporalType.TIME)
***隐藏网址***
@Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private Long id;
private String name;
private int age;
private String addree;
// Getters and Setters
}
如果没有 @javax.persistence.Entity 和 @javax.persistence.Id 这两个注解的话,它完全就是一个典型的 POJO 的 Java 类,现在加上这两个注解之后,就可以作为一个实体类与数据库中的表相对应。他在数据库中的对应的表为:
图 1. Employee 表对应的 ER 图
映射规则:
1. 实体类必须用 @javax.persistence.Entity 进行注解;
2. 必须使用 @javax.persistence.Id 来注解一个主键;
3. 实体类必须拥有一个 public 或者 protected 的无参构造函数,之外实体类还可以拥有其他的构造函数;
4. 实体类必须是一个顶级类(top-level class)。一个枚举(enum)或者一个接口(interface)不能被注解为一个实体;
5. 实体类不能是 final 类型的,也不能有 final 类型的方法;
6. 如果实体类的一个实例需要用传值的方式调用(例如,远程调用),则这个实体类必须实现(implements)java.io.Serializable 接口。
将一个 POJO 的 Java 类映射成数据库中的表如此简单,这主要得益于 Java EE 5种引入的 Configuration by Exception 的理念,这个理念的核心就是容器或者供应商提供一个缺省的规则,在这个规则下程序是可以正确运行的,如果开发人员有特殊的需求,需要改变这个默认的规则,那么就是对默认规则来说就是一个异常(Exception)。
如上例所示:默认的映射规则就是数据库表的名字和对应的 Java 类的名字相同,表中列的名字和 Java 类中相对应的字段的名字相同。
现在我们可以改变这种默认的规则:
清单 2. 使用 @Table 和 @Column 注解修改映射规则
@Entity
@Table(name="Workers")
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Long id;
@Column(name="emp_name", length=30)
private String name;
@Column(name="emp_age", nullable=false)
private int age;
@Column(name="emp_address", nullable=false ,unique=true)
private String addree;
// Getters and Setters
}
首先我们可以可以使用
@Javax.persistence.Table 这个注解来改变 Java 类在数据库表种对应的表名。这个注解的定义如下:
@javax.persistence.Column 注解,定义了列的属性,你可以用这个注解改变数据库中表的列名(缺省情况下表对应的列名和类的字段名同名);指定列的长度;或者指定某列是否可以为空,或者是否唯一,或者能否更新或插入。
从它的定义可以看出他只可以用在类中的方法前面或者字段前面。
其中 name 属性的值为数据库中的列名,unique 属性说明该烈是否唯一,nullable 属性说明是否可以为空,length 属性指明了该列的最大长度等等。其中 table 属性将在 @SecondaryTable 的使用中已有过介绍。
JPA 中两种注解方式
JPA 中将一个类注解成实体类(entity class)有两种不同的注解方式:基于属性(property-based)和基于字段(field-based)的注解。
1,基于字段的注解,就是直接将注解放置在实体类的字段的前面。前面的 Employee 实体类就是使用的这种注解方式;
2,基于属性的注解,就是直接将注解放置在实体类相应的 getter 方法前面,而不是 setter 方法前面(这一点和 Spring 正好相反)。前面的 Employee 实体类如果使用基于属性注解的方式就可以写成如下形式。
但是同一个实体类中必须并且只能使用其中一种注解方式,要么是基于属性的注解,要么是基于字段的注解。两种不同的注解方式,在数据库中对应的数据库表是相同的,没有任何区别,开发人员可以根据自己的喜好任意选用其中一种注解方式。
@SecondaryTable 的使用
上面介绍的几个例子都是一个实体类映射到数据库中的一个表中,那么能否将一个实体类映射到数据库两张或更多表中呢表中呢。在有些情况下如数据库中已经存在原始数据类型,并且要求不能更改,这个时候如果能实现一个实体类对应两张或多张表的话,将是很方便的。JPA2.0 中提供了一个 @SecondaryTablez 注解(annotation)就可以实现这种情况。下面用一个例子说明一下这个注解的使用方法:
清单 6. @SecondaryTable 的使用
@Entity
@SecondaryTables({
@SecondaryTable(name = "Address"),
@SecondaryTable(name = "Comments")
})
public class Forum implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
@Column(table = "Address", length = 100)
private String street;
@Column(table = "Address", nullable = false)
private String city;
@Column(table = "Address")
private String conutry;
@Column(table = "Comments")
private String title;
@Column(table = "Comments")
private String Comments;
@Column(table = "Comments")
private Integer comments_length;
// Getters and Setters
}
清单 5 中定义了两个 Secondary 表,分别为 Address 和 Comments,
同时在 Forum 实体类中也通过 @Column 注解将某些子段分别分配给了这两张表,那些 table 属性得值是 Adress 的就会存在于 Address 表中,
同理 table 属性的值是 Comments 的就会存在于 Comments 表中。那些没有用 @Column 注解改变属性默认的字段将会存在于 Forum 表中。
图 4 就是持久化后在数据库中对应的表的 ER 图,从图中可看出来,这些字段如我们预料的一样被映射到了不同的表中。
图 4. @SecondaryTable 持久化后对赢得 ER 图
嵌套映射
在使用嵌套映射的时候首先要有一个被嵌套的类,清单 5 中 Address 实体类使用 @Embeddable 注解,说明这个就是一个可被嵌套的类,与 @EmbeddedId 复合主键策略中的主键类(primary key class)稍有不同的是,这个被嵌套类不用重写 hashCode() 和 equals() 方法,复合主键将在后面进行介绍。
清单 7. 被嵌套类
@Embeddable
public class Address implements Serializable {
private String street;
private String city;
private String province;
private String country;
// Getters and Setters
}
清单 6 中 Employee 实体类是嵌套类的拥有者,其中使用了 @Embedded 注解将 Address 类嵌套进来了。
清单 8. 嵌套类的使用者
@Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String email;
private String cellPhone;
@Embedded
private Address address;
// Getters and Setters
}
清单 7 是持久化后生成的数据库表,可以看出被嵌套类的属性,也被持久化到了数据库中,默认的表名就是嵌套类的拥有者的类名。
清单 9. 使用嵌套类生成的表结构
CREATE TABLE `employee` (
`ID` bigint(20) NOT NULL,
`EMAIL` varchar(255) default NULL,
`NAME` varchar(255) default NULL,
`CELLPHONE` varchar(255) default NULL,
`STREET` varchar(255) default NULL,
`PROVINCE` varchar(255) default NULL,
`CITY` varchar(255) default NULL,
`COUNTRY` varchar(255) default NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
被嵌套类的注解方式,field 方式或者 property 方式,依赖于嵌套类的拥有者。上面例子中的 Employee 实体类采用的是 field 注解方式,那么在持久化的过程中,被嵌套类 Address 也是按照 field 注解方式就行映射的。
我们也可以通过 @Access 注解改变被嵌套类映射方式,清单 8 通过使用 @Access 注解将 Address 被嵌套类的注解方式设定成了 property 方式。清单 9 Employee 仍然采用 filed 注解方式。这种情况下,持久化的时候,被嵌套类就会按照自己设定的注解方式映射,而不会再依赖于嵌套类的拥有者的注解方式。但这并不会映射的结果。
清单 10. 基于 property 方式注解的被嵌套类
@Embeddable
@Access(AccessType.PROPERTY)
public class Address implements Serializable {
private String street;
private String city;
private String province;
private String country;
@Column(nullable=false)
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Column(nullable=false,length=50)
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
@Column(nullable=false,length=20)
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
}
清单 11. 基于 field 方式注解
@Entity
@Access(AccessType. FIELD)
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String email;
private String cellPhone;
@Embedded
private Address address;
// Getters and Setters
}
事先设定被嵌套类的注解方式,是一种应该大力提倡的做法,因为当同一个类被不同的注解方式的类嵌套时,可能会出现一些错误。
hibernate里@Column(name = “COST_PRICE“, unique = true, nullable = false, precision = 10,scale = 2)
(1)precision=10代表有效数字为10位,即从左边第一个不为0的数字开始到最右边的位数,scale=2代表的就是小数点右边的位数。
(2)底层数据库的细节对于hibernate来说是透明的,也就是无论什么数据库你都可以按这样子来设置,hibernate会自动帮你完成底层操作。
(3)当然是一致的,一般我们都是用hibernate的实体生成工具来生成实体类,很少有手工写的。
.net datetime加什么意思
在值类型(int,DateTime之类)后面加个"?" 表示这是一个Nullable类型.
在C#1.x的版本中,一个值类型变量是不可以被赋予null值的,否则会产生异常。
在C#2.0中,微软提供了Nullable类型,允许用它定义包含null值(即空值)的数据类型,这对处理数据库中包含可选字段以及很多方面都有很大帮助。
定义一个nullable类型非常类似于定义一个非nullable类型。不同之处在于使用类型修饰符“?”。
比如定义一个整型如下:
int myInt = 1;
要使一个整型变量可以存储一个null值,可以如下声明:
int? myNullableInt = 1;
在你的例子中,
DateTime now = null; 是会报错的, 因为DateTime类型是一个值类型, 不能为空.(就像int值不能为空一样) 但
DateTime? now = null; 却不会报错.
把一个值类型变量定义为对应的Nullable类型后, 这个变量就有一些其他的属性(这里可能会误导,看我后面的注释), 例如上例中的now
now.HasValue 是一个bool值,表示该变量是否非空.
now.Value 就是对应的DateTime类型值. 注意不要在now.HasValue为false的时候使用这个属性.
---
注: 实际上, 这个"?"语法就是定义Nullable《T》的一个快捷语法.
int? i 实际上就是 Nullable《int》 i 的一个缩写. 所以实际上"int? i;" 这句i已经不再是一个int类型,而是一个Nullable《int》类型, 我上面所述的新增的属性(.HasValue和.Value)也是Nullable《int》带来的.
***隐藏网址***
***隐藏网址***
C#中的是什么意思
可空类型修饰符(?):引用类型可以使用空引用表示一个不存在的值,而值类型通常不能表示为空。例如string str=null; 是正确的,int i=null; 编译器就会报错。
为了使值类型也可为空,就可以使用可空类型,即用可空类型修饰符"?"来表示,表现形式为"T?"
例如:int? 表示可空的整形,DateTime? 表示可为空的时间。
T? 其实是System.Nullable(泛型结构)的缩写形式,也就意味着当用到T?时编译器编译 时会把T?编译成System.Nullable的形式。
扩展资料:
C#使用注意事项:
类成员的访问权限:
1、Public:允许外界访问。
2、Private 不允许外界访问,也不允许派生类访问。
3、Protected:不允许外界访问,但允许派生类访问。
4、Internal:允许同一个命名空间中的类访问。
5、在while括号后面不要加分号,因为加分号后,就表示while语句中的循环体代表空语句。不过有时可以将while语句的循环体中的语句放在while后面的括号中去,这时就可在while括号后面加上一个分号。

更多文章:
进制转换的8421法(十进制如何转换成8421码~~请说方法)
2026年5月11日 00:45
线程和进程的区别是什么 面试(linux进程和线程的区别 面试)
2026年1月20日 03:15
rabbitmq最新版本(rabbitmq3.8.2安装步骤及集群配置)
2025年11月21日 11:00
易语言源码打开停止工作(同样一个易语言源码,为什么在我的电脑上运行就卡死,在别人的电脑上运行是正常的)
2026年5月15日 01:30
powershell快捷键打开(windows10的控制面板在哪里找)
2026年8月7日 07:15
oracle数据库完整性(oracle添加记录的时候提示违反完整性约束,未找到父项关键字怎么解决)
2025年10月15日 22:30
java找不到工作想去送快递了(昌平北大青鸟分享学Java找不到工作的原因)
2026年8月6日 10:45
后台管理系统模板如何使用(开源网店iWeb Shop的模板管理后台怎么操作有什么值得注意的地方)
2025年10月30日 18:15
高级程序设计语言的主要功能(高级语言的控制结构主要包含什么)
2025年12月8日 19:00
mapreduce的优势(hadoop与传统的关系型数据库(如oracle)相比,有什么优势及劣势)
2026年2月12日 15:00
java交互界面怎么做(北大青鸟java培训:常见交互设计表现形式)
2026年3月23日 04:00
pull together(pull together是什么意思)
2026年1月6日 13:30
substantial变形单词(哪些英语单词可以表达 “重要的” 以及它们的区别)
2026年1月13日 17:00
floor和ceil函数(Math之ceil、floor、round区别)
2026年1月22日 22:30
汉化版eclipse(在汉化版eclipse中编写java程序,无法解析start是什么意思)
2025年8月6日 22:30
interesting怎么读英语(“interesting”怎么读)
2026年5月11日 15:45











