数据层,来一波?

前言:上次创建完项目后鸽了这么久,终于想起了我的账号&密码...
回归正题,我这次来尝试做个的数据层。
对于一个博客系统而言至少需要可对文章进行增删查改操作,除了对文章的操作外,还需要作者的相关信息以及该系统的访问情况。当然一个个人博客系统也可能会有用户(暂时先不考虑实现)。
以上应该足够构成一个博客系统的基本组成。
那么它们的关系大概是这样的:
作者:文章 = 1:n (一个作者可有多篇文章)
文章:系统访问情况 = 1:1(一篇文章有一份记录,记录该文章访问情况)

ok,关系还挺简单(嘿嘿,主要是做的简单)
那么这次就用Entity Framework Core和postgresql帮我完成数据层持久化操作。
需要安装一些nuget包:

  • Npgsql.EntityFrameworkCore.PostgreSQL
    PostgreSQL数据提供的支持EF Core的基础类库,是通过EF Core使用PostgreSQL数据库的根本。
  • Npgsql.EntityFrameworkCore.PostgreSQL.Design
    使用Guid(对应Postgre数据的类型为uuid)类型的主键必须,int/long类型的主键不添加也没问题。
    接下来,先创建实体。
    文章实体部分:
     /// <summary>
    /// 文章实体
    /// </summary>
    public class Article
    {
        /// <summary>
        /// 文章Id
        /// </summary>
        public int ArticleId { get; set; }
        /// <summary>
        /// 文章标题
        /// </summary>
        public string Title { get; set; } = default!;
        /// <summary>
        /// 文章内容(客户端传输base64编码内容,服务端将内容转为文本后存为静态文件,再将文件路径存入数据库)
        /// </summary>
        public string Content { get; set; } = default!;
        /// <summary>
        /// 文章创建时间
        /// </summary>
        public DateTime CreateTime { get; set; } = DateTime.Now;
        /// <summary>
        /// 文章修改时间(若未被修改则该时间为空)
        /// </summary>
        public DateTime? ModifyTime { get; set; }

        //=================文章与作者的关系=======================
        public virtual int AuthorId { get; set; }
        public virtual Author Author { get; set; } = default!;
        //=================文章与文章记录关系=====================
        public virtual int LogId { get; set; }
        public virtual ArticleLog Log { get; set; }
    }

文章记录实体部分:

    /// <summary>
    /// 文章记录
    /// </summary>
    public class ArticleLog
    {
        /// <summary>
        /// 文章记录Id
        /// </summary>
        public int ArticleLogId { get; set; }
        /// <summary>
        /// 文章浏览次数
        /// </summary>
        public int ViewNum { get; set; }
        //===============文章与记录的关系==================
        //因为文章与记录为一对一关系,所以只需一方有个外键就行了。在这里我将外键映射放在文章实体中
        //public virtual int ArticleId { get; set; }
        public virtual Article Article { get; set; } = default!;
    }

作者实体部分:

    /// <summary>
    /// 作者实体
    /// </summary>
    public class Author
    {
        /// <summary>
        /// 作者Id
        /// </summary>
        public int AuthorId { get; set; }
        /// <summary>
        /// 名称
        /// </summary>
        public string Name { get; set; } = default!;
        /// <summary>
        /// 作者账号
        /// </summary>
        public string Account { get; set; } = default!;
        /// <summary>
        /// 作者账号的密码
        /// </summary>
        public string Password { get; set; } = default!;
        /// <summary>
        /// 上次登录时间
        /// </summary>
        public DateTime LoginTime { get; set; }
        //=================作者与文章的关系================
        public virtual IEnumerable<Article>? Articles { get; set; }
    }

注意:
1.如果使用了开启可空类型而在使用引用类型不初始化,则会警告或报错类型为空(导致编译不通过),所以上述代码中对于引用类型使用了default!来初始化(看起来是没问题了),but,这样做只是编译通过了,它的赋值结果还是null。
2.实体之间关系需要使用virtual关键字修饰

