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

模型 (POCO)、实体框架和数据模式。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (64投票s)

2013年7月4日

CPOL

9分钟阅读

viewsIcon

244569

downloadIcon

6017

模型 (POCO)、实体框架和数据模式

引言

在本应用中,我将解释如何使用微软的最佳实践来构建我们的模型和数据层。

我们的路线图是

  1. 创建模型 POCO
  2. 从实体框架创建 DBContext
  3. 创建存储库(模式)
  4. 创建 UOW(工作单元模式)
  • POCO 模型将充当数据载体,并且它们是独立的(独立的)。这些类不知道数据库的任何信息。
  • 实体框架是一个 ORM(对象关系映射器),它使我们能够连接到数据库(SQL Server)并将数据库映射到我们的模型,反之亦然。我们可以将其视为一个管道。
  • 存储库模式将通过与 EF(实体框架)通信来公开我们数据库中数据的检索、更新和保存,并简化和标准化我们处理检索、更新和保存的方式。
  • 工作单元模式是将所有存储库放在一个地方,以便我们可以对多个存储库的更改进行提交和回滚。

但我们为什么要使用所有这些模式技术呢?好吧,简单的答案是**可重用性和可维护性**,通过将类与其行为分离并遵循单一职责原则。

图 1

Using the Code

创建模型 POCO

我们将从模型开始,简单易懂。

首先,我们将向解决方案中添加一个新的类库项目,名为 `Model`,如(图 2)所示。

图 2

模型是数据载体,正如我们所说并在(图 1)中说明的,这些模型将是类,简单的类只包含属性,没有描述基础设施问题或其他您的域对象不应该拥有的职责(独立的)的属性,我们称它们为 POCO。它们是纯旧的 CLR 对象 (POCO)。

对于我们的应用程序,我们将创建三个类(`Applicant`、`Certification`、`Skill`)并将这些类添加到我们创建的模型项目中。

让我们来看看这些类

 public class Applicant
    {    
        public Applicant()
        {
            //because when we want to create a new applicant 
            //we have to initilize the collections
            //so we can add items into them.
            Certifications = new List<Certification>();
            Skills = new List<Skill>();        
        }
       
        [Key]
        public int ApplicantID { get; set; }
        public string First_Name { get; set; }
        public string Last_Name { get; set; }
        public string Email { get; set; }
        public string Tel_No { get; set; }
        public string Mobile_No { get; set; }
        public string Visa_Type { get; set; }
        public string LinkedIn_URL { get; set; }
        public string Objective { get; set; }
        public byte Active { get; set; }
        public virtual ICollection<Certification> Certifications { get; set; }
        public virtual ICollection<Skill> Skills { get; set; }
    }
 
public class Certification
    {
        [Key]
        public int id { get; set; }
        public string Title { get; set; }
        public string Name { get; set; }
        public Nullable<System.DateTime> Expire_Date { get; set; }
        public string Description { get; set; }
        public int ApplicantID { get; set; }
        public byte Active { get; set; }
        public virtual Applicant Applicant { get; set; }
    }
public class Skill
    {
        [Key]
        public int id { get; set; }
        public string Description { get; set; }
        public int ApplicantID { get; set; }
        public byte Active { get; set; }
        public virtual Applicant Applicant { get; set; } 
    }

这些只是简单的类,但如果您注意,您会看到我们有一个 `key` 属性,它来自(`using System.ComponentModel.DataAnnotations;`)。它只是为了指示 ID 是实体框架的主键,但实体框架足够智能,知道名称为 id 的属性是主键,而无需添加 `[key]` 属性。

从实体框架创建 DbContext

首先,我们将向解决方案中添加一个新的类库项目,名为 `Data`,如(图 3)所示。

图 3

其次,我们将添加对模型项目的引用,如(图 4)所示。

图 4

最后,我们将通过“管理 NuGet 包”添加实体框架,我们可以转到解决方案资源管理器中的“Data”项目,右键单击并选择“管理 NuGet 包”,如(图 5)所示。

 

图 5

然后,在搜索框中搜索实体框架,选择 Microsoft 的实体框架,然后单击安装,如(图 6)所示。

图 6

现在,我们可以开始构建我们的 `DbContext` 类了。

`DbContext` 来自实体框架,它允许我们在数据库和模型之间进行交互。它基本上定义了模型与数据库之间的关系。我个人称 `DbContext` 为管道。

