EF CodeFirst系列(3)--- 数据注释属性

 


EFCodeFirst模式使用的是约定大于配置的编程模式,这种模式利用默认约定根据我们的领域模型建立概念模型。然后我们也可以通过配置领域类来覆盖默认约定。

覆盖默认约定主要用两种手段:

1.数据注释属性(Data Annotations Attributes)

2.FluentAPI

1.数据注释属性

我们可以给领域类或者类的属性添加数据注释属性来实现覆盖默认约定,其实在MVC webApi中也会经常用到数据注释属性。一个栗子

复制代码
[Table("StudentInfo",Schema ="MySchool")]//数据库名为MySchool.StudentInfo
public class Student
{
    public Student() { }
        
    [Key]//默认把Id或者classNameId作为主键。通过[key]可以指定主键,如果是数字类型,默认自增
    public int SID { get; set; }

    [Column("Name",Order=1,TypeName="ntext")]//必有项:列名为Name ;非必有项:顺序为1,数据库中类型为ntext(order是从0开始的)
    [MaxLength(20)]
    public string StudentName { get; set; }

   [NotMapped]//不映射到数据库中
    public int? Age { get; set; }

  public string Phone{get{return "13545678901";}}//getter,setter有一个不存在,就不会映射到数据库

[Index("IndexName",IsClustered =true ,IsUnique =true)]//索引名为IndexName,是聚集索引和唯一索引,直接使用[index]时索引名为IX_StudentNo,为非聚集索引   public string StudentNo{get;set;}
[Timestamp]
//在update操作时,包含在where子句中 public byte[] RowVersion{get;set;}   public int StdId { get; set; }    [ForeignKey("StdId")] public virtual Standard Standard { get; set; } }
复制代码

 

一些常用的数据注释属性

Key 数据库中对应列为主键
Timestamp

数据库中对应列为timestamp类型,主要用于解决高并发问题

注:一个类只能用一次,且修饰的属性必须为byte[]类型

ConcurrencyCheck 数据库中对应列进行乐观并发检测,主要用于解决高并发问题
Required 属性不为空,数据中对应列
MinLength/MaxLength 属性和数据库中的最小/最大的string长度
StringLength 属性和数据库中的最小,最大

架构属性

Table 用于实体,配置实体对应的数据库表名和表结构
Column 用于属性,配置属性对应数据库列名,顺序和数据类型
Index 用于属性,配置对应数据库的列为索引
ForeignKey 用于属性,指定属性为一个外键
NotMapped 用于实体/属性,不在数据库中生成映射
DataBaseGernerated 用于属性,设置数据库对应列值的生成,如identity,computed或者none

2.一些补充

1.复合主键

EF6中通过Key和Column属性可以实现复合主键,一个栗子:

复制代码
public class Student
{
    [Key]
    [Column(Order=1)]
    public int StudentKey { get; set; }
     
    [Key]
    [Column(Order=2)]
    public int AdmissionNum { get; set; }
     
    public string StudentName { get; set; }
}
复制代码

数据库如下:

 

注意:EF core中不支持通过[key]属性来设置复合主键,我们可以通过Fluent API的HasKey()方法来实现

2.ForeignKey的三种形式

一个栗子:

复制代码
public class Student
{
    public int StudentID { get; set; }
    public string StudentName { get; set; }
        
    //Foreign key for Standard
    public int StandardId { get; set; }
    public Standard Standard { get; set; }
}

public class Standard
{
    public int StandardId { get; set; }
    public string StandardName { get; set; }
    
    public ICollection<Student> Students { get; set; }
}
复制代码

在上边的栗子中,没有使用ForeignKey覆盖默认的约定,那么安照默认约定执行:EF找到导航属性Standard,找到Standard的主键为StandardId,正好Student类中有StandardId属性,那么就把StandardId设为外键;如果Student类没有StandardId呢?EF会创建一个外键<导航属性名>_<导航属性主键>,在本例中就是Standard_StandardId

我们不想使用EF自动创建的外键,而是想把一个属性设置成外键,但是这个属性和导航属性的主键名不一致,我们应该怎么去实现呢?

1.[ForeignKey(导航属性名)]   依赖实体(Student依赖Standard,所以Student是依赖实体)中在我们想设置为外键的属性上指定导航属性

复制代码
public class Student
{
    public int StudentID { get; set; }
    public string StudentName { get; set; }
        
    [ForeignKey("Standard")]
    public int StandardRefId { get; set; }
    public Standard Standard { get; set; }
}

public class Standard
{
    public int StandardId { get; set; }
    public string StandardName { get; set; }
    
    public ICollection<Student> Students { get; set; }
}
复制代码

2.[ForeignKey(导航属性名)] 依赖实体中在导航属性上指定属性名

复制代码
public class Student
{
    public int StudentID { get; set; }
    public string StudentName { get; set; }
        
    public int StandardRefId { get; set; }
    
    [ForeignKey("StandardRefId")]
    public Standard Standard { get; set; }
}

public class Standard
{
    public int StandardId { get; set; }
    public string StandardName { get; set; }
    
    public ICollection<Student> Students { get; set; }
}
复制代码

3.主实体中在导航属性上指定属性名(不推荐)

复制代码
ublic class Student
{
    public int StudentID { get; set; }
    public string StudentName { get; set; }
        
    public int StandardRefId { get; set; }
    public Standard Standard { get; set; }
}

public class Standard
{
    public int StandardId { get; set; }
    public string StandardName { get; set; }
    
    [ForeignKey("StandardRefId")]
    public ICollection<Student> Students { get; set; }
}
复制代码

3.ConcurrencyCheck解决高并发

  ConcurrencyCheck属性可以用在领域类的一个或多个属性中,领域类的属性使用它修饰后,数据库中对应的列会启用乐观并发检测。

一个栗子:

public class Student
{
    public int StudentId { get; set; }
     
    [ConcurrencyCheck]
    public string StudentName { get; set; }
}

上边栗子中StudentName使用ConcurrencyCheck注释,EF会在Update时将StudentName添加到where子句中。

复制代码
using(var context = new SchoolContext()) 
{
    var std = new Student()
    {
        StudentName = "Bill"
    };

    context.Students.Add(std);
    context.SaveChanges();

    std.StudentName = "Steve";
    context.SaveChanges();
}
复制代码

在执行SaveChanges()方法时,执行的Sql命令如下:

exec sp_executesql N'UPDATE [dbo].[Students]
SET [StudentName] = @0
WHERE (([StudentId] = @1) AND ([StudentName] = @2))
',N'@0 nvarchar(max) ,@1 int,@2 nvarchar(max) ',@0=N'Steve',@1=1,@2=N'Bill'
go            

注意:TimeStamp只能用于byte[]类型,且一个类中只能用一次,而ConcurrencyCheck用于任何数据类型的属性且能在一个类中使用多次。

posted @   捞月亮的猴子  阅读(6372)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
点击右上角即可分享
微信分享提示