接下来添加映射关系(使用EFCore中为我们定义的IEntityTypeConfiguration 接口):
文章实体与数据库之间的映射关系:

    public class ArticleConfig : IEntityTypeConfiguration<Article>
    {
        public void Configure(EntityTypeBuilder<Article> builder)
        {
            //在数据库中该实体被映射为表:article
            builder.ToTable("article");
            //以实体中ArticleId字段映射为数据库中名称为pk_id的主键
            builder.HasKey(k => k.ArticleId);
            //将主键映射为pk_id为名称、INTEGER为类型的自增长字段
            builder.Property(k => k.ArticleId).ValueGeneratedOnAdd().HasColumnType("INTEGER").HasColumnName("pk_id");
            //实体中Content字段段映射为数据库中名称为content、类型为VARCHAR(50)非空的字段
            builder.Property(p => p.Title).HasColumnType("VARCHAR(50)").HasColumnName("title").IsRequired();
            //实体中Content字段段映射为数据库中名称为content、类型为VARCHAR(512)非空的字段
            builder.Property(p => p.Content).HasColumnType("VARCHAR(512)").HasColumnName("content").IsRequired();
            //实体中CreateTime字段段映射为数据库中名称为create_time、类型为DATE的非空字段
            builder.Property(p => p.CreateTime).HasColumnType("DATE").HasColumnName("create_time").IsRequired();
            //实体中ModifyTime字段段映射为数据库中名称为modify_time、类型为DATE的可空字段
            builder.Property(p => p.ModifyTime).HasColumnType("DATE").HasColumnName("modify_time").IsRequired(false);
            //文章与作者之间的关系,使用文章实体中AuthorId字段作为外键,映射为数据库中名为fk_article_id、类型为INTEGER的非空外键
            builder.Property(f => f.AuthorId).HasColumnType("INTEGER").HasColumnName("fk_article_id").IsRequired();
            builder.HasOne(o => o.Author).WithMany(m => m.Articles).HasForeignKey(f => f.AuthorId);
            //文章与文章记录之间的关系,使用文章实体中LogId字段作为外键,映射为数据库中名为fk_log_id、类型为INTEGER的非空外键
            builder.Property(f=>f.LogId).HasColumnType("INTEGER").HasColumnName("fk_log_id").IsRequired();
            builder.HasOne(o => o.Log).WithOne(o => o.Article).HasForeignKey<Article>(f => f.LogId);
            //文章与作者关系已在作者实体配置,此处不需要重复配置
        }
    }

文章记录实体与数据库之间的映射关系:

  /// <summary>
    /// 文章记录实体与数据库之间映射关系
    /// </summary>
    public class ArticleLogConfig : IEntityTypeConfiguration<ArticleLog>
    {
        public void Configure(EntityTypeBuilder<ArticleLog> builder)
        {
            //在数据库中该实体被映射为表:article_log
            builder.ToTable("article_log");
            //以实体中ArticleLogId字段映射为数据库中名称为pk_id、INTEGER类型的自增长主键
            builder.HasKey(k => k.ArticleLogId);
            //将主键映射为pk_id为名称、INTEGER为类型的自增长字段
            builder.Property(k => k.ArticleLogId).ValueGeneratedOnAdd().HasColumnType("INTEGER").HasColumnName("pk_id");
            //实体中ViewNum字段段映射为数据库中名称为view_num、类型为INTEGER的非空字段
            builder.Property(p => p.ViewNum).HasColumnType("INTEGER").HasColumnName("view_num").IsRequired();
            //映射关系已经在文章配置中配置,所以此处不需要配置
        }
    }

作者实体与数据库之间映射关系

     /// <summary>
    /// 作者实体与数据库之间映射关系
    /// </summary>
    public class AuthorConfig : IEntityTypeConfiguration<Author>
    {
        public void Configure(EntityTypeBuilder<Author> builder)
        {
            //在数据库中该实体被映射为表:author
            builder.ToTable("author");
            //以实体中AuthorId字段映射为数据库中主键
            builder.HasKey(k => k.AuthorId);
            //将主键映射为pk_id为名称、INTEGER为类型的自增长字段
            builder.Property(k => k.AuthorId).ValueGeneratedOnAdd().HasColumnType("INTEGER").HasColumnName("pk_id");
            //实体中Name字段段映射为数据库中名称为name、类型为VARCHAR(20)的非空字段
            builder.Property(p => p.Name).HasColumnType("VARCHAR(20)").HasColumnName("name").IsRequired();
            //实体中Account字段段映射为数据库中名称为account、类型为VARCHAR(40)的非空字段
            builder.Property(p => p.Account).HasColumnType("VARCHAR(40)").HasColumnName("account").IsRequired();
            //实体中Password字段段映射为数据库中名称为account、类型为VARCHAR(32)的非空字段
            //密码部分使用MD5摘要后存入数据库(毕竟不能限制密码长度=_=)
            builder.Property(p => p.Password).HasColumnType("VARCHAR(32)").HasColumnName("password").IsRequired();
            //实体中LoginTime字段段映射为数据库中名称为login_time、类型为DATE的非空字段
            builder.Property(p => p.LoginTime).HasColumnType("DATE").HasColumnName("login_time").IsRequired();
        }
    }

