Code First開發系列實戰之使用EF搭建小型博客平台


返回《8天掌握EF的Code First開發》總目錄


本篇目錄

本系列的源碼本人已托管於Coding上:點擊查看,想要注冊Coding的可以點擊該連接注冊
先附上codeplex上EF的源碼:entityframework.codeplex.com,此外,本人的實驗環境是VS 2013 Update 5,windows 10,MSSQL Server 2008/2012。

前面我們已經用7篇博客將EF的Code First知識系統地介紹完畢了,接下來就應該檢驗一下真理了。通過使用EF為一個完整的應用創建數據訪問層,我們會在實踐中看到所有的理論知識,這個應用是一個簡單的博客平台,並且使用了EF進行數據訪問。

理解應用需求

首先理解一下這個博客平台的需求,我們只是開發一個簡單的、可用作博客框架的應用,只有一些常見的功能,不可能像博客園那么強大。下面列一下這個博客框架具有的一些功能:

  • 作者可以創建博客分類
  • 作者可以管理(添加和更新)分類
  • 作者可以創建新博客
  • 作者可以管理(更新或刪除)博客
  • 首頁會顯示所有博客的列表,列表內容包括博客標題,發布時期,博客的前200個字符以及一個查看更多鏈接
  • 當點擊首頁博客列表的標題時,會顯示整篇博客
  • 用戶可以對博客發表評論
  • 用戶可以刪除評論

我們會以類庫的形式開發一個簡單的數據訪問層,該類庫會有助於這些功能的實現。因為我們創建了數據訪問的類庫,客戶端應用就可以使用該類庫了。

從博客框架的角度講,這些並不是完整的功能集,但是這些功能足以用來描述EF相關的概念了。我會創建一個簡單的ASP.NET MVC應用,ASP.NET MVC相關的問題本系列不涉及,但是我會展示一些應用運行的截圖以證明數據訪問層是正常工作的。

數據庫設計

在創建POCO實體類之前,先來看一下要實現之前提到的功能,需要的可視化數據庫設計。

首先,需要滿足用戶注冊,驗證和授權機制的表,只需要創建下面的三張表即可:

  • Users:這張表用於保存所有用戶的信息,用於用戶登錄和管理賬戶。
  • Roles:用於跟蹤應用中的角色,在這個博客應用中,只有兩個角色:Authors(作者)和Users(用戶)。作者可以發布和管理博客。用戶只能對博客發表評論。
  • UserRoles:用於創建Users和Roles間的多對多關系。

基本的驗證和授權表有了,再看看要實現上述功能還需要什么表:

  • Categories:用於存儲作者創建的所有博客分類。
  • Blogs:用於存儲作者創建的所有博客。
  • Comments:用於存儲用戶對博客發表的評論。

綜上,可視化數據庫設計圖如下:

圖片

創建實體數據模型

現在開始一個個創建POCO實體類,完成實體類的創建之后,我們再來實現它們之間的關系,也就是在實體類中添加導航屬性。

創建實體類

User實體

首先,咱們創建保存用戶明細的實體User,該實體對需要驗證的用戶數據負責,代碼如下:

public partial class User
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public string Email { get; set; }
    public bool EmailConfirmed { get; set; }
    public string PasswordHash { get; set; }
    public string SecurityStamp { get; set; }
}


EF會為Id生成自動增長的屬性,其他屬性都是用來認證用戶的屬性。

這里先假設User模型使用哈希和加鹽技術存儲用戶的密碼,PasswordHash包含了用戶提供密碼的哈希值,SecurityStamp包含了鹽值。如果使用其他任何的密碼存儲機制,可以相應更改User模型。

Role實體

創建Role類的主要目的是為了授權,比如,為了識別當前登錄系統的用戶是一個author還是一個普通的user。代碼如下:

public partial class Role
{
    public int Id { get; set; }

    [Required]
    [StringLength(256)]
    public string Name { get; set; }
}


Category實體

Category代表博客的所屬類別,代碼如下:

public partial class Category
{
    public int Id { get; set; }

    [Required]
    [StringLength(200)]
    public string CategoryName { get; set; }
}


Blog實體

Blog實體用來存儲博客數據。代碼如下:

public partial class Blog
{
    public int Id { get; set; }

    [Required]
    [StringLength(500)]
    public string Title { get; set; }

    [Required]
    public string Body { get; set; }
    public DateTime CreationTime { get; set; }
    public DateTime UpdateTime { get; set; }
}


Comment實體

Comment實體表示用戶對博客發表的評論。代碼如下:

public partial class Comment
{
    public int Id { get; set; }
    public string Body { get; set; }
    public DateTime CreationTime { get; set; }
}


創建關系和導航屬性

目前,我們只創建了獨立的實體,這些實體之間還沒有任何關系,現在就來實現這些實體間的關系。在使用代碼實現之前,先來看看所有需要的關系:

  • User-Role:因為任何每個用戶可以有多個角色,而每個角色也可以有多個用戶,所以User和Role之間是多對多關系
  • Category-Blog:因為每個類別可以有多篇博客,而這里每篇博客屬於一個類別,所以它們的關系是一對多關系
  • User-Blog:因為每個用戶可以有多篇博客,而每篇博客屬於一個用戶,所以它們是一對多關系
  • Blog-Comment:因為每篇博客可以有多條評論,而每條評論屬於一篇博客,所有它們是一對多關系
  • User-Comment:因為每個用戶可以有多條評論,而每個評論屬於一個用戶,所以它們是一對多關系

下面就在實體類中實現這些關系(這里只列出新加的代碼)。

User實體

從上面列出的關系可以看出,User實體需要實現以下關系:

  • 因為每個用戶有多個角色,所以需要一個Roles集合屬性作為導航屬性
  • 因為每個用戶有多篇博客,所以需要一個Blogs集合屬性作為導航屬性
  • 因為每個用戶有條評論,所以需要一個Comments集合屬性作為導航屬性
public partial class User
{
    public User()
    {
        Blogs=new HashSet<Blog>();
        Comments=new HashSet<Comment>();
        Roles=new HashSet<Role>();
    }
    //省略部分屬性
    public virtual ICollection<Blog> Blogs { get; set; }
    public virtual ICollection<Comment> Comments { get; set; }
    public virtual ICollection<Role> Roles { get; set; }
}


Role實體

因為每個角色有多個用戶,所以需要添加一個Users集合屬性作為導航屬性

public partial class Role
{
    public Role()
    {
        Users=new HashSet<User>();
    }
    //省略部分屬性
    public virtual ICollection<User> Users { get; set; }
}


Category實體

因為每個類別包含多篇博客,所以Category需要添加一個Blogs集合屬性作為導航屬性

public partial class Category
{
    public Category()
    {
        Blogs=new HashSet<Blog>();
    }

    //省略部分屬性

    public virtual ICollection<Blog> Blogs { get; set; }
}


Blog實體

Blog需要實現的關系如下:

  • 因為每個Blog屬於一個Category實體,所以需要添加一個屬性實現和Category實體的外鍵關系,以及到Category實體的導航屬性
  • 因為每個Blog屬於一個用戶實體,所以需要添加一個屬性實現和User實體的外鍵關系,以及一個到User實體的導航屬性
  • 因為每個Blog實體包含多條評論,所以需要添加Comments集合屬性作為導航屬性

代碼如下:

public partial class Blog
{
    public Blog()
    {
        Comments=new HashSet<Comment>();
    }

	//省略部分屬性
	public int CategoryId { get; set; }
	public int AuthorId { get; set; }

    public virtual Category Category { get; set; }
    public virtual User User { get; set; }
    public virtual ICollection<Comment> Comments { get; set; }
}


上面的代碼中,CategoryId屬性用於創建和Category實體的外鍵關系,AuthorId用於創建和User實體的外鍵關系。