在 `Dbcontext` 中,我们将拥有 `DbSet`,它定义了表与模型之间的关系。当我们转到我们的示例时,`DbContext` 将包含 `public DbSet Applicants { get; set; }`,将 `DbSet` 视为将我们的模型与数据库中的表绑定的粘合剂。

我们将在 `DbContext` 中拥有的另一件事是 `Configurations`,它负责配置数据库中的实体(表)关系。

这是 `DbContext` 类

public partial class MyExperienceDBContext : DbContext
    { 
        public MyInfoDBContext()
            : base("Name=MyExperience")//this is the connection string name
        {
        }
 
        public DbSet<Applicant> Applicants { get; set; }
        public DbSet<Certification> Certifications { get; set; }     
        public DbSet<Skill> Skills { get; set; }      
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new ApplicantConfig());
            modelBuilder.Configurations.Add(new CertificationConfig());
       
            modelBuilder.Configurations.Add(new SkillConfig());          
        }
    }

我将我的 `DbContext` 类命名为 `MyExperienceDBContext`,该类继承自 `DbContext`,这是实体框架的一部分。在构造函数中,我调用了 `DbContext` 类的构造函数并传入了连接字符串名称(“`Name=MyExperience`”),此连接字符串当然将存在于您应用程序的配置文件中。

从 `OnModelCreating` 方法可以看出,我们指向了 `ApplicantConfig`、`CertificationConfig` 和 `SkillConfig` 类,我们将创建这些类,请注意,这些类通常包含关系 `configuration` 和 `Column` 到模型属性。

这是配置类

  public class SkillConfig : EntityTypeConfiguration<Skill>
    {
        public SkillConfig()
        {            
            // Relationships.
            //skill has relation with applicant.
            //applicant has many skills (one applicant has many skills )
            //forign key on skill is ApplicantID
            this.HasRequired(t => t.Applicant)
                .WithMany(g => g.Skills)
                .HasForeignKey(d => d.ApplicantID); 
        }
    } 
 
public class CertificationConfig : EntityTypeConfiguration<Certification>
    {
        public CertificationConfig()
        {           
            // Relationships
            //Certification has relation with applicant.
            //applicant has many Certification (one applicant has many Certification )
            //forign key on Certification is ApplicantID
            this.HasRequired(t => t.Applicant)
                .WithMany(t => t.Certifications)
                .HasForeignKey(d => d.ApplicantID); 
        }
    } 
 
public class ApplicantConfig : EntityTypeConfiguration<Applicant>
    {
        public ApplicantConfig()
        {
            
        }
    }

请注意,配置类继承自 `EntityTypeConfiguration`,它来自(`using System.Data.Entity.ModelConfiguration;`),这属于实体框架,负责数据库中实体的(表)配置。

创建存储库(模式)(在 Data Class Library 项目中)

为什么选择存储库?

  • 一个地方检索更新数据,因此**可维护性**。
  • 假设您想通过直接与 `DBContext` 对话来更新数据库中的申请人,您可以这样做,但每次想要更新时都必须在不同地方编写相同的代码。存储库将确保您永远不会重复编写代码(示例中的更新),因此**可重用性**。
  • 遵循**单一职责原则 (SRP)**,确保一个地方负责检索或更新您的数据库。

让我们看看我们的存储库会是什么样子

public interface IRepository<T> where T : class
    {
        IQueryable<T> GetAll(); 
        T GetById(int id); 
        void Add(T entity);
        void Update(T entity);
        void Delete(T entity);
        void Delete(int id);
    } 
 
    /// <summary>
    /// The EF-dependent, generic repository for data access
    /// </summary>
    /// <typeparam name="T">Type of entity for this Repository.</typeparam>
    public class MyRepository<T> : IRepository<T> where T : class
    {
        public MyRepository(DbContext dbContext)
        {
            if (dbContext == null) 
                throw new ArgumentNullException("Null DbContext");
            DbContext = dbContext;
            DbSet = DbContext.Set<T>();
        }
 
        protected DbContext DbContext { get; set; }
 
        protected DbSet<T> DbSet { get; set; }
 
        public virtual IQueryable<T> GetAll()
        {
            return DbSet;
        }
 
        public virtual T GetById(int id)
        {           
            return DbSet.Find(id);
        }
 
        public virtual void Add(T entity)
        {
            DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
            if (dbEntityEntry.State != EntityState.Detached)
            {
                dbEntityEntry.State = EntityState.Added;
            }
            else
            {
                DbSet.Add(entity);
            }
        }
 
        public virtual void Update(T entity)
        {
            DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
            if (dbEntityEntry.State == EntityState.Detached)
            {
                DbSet.Attach(entity);
            }  
            dbEntityEntry.State = EntityState.Modified;
        }
 
        public virtual void Delete(T entity)
        {
            DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
            if (dbEntityEntry.State != EntityState.Deleted)
            {
                dbEntityEntry.State = EntityState.Deleted;
            }
            else
            {
                DbSet.Attach(entity);
                DbSet.Remove(entity);
            }
        }
 
        public virtual void Delete(int id)
        {
            var entity = GetById(id);
            if (entity == null) return; // not found; assume already deleted.
            Delete(entity);
        }
    }