好啦,数据实体与映射规则有了,还需要帮我们完成映射的上下文,emmmmmmmmm就叫DbManager吧。

    /// <summary>
    /// 管理数据库的连接与实体的映射
    /// </summary>
    public class DbManager:DbContext
    {
        //数据库连接字符串
        private string? _connectString;
        public DbManager(string connectString)
        {
            _connectString = connectString;
        }
        /// <summary>
        /// 应用实体映射规则(没错就是我们创建的以Config结尾的那些类)
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //这里将自动加载所有的映射类=_=
            //如果希望手动加载,则(以AuthorConfig为例):
            //modelBuilder.ApplyConfiguration(new AuthorConfig());
            //使用Assembly.GetExecutingAssembly();会有性能问题
            //使用typeof(TSelf).Assembly表示当前程序集
            modelBuilder.ApplyConfigurationsFromAssembly(typeof(DbManager).Assembly);
            base.OnModelCreating(modelBuilder);
        }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (_connectString!=null)
            {
                optionsBuilder.UseNpgsql(_connectString);
            }
            base.OnConfiguring(optionsBuilder);
        }
    }

为了确保延迟加载,避免导航属性的循环引用,需要引入个nuget包:
Microsoft.EntityFrameworkCore.Proxies
最终的DbManager代码:

    /// <summary>
    /// 管理数据库的连接与实体的映射
    /// </summary>
    public class DbManager:DbContext
    {
        //数据库连接字符串
        private string? _connectString;
        public DbManager(string connectString)
        {
            _connectString = connectString;
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfigurationsFromAssembly(typeof(DbManager).Assembly);
            base.OnModelCreating(modelBuilder);
        }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (_connectString!=null)
            {
                optionsBuilder.UseNpgsql(_connectString);
            }
            //确认启用延迟加载,以免自动加载导航属性从而循环引用问题
            optionsBuilder.UseLazyLoadingProxies();
            base.OnConfiguring(optionsBuilder);
        }
    }

行了,现在我们的实体有了,数据库配置也行了,我们还差个对外的调用类,就取名为IDbMapper和DbMapper吧。(什么?为什么有两?嘿嘿,因为在dotnet core中一般是以注入的方式提供服务)
(先来波同步哒)

     /// <summary>
    /// 对外的数据层访问接口
    /// </summary>
    public interface IDbMapper
    {
        /// <summary>
        /// 向数据库中加入实体数据
        /// </summary>
        /// <typeparam name="TEntity">待加入的实体类型</typeparam>
        /// <param name="entity">待加入的实体数据</param>
        /// <returns>被追踪的实体对象</returns>
        TEntity? Add<TEntity>(TEntity entity) where TEntity : class, new();
        /// <summary>
        /// 删除数据库中的实体数据
        /// </summary>
        /// <typeparam name="TEntity">待删除的实体类型</typeparam>
        /// <param name="entity">待删除的实体数据</param>
        /// <returns>被删除实体数据</returns>
        TEntity? Delete<TEntity>(TEntity entity) where TEntity : class, new();
        /// <summary>
        /// 更新数据库中数据
        /// </summary>
        /// <typeparam name="TEntity">待更新的实体类型</typeparam>
        /// <param name="entity">待更新的实体类型</param>
        /// <returns>返回追踪的实体数据</returns>
        TEntity? Update<TEntity>(TEntity entity) where TEntity : class, new();
        /// <summary>
        /// 获取所有实体数据
        /// </summary>
        /// <typeparam name="TEntity">待获取的实体类型</typeparam>
        /// <returns>数据库中数据列表</returns>
        IEnumerable<TEntity> GetAllEntities<TEntity>() where TEntity : class, new();
        /// <summary>
        /// 获取第一个实体数据
        /// </summary>
        /// <typeparam name="TEntity">待获取的实体类型</typeparam>
        /// <returns>数据库中第一个该类型数据</returns>
        TEntity? GetFirstEntity<TEntity>() where TEntity : class, new();
        /// <summary>
        /// 获取符合条件的数据列表
        /// </summary>
        /// <typeparam name="TEntity">待获取的数据类型</typeparam>
        /// <param name="exp">Lamda条件语句</param>
        /// <returns>符合条件的数据列表</returns>
        IEnumerable<TEntity> GetEntities<TEntity>(Expression<Func<TEntity, bool>> exp) where TEntity : class, new();
        /// <summary>
        /// 获取一个符合条件的数据
        /// </summary>
        /// <typeparam name="TEntity">待获取的数据类型</typeparam>
        /// <param name="exp">Lamda条件语句</param>
        /// <returns>符合条件的数据</returns>
        TEntity? GetEntity<TEntity>(Expression<Func<TEntity, bool>> exp) where TEntity : class, new();
    }