Comment實體

  • 因為每條評論屬於一篇博客,所以需要添加一個屬性實現和Blog實體的外鍵關系,以及到Blog實體的導航屬性
  • 因為每條評論屬於一個用戶,所以需要添加一個屬性實現和User實體的外鍵關系,以及到User實體的導航屬性

代碼如下:

public partial class Comment
{
	//省略部分屬性
    public int? BlogId { get; set; }
    public int? PosterId { get; set; }
    public virtual Blog Blog { get; set; }
    public virtual User User { get; set; }
}


BolgId用於創建和Blog實體之間的外鍵關系,PosterId用於創建和User實體之間的外鍵關系。

實現DbContext類

至此,所有的實體類已經創建好了,現在是時候創建DbContext類了。DbContext類包含了我們創建的實體的DbSet對象,我們需要重寫OnModelCreating方法來指定實體間下面的關系:

  • Category和Blog之間的一對多關系
  • Blog和Comment之間的一對多關系
  • User和Blog之間的一對多關系
  • User和Comment之間的一對多關系
  • User和Role之間的多對多關系

代碼如下:

public class BlogAppContext:DbContext
{
    public BlogAppContext() : base("name=BlogAppConn")
    {
    }

    public virtual DbSet<Blog> Blogs { get; set; }
    public virtual DbSet<Category> Categories { get; set; }
    public virtual DbSet<Comment> Comments { get; set; }
    public virtual DbSet<Role> Roles { get; set; }
    public virtual DbSet<User> Users { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Category和Blog之間的一對多關系
        modelBuilder.Entity<Category>()
            .HasMany(c=>c.Blogs)
            .WithRequired(b=>b.Category)
            .HasForeignKey(b=>b.CategoryId)
            .WillCascadeOnDelete(false);

        //Blog和Comment之間的一對多關系
        modelBuilder.Entity<Blog>()
            .HasMany(b=>b.Comments)
            .WithRequired(c=>c.Blog)
            .HasForeignKey(c=>c.BlogId)
            .WillCascadeOnDelete(false);

        //User和Blog之間的一對多關系
        modelBuilder.Entity<User>()
            .HasMany(u=>u.Blogs)
            .WithRequired(b=>b.User)
            .HasForeignKey(b=>b.AuthorId)
            .WillCascadeOnDelete(false);

        //User和Comment之間的一對多關系
        modelBuilder.Entity<User>()
            .HasMany(u => u.Comments)
            .WithOptional(c => c.User)
            .HasForeignKey(c => c.PosterId);

        //User和Role之間的多對多關系
        modelBuilder.Entity<User>()
            .HasMany(u => u.Roles)
            .WithMany(r => r.Users)
            .Map(act => act.ToTable("UserRoles")
            .MapLeftKey("UserId")
            .MapRightKey("RoleId"));

        base.OnModelCreating(modelBuilder);
    }
}


上面,我們創建了DbContext類——BlogAppContext。構造函數使用了配置文件中名為BlogAppConn的連接字符串,然后重寫了 OnModelCreating方法來實現實體間的多種關系。注意最后一個多對多關系的配置,這種配置會告訴EF生成一個連接表。

執行數據訪問

現在需要實現應用程序的數據訪問層了,需要做的第一件事就是創建一個類庫,然后將所有的實體類和上下文類拷貝到類庫中,截圖如下:

圖片

要實現可擴展的數據訪問層,我們要使用倉儲和工作單元模式。

理解倉儲模式

當我們使用不同的數據模型和領域模型時,倉儲模式特別有用。倉儲可以充當數據模型和領域模型之間的中介。在內部,倉儲以數據模型的形式和數據庫交互,然后給數據訪問層之上的應用層返回領域模型。

在我們這個例子中,因為使用了數據模型作為領域模型,因此,也會返回相同的模型。如果想要使用不同的數據模型和領域模型,那么需要將數據模型的值映射到領域模型或使用任何映射庫執行映射。

現在定義倉儲接口IRepository如下:

