65.9K
CodeProject 正在变化。 阅读更多。
Home

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

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2013 年 6 月 24 日

CPOL

4分钟阅读

viewsIcon

11560

一种简洁易行的方法,可使用 MVC 和 Entity Framework 实现快速解决方案,同时保持代码整洁,使数据库访问不与对象绑定,并充分利用 MVC 和 Entity Framework 的强大功能。

引言

本文的目的是展示一种简洁易行的方法,可使用 MVC 和 Entity Framework 实现快速解决方案,同时保持代码整洁,使数据库访问不与对象绑定,并充分利用 MVC 和 Entity Framework 的强大功能。

为此,我们将首先创建一个 MVC 4 网站,一个用于 POCO 类的独立项目,以及最后一个同样重要的项目,其中将包含与数据库访问相关的类。

在使用 code-first 时,我发现的最佳方法是在开始编码之前设计数据库模型。基本上,良好的数据库设计在 code-first 中非常有帮助,它将节省大量时间,避免因奇怪的问题而浪费时间。这并不是说 Entity Framework 不能与设计不佳的数据库配合使用,但如果您是初学者或之前没有遇到过这种情况,那肯定会很困难。

对于数据库,我们将使用一个个人房地产项目的数据库示例,其设计并非最佳,但这是一个开始。

我们将从 GenericPropertyResidentialProperty 表开始。

上图中最重要的几点是主键和外键。一旦有了数据库设计,我们就可以继续创建与数据库映射的类。我最喜欢的方法称为“表类型”(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** 查询。

希望这对您有所帮助。

请随时发表评论。

感谢阅读!

© . All rights reserved.