在我们深入研究之前,我们应该知道为什么我选择泛型 `Repository` 类,为什么不是具体类,比如 `MyApplicantRepository` 或 `MySkillRepository`?嗯,您可以这样做,它仍然是存储库模式,但当您思考时,您必须为每个实体构建一个存储库类。在我们的示例中,最终将编写 3 个存储库。在正常项目中,您可能有 30 个,甚至更多,这意味着您将不得不编写新的 30 个存储库,这不容易维护。

但是当我们使用我们的通用存储库时,您可以将其用于所有实体,易于维护和重用。

首先,我们想将存储库与我们的 `Dbcontext` 相关联。这将在存储库构造函数中完成。

public MyRepository(MyExperienceDBContext dbContext)
        {
            if (dbContext == null) 
                throw new ArgumentNullException("Null DbContext");
            DbContext = dbContext;
            DbSet = DbContext.Set<T>();
        }

由于 `DbContext` 不是特定类型,我们将在这一行中进行设置。

( `DbSet = DbContext.Set();`)在存储库构造函数内部,`DbSet` 将与我们的泛型类 `` 类型相同。 

在接口 `IRepository` 中,我们有以下方法,`MyRepository` 将不得不实现这些方法。

IQueryable<T> GetAll(); // we can build on top of it to get more filtered data
T GetById(int id); //get by ID
void Add(T entity);//add new
void Update(T entity);//update
void Delete(T entity);//delete entity
void Delete(int id);//delete by id

在 `MyRepository` 类中,我们实现了 `IRepository` 方法。您可以查看我们如何实现这些方法,也可以修改它们或添加您自己的新方法。

public virtual void Update(T entity)
{
    DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
    if (dbEntityEntry.State == EntityState.Detached)
    {
        DbSet.Attach(entity);
    }  
    dbEntityEntry.State = EntityState.Modified;
}

例如,在上面的更新方法中,我们检查实体状态是否已附加(`DbContext` 知道它)或未附加,如果已附加,则表示我们可以跟踪修改;如果未附加,我们必须将其附加到我们的 `DbContext`,以便我们可以跟踪新修改。最后,我们将状态更改为已修改,以提醒 `SaveChanges` 我们已更改实体。

您可以通过此链接了解有关 `DbContext` 实体状态的更多信息。

现在,让我们开始定制我们的 `Repository` 类。我假设我们想获取所有具有特定技能的申请人,但我们的标准存储库类中没有这个方法!!!而且,此方法仅适用于 `applicants` 实体(不适用于 `Skills` 或 `Certifications` 实体),那么我们该怎么办??

简单,我们将为申请人创建一个自定义存储库,同时,我们将重用主存储库,如何做到?

在这里,我首先创建了 `IRepoApplicant`,它将继承自 `IRepository`,如下所示。

public interface IRepoApplicant:IRepository<Applicant>
    {      
        //new customize method for applicants
        //gets applicants with specific skill
        IQueryable<Applicant> GetApplicantsWihSkillName(string SkillName); 
    }

在这里,您可以看到我创建了方法 `GetApplicantsWihSkillName (string SkillName);`,让我们来看看实现。

public  class RepoApplicant :MyRepository<Applicant> ,IRepoApplicant  
    { 
      public RepoApplicant(DbContext context):base(context) 
      {
      
      } 
 
      //new customize method for applicants
      //gets applicants with specific skill
        public IQueryable<Applicant> GetApplicantsWihSkillName(string SkillName)
        {
            return GetAll().Where(p => p.Skills.Any
            (o => o.Description.ToLower() == SkillName.ToLower() ));
        }
    }