public interface IRepository<T> where T:class
{
    IEnumerable<T> GetAllList(Expression<Func<T,bool>> predicate=null);
    T Get(Expression<Func<T, bool>> predicate);
    void Insert(T entity);
    void Delete(T entity);
    void Update(T entity);
    long Count();
}


上面的幾個方法都是常見的CRUD操作,就不解釋了,下面看看如何為實體實現具體的倉儲類:
以下是Category類的倉儲實現:

public class CategoryRepository:IRepository<Category>
{
    private BlogAppContext _context = null;

    public CategoryRepository(BlogAppContext context)
    {
        _context = context;
    }

    public IEnumerable<Category> GetAllList(System.Linq.Expressions.Expression<Func<Category, bool>> predicate = null)
    {
        if (predicate==null)
        {
            return _context.Categories.ToList();
        }
        return _context.Categories.Where(predicate).ToList();
    }

    public Category Get(System.Linq.Expressions.Expression<Func<Category, bool>> predicate)
    {
        return _context.Categories.SingleOrDefault(predicate);
    }

    public void Insert(Category entity)
    {
        _context.Categories.Add(entity);
    }

    public void Delete(Category entity)
    {
        _context.Categories.Remove(entity);
    }

    public void Update(Category entity)
    {
        _context.Categories.Attach(entity);
        _context.Entry(entity).State=EntityState.Modified;
    }

    public long Count()
    {
       return _context.Categories.LongCount();
    }
}


在上面的代碼中,我們使用了BlogAppContext類執行了Category實體上的所有CRUD操作,並實現了IRepository接口,同樣的道理,我們可以實現其他的倉儲類如 BlogRepository和CommentRepository。然而,所有的倉儲如果真的一個個寫完的話, 除了使用了上下文各自的屬性外,其他的所有代碼都是相同的。因此,我們不用為每個實體創建一個單獨的倉儲類,而是采用對所有實體類都有效的泛型倉儲。

泛型倉儲類代碼如下:

public class Repository<T>:IRepository<T> where T:class
{
    private readonly BlogAppContext _context = null;
    private readonly DbSet<T> _dbSet; 
    public Repository(BlogAppContext context)
    {
        _context = context;
        _dbSet = _context.Set<T>();
    }

    public IEnumerable<T> GetAllList(System.Linq.Expressions.Expression<Func<T, bool>> predicate = null)
    {
        if (predicate==null)
        {
            return _dbSet.AsEnumerable();
        }
        return _dbSet.Where(predicate).ToList();
    }

    public T Get(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
    {
        return _dbSet.SingleOrDefault(predicate);
    }

    public void Insert(T entity)
    {
        _dbSet.Add(entity);
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity);
    }

    public void Update(T entity)
    {
        _dbSet.Attach(entity);
        _context.Entry(entity).State=EntityState.Modified;
    }

    public long Count()
    {
        return _dbSet.LongCount();
    }
}


這個泛型倉儲類很有用,因為一個類就可以對所有的實體類執行CRUD操作。

實現泛型倉儲是很好的做法,因為一個應用可能包含上百個實體,如果每個實體都編寫倉儲的話,那么要寫太多乏味的代碼。

理解工作單元

我們已經知道,DbContext默認支持事務,當實例化一個新的DbContext對象時,就會創建一個新的事務,當調用SaveChanges方法時,事務會提交。問題是,如果我們使用相同的DbContext對象把多個代碼模塊的操作放到一個單獨的事務中,該怎么辦呢?答案就是工作單元(Unit of Work)。

工作單元本質是一個類,它可以在一個事務中跟蹤所有的操作,然后將所有的操作作為原子單元執行。看一下倉儲類,可以看到DbContext對象是從外面傳給它們的。此外,所有的倉儲類都沒有調用SaveChanges方法,原因在於,我們在創建工作單元時會將DbContext對象傳給每個倉儲。當想保存修改時,就可以在工作單元上調用SaveChanges方法,也就在DbContext類上調用了SaveChanges方法。這樣就會使得涉及多個倉儲的所有操作成為單個事務的一部分。