DbMapper的实现部分代码

    /// <summary>
    /// 数据访问层实现
    /// </summary>
    public class DbMapper:IDbMapper
    {
        protected DbManager dbManager { get; }
        public DbMapper(DbManager dbManager)
        {
            this.dbManager = dbManager;
        }
        protected virtual IEnumerable<TEntity> CompileQuery<TEntity>(Expression<Func<TEntity, bool>> exp) where TEntity : class, new()
        {
            var fn = EF.CompileQuery((DbManager context, Expression<Func<TEntity, bool>> exps) =>
                context.Set<TEntity>().Where(exps));
            return fn(dbManager, exp);
        }

        protected virtual TEntity? CompileQuerySingle<TEntity>(Expression<Func<TEntity, bool>> exp) where TEntity : class, new()
        {
            var fn = EF.CompileQuery((DbManager context, Expression<Func<TEntity, bool>> exps) => context.Set<TEntity>().FirstOrDefault(exps));
            return fn(dbManager, exp);
        }
        public TEntity? Add<TEntity>(TEntity entity) where TEntity : class, new()
        {
            TEntity? result = null;
            try
            {
                var trace = dbManager.Set<TEntity>().Add(entity);
                dbManager.SaveChanges();
                result = trace.Entity;
            }
            catch
            {
                //TODO:记录异常
            }

            return result;
        }

        public TEntity? Delete<TEntity>(TEntity entity) where TEntity : class, new()
        {
            TEntity? result = null;
            try
            {
                var trace = dbManager.Set<TEntity>().Remove(entity);
                dbManager.SaveChanges();
                result = trace.Entity;
            }
            catch
            {
                //TODO:记录异常
            }

            return result;

        }

        public TEntity? Update<TEntity>(TEntity entity) where TEntity : class, new()
        {
            TEntity? result = null;
            try
            {
                var trace = dbManager.Set<TEntity>().Update(entity);
                dbManager.SaveChanges();
                result = trace.Entity;
            }
            catch
            {
                //TODO:记录异常
            }

            return result;

        }
        public IEnumerable<TEntity> GetEntities<TEntity>(Expression<Func<TEntity, bool>> exp) where TEntity : class, new()
        {
            return CompileQuery(exp);
        }

        public TEntity? GetEntity<TEntity>(Expression<Func<TEntity, bool>> exp) where TEntity : class, new()
        {
            return CompileQuerySingle(exp);
        }

        public IEnumerable<TEntity> GetAllEntities<TEntity>() where TEntity : class, new()
        {
            return dbManager.Set<TEntity>().ToList();
        }

        public TEntity? GetFirstEntity<TEntity>() where TEntity : class, new()
        {
            return dbManager.Set<TEntity>().FirstOrDefault();
        }
    }

最终代码结构:

呼~,最后我们来测试一波~
测试方法:

        [TestMethod]
        public void DbDataTest()
        {
            var connectString = @"咱的数据库连接字符串~~~";
            var dbManager = new DbManager(connectString);
            var dbMapper = new DbMapper(dbManager);
            //确保数据库被删除啦
            dbManager.Database.EnsureDeleted();
            //确保数据库被新建
            dbManager.Database.EnsureCreated();
            dbMapper.Add(new Author
            {
                Name = "233",
                Account = "233",
                Password = "233",
                LoginTime=DateTime.Now
            });
            var author = dbMapper.GetFirstEntity<Author>();
            Assert.IsTrue(author.Name == "233");
        }
    }

结果如下:

数据库中数据:

打完收工~~~~

猜你喜欢

转载自www.cnblogs.com/sncufan/p/12394628.html