首先,如您所见,我们继承自 `MyRepository`,我们知道这是针对 `applicant` 实体的,因此我们将 `Applicant` 泛型传递给 `MyRepository`。

其次,请注意构造函数将把它的 `DbContext` 传递给 `MyRepository`。

现在,我们可以重用主存储库和我们新的自定义申请人存储库中的所有内容。

创建 UOW(工作单元模式)(在 Data Class Library 项目中)

UOW是我们应用程序的最后一块。

为什么我们使用 UOW?

UOW 充当一个外观,它将在一个地方聚合我们所有的存储库的初始化、调用和处置,并且还将我们的应用程序(控制台、控制器、ASP.NET 网页)与 `DbContext` 和存储库分离(解耦)。

UOW还将负责在一个我们称为 `commit` 方法的地方保存来自多个存储库的所有更改。

让我们看看 UOW 会是什么样子: 

   /// <summary>
    /// Interface for the My Experience Unit of Work"
    /// </summary>
    public interface IMyExperienceUow
    {
        // Save pending changes to the data store.
        void Commit();
 
        // Repositories
        IRepository<Skill> Skills { get; }
        IRepository<Certification> Certifications { get; }
        IRepoApplicant Applicants { get; }
    }

正如您所见,它简单的接口将公开我们的存储库和 `commit` 方法。

  /// <summary>
    /// The "Unit of Work"
    ///     1) decouples the repos from the console,controllers,ASP.NET pages....
    ///     2) decouples the DbContext and EF from the controllers
    ///     3) manages the UoW
    /// </summary>
    /// <remarks>
    /// This class implements the "Unit of Work" pattern in which
    /// the "UoW" serves as a facade for querying and saving to the database.
    /// Querying is delegated to "repositories".
    /// Each repository serves as a container dedicated to a particular
    /// root entity type such as a applicant.
    /// A repository typically exposes "Get" methods for querying and
    /// will offer add, update, and delete methods if those features are supported.
    /// The repositories rely on their parent UoW to provide the interface to the
    /// data .
    /// </remarks>
    public class MyExperienceUow : IMyExperienceUow, IDisposable
    { 
        private MyExperienceDBContext DbContext { get; set; } 
 
        public MyExperienceUow()
        {
            CreateDbContext();                   
        }
 
        //repositories
        #region Repositries
        private IRepository<Skill> _skills;
        private IRepository<Certification> _certifications;
        private IRepoApplicant _applicants;         
        
        //get Skills repo
        public IRepository<Skill> Skills
        {
            get 
            {
                if (_skills == null)
                {
                    _skills = new MyRepository<Skill>(DbContext);
                
                }
                return _skills;            
            }
        } 
 
        //get Certification repo
        public IRepository<Certification> Certifications
        {
            get
            {
                if (_certifications == null)
                {
                    _certifications = new MyRepository<Certification>(DbContext);
 
                }
                return _certifications; 
            }
        }
        //get aplicants repo
        public IRepoApplicant Applicants
        {
            get
            {
                if (_applicants == null)
                {
                    _applicants = new RepoApplicant(DbContext);
 
                }
 
                return _applicants; 
            }
        }
 
        #endregion
         
        /// <summary>
        /// Save pending changes to the database
        /// </summary>
        public void Commit()
        {             
            DbContext.SaveChanges();
        }
 
        protected void CreateDbContext()
        {
            DbContext = new MyExperienceDBContext();        
 
            // Do NOT enable proxied entities, else serialization fails.
            //if false it will not get the associated certification and skills when we
           //get the applicants
            DbContext.Configuration.ProxyCreationEnabled = false;
 
            // Load navigation properties explicitly (avoid serialization trouble)
           DbContext.Configuration.LazyLoadingEnabled = false;
 
            // Because Web API will perform validation, we don't need/want EF to do so
            DbContext.Configuration.ValidateOnSaveEnabled = false;
 
            //DbContext.Configuration.AutoDetectChangesEnabled = false;
            // We won't use this performance tweak because we don't need 
            // the extra performance and, when autodetect is false,
            // we'd have to be careful. We're not being that careful.
        }        
 
        #region IDisposable
 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (DbContext != null)
                {
                    DbContext.Dispose();
                }
            }
        }
 
        #endregion      
    }