這里定義我們的工作單元類如下:

public class UnitOfWork:IDisposable
{
    private readonly BlogAppContext _context = null;
    private Repository<Blog> _blogRepository = null;
    private Repository<Category> _categoryRepository;
    private Repository<Comment> _commentRepository = null;
    private Repository<Role> _roleRepository = null;
    private Repository<User> _userRepository = null;

    public UnitOfWork(Repository<Blog> blogRepository, Repository<Category> categoryRepository, Repository<Comment> commentRepository, Repository<Role> roleRepository, Repository<User> userRepository)
    {
        _blogRepository = blogRepository;
        _categoryRepository = categoryRepository;
        _commentRepository = commentRepository;
        _roleRepository = roleRepository;
        _userRepository = userRepository;
        _context=new BlogAppContext();
    }

    public Repository<Blog> BlogRepository
    {
        get { return _blogRepository ?? (_blogRepository = new Repository<Blog>(_context)); }
    }

    public Repository<Category> CategoryRepository
    {
        get { return _categoryRepository ?? (_categoryRepository = new Repository<Category>(_context)); }
    }

    public Repository<Comment> CommentRepository
    {
        get { return _commentRepository ?? (_commentRepository = new Repository<Comment>(_context)); }
    }

    public Repository<Role> RoleRepository
    {
        get { return _roleRepository ?? (_roleRepository = new Repository<Role>(_context)); }
    }

    public Repository<User> UserRepository
    {
        get { return _userRepository ?? (_userRepository = new Repository<User>(_context)); }
    }

    public void SaveChanges()
    {
        _context.SaveChanges();
    }


    public void Dispose()
    {
        throw new NotImplementedException();
    }
}


上面就是我們定義的工作單元類,它會創建實際的倉儲類,然后將DbContext的相同實例傳給這些倉儲。當處理完所有的操作時,然后調用這個類的SaveChanges方法,也就是調用了DbContext類的SaveChanges方法,從而將數據保存到數據庫中。

現在,事務和這個UnitOfWork類聯系起來了,每個工作單元都有自己的事務,每個事務都可以使用相應的倉儲類處理多個實體,當調用了UnitOfWork類的SaveChanges方法時,事務就會提交。

UnitOfWork類也可以以泛型的方式實現,這樣就不需要為每個倉儲類添加一個屬性了。


這里進行數據庫遷移,生成特定的數據庫,至於連接字符串、數據庫遷移等的配置,這里不再贅述,請查看該系列前面的章節。生成數據庫后,然后插入測試數據進行測試。

給出我這里的測試數據:

/*Categories表數據*/
INSERT dbo.Categories( CategoryName )VALUES  ( N'ABP理論基礎')
INSERT dbo.Categories( CategoryName )VALUES  ( N'ABP理論高級')
INSERT dbo.Categories( CategoryName )VALUES  ( N'ABP實踐基礎')
INSERT dbo.Categories( CategoryName )VALUES  ( N'ABP實踐高級')
INSERT dbo.Categories( CategoryName )VALUES  ( N'ASP.NET')
INSERT dbo.Categories( CategoryName )VALUES  ( N'EF')
INSERT dbo.Categories( CategoryName )VALUES  ( N'JavaScript')
INSERT dbo.Categories( CategoryName )VALUES  ( N'TypeScript')

管理分類

數據訪問層的類庫已經創建好了,現在看看如何對Category實體執行各種各樣的操作。

顯示分類列表

要列出所有的分類,只需要創建UnitOfWork對象,然后使用CategoryRepository檢索數據庫中的所有分類即可:

public ActionResult Index()
{
    using (var uow=new UnitOfWork())
    {
         var categories = uow.CategoryRepository.GetAll().ToList();
         return View(categories);
    }
}


效果截圖如下:

圖片

雖然我們創建的是ASP.NET MVC項目,但是我們的重點還是EF Code First的實踐,因此,很多相關的MVC代碼大家自己可以查看源碼,只需要關注數據的讀取和寫入即可。

