使用 Entity Framework Code-First 实现 .NET MVC 应用程序,轻松上手 - 第一部分





0/5 (0投票)
一种简洁易行的方法,可使用 MVC 和 Entity Framework 实现快速解决方案,同时保持代码整洁,使数据库访问不与对象绑定,并充分利用 MVC 和 Entity Framework 的强大功能。
引言
本文的目的是展示一种简洁易行的方法,可使用 MVC 和 Entity Framework 实现快速解决方案,同时保持代码整洁,使数据库访问不与对象绑定,并充分利用 MVC 和 Entity Framework 的强大功能。
为此,我们将首先创建一个 MVC 4 网站,一个用于 POCO 类的独立项目,以及最后一个同样重要的项目,其中将包含与数据库访问相关的类。
在使用 code-first 时,我发现的最佳方法是在开始编码之前设计数据库模型。基本上,良好的数据库设计在 code-first 中非常有帮助,它将节省大量时间,避免因奇怪的问题而浪费时间。这并不是说 Entity Framework 不能与设计不佳的数据库配合使用,但如果您是初学者或之前没有遇到过这种情况,那肯定会很困难。
对于数据库,我们将使用一个个人房地产项目的数据库示例,其设计并非最佳,但这是一个开始。
我们将从 GenericProperty
和 ResidentialProperty
表开始。
上图中最重要的几点是主键和外键。一旦有了数据库设计,我们就可以继续创建与数据库映射的类。我最喜欢的方法称为“表类型”(Table Per Type),即基本上每个数据库表对应一个类。让我们从 Generic Property 开始。
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace PTIPortal.DA.Models
{
[Table("GenericProperty")]
public partial class GenericProperty
{
public GenericProperty()
{
this.PropertyPhotoes = new List<PropertyPhoto>();
this.Amenities = new List<PropertyAmenity>();
}
[Key]
[Display(Name="ID")]
public long PropertyId { get; set; }
[Display(Name = "Type")]
public int PropertyTypeId { get; set; }
public System.Guid PropertyUniqueId { get; set; }
[Display(Name = "City")]
public long CityId { get; set; }
public double LatitudeDecimal { get; set; }
public double LongitudeDecimal { get; set; }
[Display(Name="Size")]
public double LotSize { get; set; }
[Display(Name = "Units")]
public int LotsizeUnitOfMeasureId { get; set; }
[Display(Name = "Owner")]
public long OwnerInfoId { get; set; }
[Display(Name = "Description")]
public string Description { get; set; }
[Display(Name = "Status")]
public int WorkflowStepId { get; set; }
[Display(Name = "Hidden")]
public bool IsHidden { get; set; }
public virtual OwnerInfo OwnerInfo { get; set; }
public virtual PropertyType PropertyType { get; set; }
[ForeignKey("LotsizeUnitOfMeasureId")]
public virtual UnitOfMeasure UnitOfMeasure { get; set; }
[ForeignKey("WorkflowStepId")]
public virtual WorkflowStatu WorkflowStatu { get; set; }
public virtual ICollection<PropertyPhoto> PropertyPhotoes { get; set; }
[Association("PropertyAmenity", "PropertyId", "AmenityId")]
public virtual ICollection<PropertyAmenity> Amenities { get; set; }
[ForeignKey("CityId")]
public virtual City PropertyCity { get; set; }
[NotMapped]
public int[] AmenitiesId { get; set; }
[NotMapped]
[Display(Name = "Location")]
public string Location
{
get
{
return string.Format("{0}, {1}, {2}",
this.PropertyCity.CityName,
this.PropertyCity.Province_State.ProvinceState_Name,
this.PropertyCity.Province_State.Country1.CountryName);
}
}
}
}
Code-First 默认情况下会根据命名约定将属性映射到数据库列,这意味着它会尝试查找与属性同名的列并将数据映射到其中。在某些情况下,可以并且必须覆盖此行为。如果您有一个名称不同的属性,并且需要将其映射到特定列,只需用 [Column("PropertyNameGoesHere")]
特性修饰该属性,即可实现自动映射。
ID
属性,它上方有一些数据注释。Key
用于指定标识实体的属性,在大多数情况下,这些应该是我们的主键。
它还有一个 Display
特性,这在 MVC 中用于指示打印属性标签名称时,应显示 string
中的内容,在此示例中为 ID
。
您可以做得更好,并像这样从资源文件中检索值
[Display(Name="GenericPropertyId", ResourceType=typeof(PTIPortal.BO.Messages))]
只需一行代码,即可为您的属性标签设置本地化。
让我们看看 WorkflowStatus
属性。它是一个导航属性,允许我们从当前对象导航到相关记录。导航属性使用 virtual
声明,如果您有一对多或多对多关系,您将改用 ICollection<T>
。ForeignKey
特性指示当前类中用于与相关对象的主键进行连接的属性。
标记为 NotMapped
的属性基本上是添加到模型中但与数据库列无关的附加属性。必须将它们标记为 NotMapped
,否则它们将包含在自动生成的 SQL 语句中,并且对于数据库结构无效。
在此方法中,我将在 MVC 端使用这些属性。
构造函数初始化 List
属性。这基本上是为了防止在调用与 generic property 相关的 photo 或 amenity 的 .Add
时出现**对象引用未设置**错误。
ResidentialProperty - 继承
使用 code-first,您仍然可以使用继承,但需要遵循一些规则,例如“父”表和“子”表的主键在数据库中具有相同的名称。
基本上,它会查找 parent
类中的 key
属性,生成的 SQL 将在这两端使用该列名进行连接。
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace PTIPortal.DA.Models
{
[Table("ResidentialProperty")]
public partial class ResidentialProperty : GenericProperty
{
//public long PropertyId { get; set; }
[Display(Name = "Listing Type")]
public int ListingTypeId { get; set; }
[Display(Name = "Price")]
public decimal Price { get; set; }
[Display(Name = "currency")]
public int PriceCurrencyId { get; set; }
[Display(Name = "Full Baths")]
public int FullBaths { get; set; }
[Display(Name = "Half Baths")]
public int HalfBaths { get; set; }
[Display(Name = "Bedrooms")]
public int Bedrooms { get; set; }
[Display(Name = "Parking Spaces")]
public int GarageCarCount { get; set; }
[Display(Name = "Floors/Stories")]
public int Floors { get; set; }
[Display(Name = "Residential")]
public string ResidentialName { get; set; }
[Display(Name = "Additional Information")]
public string OtherInfo { get; set; }
[ForeignKey("PriceCurrencyId")]
public virtual Currency Currency { get; set; }
//public virtual GenericProperty GenericProperty { get; set; }
public virtual ListingType ListingType { get; set; }
}
}
开始乐趣 - 创建 Context
您可以将 context 视为我们对象与数据库的连接。
Context 将处理数据库连接、异常、发送消息、验证数据等操作。
public partial class PTIDBContext : DbContext
{
static PTIDBContext()
{
Database.SetInitializer<PTIDBContext>(null);
}
public PTIDBContext()
: base("Name=PTIDBContext")
{
}
public DbSet<GenericProperty> GenericProperties { get; set; }
public DbSet<ResidentialProperty> ResidentialProperties { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
base("Name=PTIDBContext")
行基本上调用了基类构造函数,并告诉它查找名称为 =
后的 connectionstring
。
然后,您为要查询的每个表创建一个 DbSet
。您可以将其视为包含数据并可执行查询的表。
OnModelCreating
方法可以用来配置模型,通常使用 **Fluent-API**。您可以使用 **Fluent-API** 完成的大多数事情都可以使用 **Data Annotations** 完成,每种方法都有其优缺点,但这超出了本文的范围。
在进行查询时,code-first 默认会使用 DbSet
名称作为生成 SQL 的表名。
因此,它会执行 Select .... FROM GenericProperties
,这将失败。我们在定义类时通过用 [Table("GenericProperty")]
和 [Table("ResidentialProperty")]
修饰它们来避免这种情况。
一旦您的**类**、**context** 和 **DbSets** 都准备就绪,您就可以开始使用它们了。
只需创建 **context** 的新实例,访问 **DbSets**,然后在它们上执行 **linq** 查询。
希望这对您有所帮助。
请随时发表评论。
感谢阅读!