请注意,在构造函数中,我们调用了 `CreateDbContext()` 方法,该方法将处理两件事:

  • 初始化 `DbContext`(`DbContext = new MyExperienceDBContext();`)
  • 配置我们的 `DbContext`,例如,您可以将 `lazyloading` 设置为 `false`。

请注意,这些配置将应用于所有存储库。

最后,我们获取存储库作为属性,该属性将负责在尚未初始化时初始化特定存储库。

//get aplicants repo
        public IRepoApplicant Applicants
        {
            get
            {
                if (_applicants == null)
                {
                    _applicants = new RepoApplicant(DbContext); 
                }
 
                return _applicants; 
            }
        }

现在,我们差不多可以启动这个庞大的应用程序了:),但首先,我们必须检查一下我们的控制台应用程序(也许您的应用程序是 MVC 或 ASP.NET,但我使用的是控制台),看看有几件小事。

  1. 我们将确保在我们的控制台项目的 *config* 文件(*App.config*)中有一个名为(“`MyExperience`”)的连接字符串,它指向一个新的数据库(没有表)。

    您可以将连接字符串的名称更改为您喜欢的任何名称,但请确保也在 `MyExperienceDBContext` 构造函数中更改它,如下所示。

    public partial class MyExperienceDBContext : DbContext
        {     
            public MyInfoDBContext()
                : base("Name=MyExperience")//this is the connection string name
            {
            }
  2. 以与我们在 Data 项目中添加实体相同的方式,将实体框架添加到我们的控制台项目。查看(图 5)。
  3. 在我们的控制台项目中添加对 Model 和 Data 项目的引用,如(图 7)所示。

    图 7
  4. 将代码添加到您的控制台应用程序的 `main` 方法中。
    //create new Certification
    var newCertification = new Certification();
    newCertification.Active = 1;
    newCertification.Description = "MCP";
    newCertification.Expire_Date = null;
    newCertification.id = 1;
    newCertification.Name = "Microsoft Profissional";
    newCertification.Title = "Microsoft Web Development ASP.NET";
    
    //create new skill
    var newSkill1 = new Skill();
    newSkill1.Active = 1;
    newSkill1.Description = "C#";
    newSkill1.id = 1;
    
    var newSkill2 = new Skill();
    newSkill2.Active = 1;
    newSkill2.Description = "MVC";
    newSkill2.id = 1;
    
    //create new applicant
    var newApplicant = new Applicant();
    
    newApplicant.Active = 1;
    newApplicant.ApplicantID = 1;
    newApplicant.Certifications.Add(newCertification);
    newApplicant.Email = "master_khalil@yahoo.com";
    newApplicant.First_Name = "Khaleel";
    newApplicant.Last_Name = "Esbaitah";
    newApplicant.LinkedIn_URL = "http://www.linkedin.com/pub/khaleel-esbaitah/1b/703/913";
    newApplicant.Mobile_No = "34234234";
    newApplicant.Objective = "To Join an organisation where I can emphasise my strength ";
    newApplicant.Skills.Add(newSkill1);
    newApplicant.Skills.Add(newSkill2);
    newApplicant.Tel_No = "1234567";
    newApplicant.Visa_Type = "PR"; 
    
    //add new applicant to applicants entity
    //using UOW facade
    IMyExperienceUow uow = new MyExperienceUow();
    
    uow.Applicants.Add(newApplicant);
    // commit all changes to DB
    uow.Commit(); 
    
    var result= uow.Applicants.GetAll();

我们创建了新的 `Skill`、`Certification` 和 `Applicant`。

请注意,我们没有初始化存储库或 `DbContext`,我们只是初始化了我们的 `MyExperienceUow` 的新实例,然后将新创建的 `applicant` 添加到 `UOW` 类中已存在的 `Applicants` 存储库中,然后使用 `Commit` 方法提交了更改。

请注意,我们的数据库中将没有任何表,但当我们启动应用程序并添加新的 `Applicant` 然后 `Commit` 时,实体框架足够智能,可以注意到没有表,因此它将从我们的 `DbContext` 创建表并读取我们的配置。

现在,一切就绪。

重要提示

在实际项目中,90% 的时间数据库已经存在,那么我们该怎么办??

很简单,有一个名为“Entity Framework Power Tools”的工具。它基本上会将您的数据库反向工程为 POCO 类和 `DbContextClass`。有关更多信息,请访问此链接

© . All rights reserved.