添加分類

要添加一個新的分類,需要創建一個Category模型,然后調用CategoryRepository的Insert方法,首先來看一下視圖:

圖片

控制器Action代碼:

[HttpPost]
public ActionResult Create(Category model)
{
    try
    {
        using (var uow = new UnitOfWork())
        {
            uow.CategoryRepository.Insert(model);
            uow.SaveChanges();
        }

        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}


更新分類

更新分類時,首先要從數據庫中檢索到該分類,然后更新必填的屬性,再調用CategoryRepository的Update方法。看一下更新時的視圖:

圖片

看一下打開編輯頁面的邏輯控制Action代碼:

public ActionResult Edit(int id)
{
    using (var uow=new UnitOfWork())
    {
        var category = uow.CategoryRepository.Get(c => c.Id == id);
        return View(category);
    }
}


上面的Edit方法,使用了id值來檢索分類,然后再將檢索到的模型傳遞到視圖上,這樣就可以更新該模型了。用戶在該模型的原始值的基礎上進行修改,點擊Save按鈕之后,該模型又會傳給控制器,下面看一下如何將更新后的模型更新到數據庫中:

[HttpPost]
public ActionResult Edit(Category model)
{
    try
    {
        using (var uow=new UnitOfWork())
        {
            uow.CategoryRepository.Update(model);
            uow.SaveChanges();
        }

        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}


刪除分類

要刪除分類,首先要確保數據庫中存在該分類,所以,先要檢索到該分類,然后調用CategoryRepository的Delete方法。下面看一下控制器的Action方法如何通過分類的Id刪除分類:

public ActionResult Delete(int id)
{
    try
    {
        using (var uow=new UnitOfWork())
        {
            var category = uow.CategoryRepository.Get(c => c.Id == id);
            uow.CategoryRepository.Delete(category);
            uow.SaveChanges();
        }

        return RedirectToAction("Index");
    }
    catch(Exception ex)
    {
        throw ex;
    }
}



刪除時,我們先要通過Id檢索到指定的分類,然后才調用倉儲的Delete方法進行刪除。

管理博客

和之前的分類管理功能類似,很多話就不再重復說了,直接貼代碼!

添加新的博客

視圖效果:

圖片

這個界面,寫過博客的人應該很熟悉了,對應的各個字段就不一一解釋了。當點擊Create按鈕時,會創建一個Blog模型,然后該模型傳給控制器的action方法,action方法使用UnitOfWork和Repository類將該Blog模型對象添加到數據庫:

[HttpPost]
public ActionResult Create(Blog blog)
{
    try
    {
        using (var uow = new UnitOfWork())
        {
            blog.CreationTime = DateTime.Now;
            blog.UpdateTime = DateTime.Now;
            //blog.AuthorId = uow.UserRepository.Get(u => u.UserName == User.Identity.Name).Id;//以后加入Identity技術時使用
            blog.AuthorId = 1;
            uow.SaveChanges();
        }

        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}


為了簡化,這里使用了textarea標簽作為博客正文的輸入,作為支持Markdown語法的網站來說,textarea也足夠用了!但是對於不熟悉Markdown語法的用戶來說,最好使用富文本編輯器,如CKEditor等等。

更新博客

就拿博客園來說,比如發表了一篇博客,如果發現有錯別字或者哪里說的不對等等,這些情況下,我們都可以對發布的博客進行更新。視圖效果:

圖片

更新展示頁面對應的Action方法如下:

public ActionResult Edit(int id)
{
    using (var uow = new UnitOfWork())
    {
        var categories = uow.CategoryRepository.GetAll().ToList();
        ViewData["categories"] = new SelectList(categories, "Id", "CategoryName");
        var blog = uow.BlogRepository.Get(b => b.Id == id);
        return View(blog);
    }
}


點擊Save按鈕后,更新視圖中的模型會傳回給Action方法:

[HttpPost]
public ActionResult Edit(Blog blog)
{
    try
    {
        using (var uow=new UnitOfWork())
        {
            blog.UpdateTime=DateTime.Now;
            uow.BlogRepository.Update(blog);
            uow.SaveChanges();
        }
        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}


以上展示了更新一個Blog實體的Action方法,視圖代碼等其他沒展示的部分請在源碼中查看。有些Action方法后期也會有所改變,也請以最終的源代碼為准。

刪除博客

public ActionResult Delete(int id)
{
    try
    {
        using (var uow=new UnitOfWork())
        {
            var blog = uow.BlogRepository.Get(b => b.Id == id);
            uow.BlogRepository.Delete(blog);
            uow.SaveChanges();
        }

        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}



博客的刪除和更新操作應該只有該博客的作者可以進行操作。因此,只有登錄的用戶是該博客的作者時,操作鏈接才應該顯示出來。因此,應該在視圖頁面中添加下面的代碼:

if(User.Identity.Name == Model.User.UserName)
{
  //操作鏈接
}

在主頁顯示博客列表

這個稍微有點棘手,因為要實現以下功能:

  • 在主頁上只顯示前10篇博客
  • 每篇博客可以看到標題和前200個字符
  • 每篇博客應該顯示評論數量
  • 每篇博客應該顯示推送日期和更新日期
  • 每篇博客應該顯示作者名字

要實現這些功能,我們要定義一個視圖模型ViewModel,然后使用Linq To Entities的投影從數據庫中檢索出所有信息。ViewModel定義如下:

// GET: /Home/
public ActionResult Index(int page=0)
{
    using (var uow=new UnitOfWork())
    {
        var blogs = uow.BlogRepository.GetAll()
            .Include(b=>b.User).Include(b=>b.Comments)
            .OrderByDescending(b => b.CreationTime)
            .Skip(page*10)
            .Take(10);

        var blogSummaryList = blogs.Select(b => new BlogViewModel
        {
            AuthorName = b.User.UserName,
            CommentsCount = b.Comments.Count,
            CreationTime = b.CreationTime,
            Id = b.Id,
            Overview = b.Body.Length>200?b.Body.Substring(0,200):b.Body,
            Title = b.Title,
            UpdateTime = b.UpdateTime
        }).ToList();

        return View(blogSummaryList);
    }
}


上面的代碼對於EF新手來說可能有些難度,但是如果你學習了之前的7篇博客,恐怕這些代碼也是很簡單的!
稍作解釋,blogs是Blog實體的分頁結果集,因為ViewModel用到了AuthorName和CommentsCount,所以必須通過預先加載的方式加載Users表和Comments表。Skip和Take是兩個重要的分頁函數,但在分頁之前,記得先要進行排序,否則會編譯出錯。blogSummaryList是對blogs進行投影,將實體集合轉換成對應的視圖模型集合。

最后看一下效果圖:

圖片

展示單條博客

當用戶點擊閱讀全文時,應該展示以下信息:

  • 博客標題、正文、博主用戶名
  • 發布日期和更新日期
  • 該博客的所有評論
  • 發表評論的超鏈接

下面看一下實現這個目的的Action方法:

public ActionResult Details(int id)
{
    using (var uow = new UnitOfWork())
    {
        var blog = uow.BlogRepository.Get(b => b.Id == id);
        var blogDetailViewModel=new BlogDetailViewModel
        {
            AuthorName = blog.User.UserName,
            Body = blog.Body,
            CreationTime = blog.CreationTime,
            Id = blog.Id,
            Title = blog.Title,
            UpdateTime = blog.UpdateTime,
            CategoryName = blog.Category.CategoryName
        };
        return View(blogDetailViewModel);
    }
}


其中,BlogDetailViewModel是一個視圖模型類,它的定義如下:

public class BlogDetailViewModel
{
    public int Id { get; set; }
    [DisplayName("標題")]
    public string Title { get; set; }
    [DisplayName("正文")]
    public string Body { get; set; }
    [DisplayName("博主")]
    public string AuthorName { get; set; }
    [DisplayName("所屬分類")]
    public string CategoryName { get; set; }
    [DisplayName("發布日期")]
    public DateTime CreationTime { get; set; }
    [DisplayName("更新日期")]
    public DateTime UpdateTime { get; set; }

}


最終效果圖如下:

圖片

至於發表評論功能,在下面的管理評論一節完成。

管理評論

添加評論

點擊“Comment”超鏈接會顯示一個評論區域和一個發表按鈕,如下:

圖片

填寫完評論后,點擊發表評論按鈕,數據提交到下面的Action方法:

[HttpPost]
public ContentResult Create(Comment comment)
{
    try
    {
        using (var uow=new UnitOfWork())
        {
            comment.PosterId = 2;//這里直接寫入Id,本該寫入當前評論者的Id
            comment.CreationTime=DateTime.Now;
            uow.CommentRepository.Insert(comment);
            uow.SaveChanges();
        }
        return Content(JsonConvert.SerializeObject(comment));
    }
    catch(Exception ex)
    {
        throw ex;
    }
}


最終效果如下:

圖片

顯示評論列表

因為我們已經在Blog實體中有了Comments導航屬性,所以,要在Blog明細的頁面上顯示評論列表,只需要循環該集合即可展示所有評論。

修改展示博客明細頁面所對應的Action方法的代碼如下:

public ActionResult Details(int id)
{
    using (var uow = new UnitOfWork())
    {
        var blog = uow.BlogRepository.Get(b => b.Id == id);
        var blogDetailViewModel=new BlogDetailViewModel
        {
            AuthorName = blog.User.UserName,
            Body = blog.Body,
            CreationTime = blog.CreationTime,
            Id = blog.Id,
            Title = blog.Title,
            UpdateTime = blog.UpdateTime,
            CategoryName = blog.Category.CategoryName
        };
        List<CommentViewModel> commentList= blog.Comments.Select(comment => new CommentViewModel
        {
            PosterName = comment.User.UserName, Message = comment.Body, CreationTime = comment.CreationTime
        }).ToList();

        ViewData["Comments"] = commentList;
        return View(blogDetailViewModel);
    }
}


視圖上對應的代碼:

<h3>評論列表</h3>

@{
    if (ViewData["Comments"] != null)
    {
        var comments = ViewData["Comments"] as List<CommentViewModel>;
        <div id="CommentContainer">
            @foreach (var comment in comments)
            {

                <hr />
                <span><strong>@comment.PosterName</strong></span> <span>評論於 @comment.CreationTime </span><br />
                <p>@comment.Message</p>

            }
        </div>
    }

}


刪除評論

public ContentResult Delete(int id)
{
    try
    {
        using (var uow=new UnitOfWork())
        {
            var comment= uow.CommentRepository.Get(c => c.Id == id);
            uow.CommentRepository.Delete(comment);
            uow.SaveChanges();
        }

        return Content("ok");
    }
    catch (Exception ex)
    {
        return Content(ex.Message);
    }
}


Action方法很簡單,前端代碼請查看源代碼。

總結

這一篇,我們基本上使用了之前7篇博文的所有知識點完成了一個小型博客平台半成品(User,Role等沒有實現),至此,我們也就有能力使用EF的Code First方式開發以數據為中心的應用。

與現實中的應用相比,這個簡單的應用知識冰山一角。現實中的應用遠比這個應用規模更大,且更復雜。但是本篇的初衷是讓開發者熟悉如何使用EF的Code First方式着手應用開發,以及如何開發端到端的功能。

到這里,這個《8天掌握EF的Code First開發》就結束了。當然,EF是一個說不盡的話題,老話說,“知易行難”,因此關於EF,我們還有很多東西要學,這需要我們每天不停地操練積累經驗。同時,還有很多優秀的第三方ORM工具,比如Dapper、NHibernate等等,但我相信,好好掌握了一個ORM,其他的都不在話下。學習完了這個系列,我覺得你應該完全准備好在任何應用中使用EF了!大家加油吧!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM