GraphQl+ef+mysql打造属于自己的数据请求网关

时间:2024-03-30 09:31:42

 用EF已经有段时间了,通常都是和sql server搭配使用,开发效率确实不错,闲来无事,就整了套ef+mysql玩玩,ef+mysql与ef+sql server差别还是挺大的,下面就说说我遇到的坑,

   我在模型层没有引入EF,故没办法使用EF的特性为我的字段做约束,这也是我期望的结果,我希望模型层应该和数据库无关。模型定义完执行

1,启用数据迁移:Enable-Migrations,会自动生成一个Migrations/Configuration.cs文件,自动迁移默认为false
2,增加数据迁移项: Add-Migration record,会自动生成数据库建表脚本,
3,执行数据库更新: update-database,自动生成数据库表结构

外键约束
  EF添加外键约束比较特殊,在domain层实体结构中,我们定义一个用户角色实体,

        public Guid? user_id { get; set; }

        public Guid? role_id { get; set; }

        public User user { get; set; }

        public Role role { get; set; }

注意事项

   user_id 和role_id必须为可空字段,如果不是可空字段,数据库会生成               user_id,user_id_id,role_id,role_id_id。实体关系映射

   public class UserRoleMapper: EntityTypeConfiguration<UserRole>

    {

        public UserRoleMapper()

        {

            HasKey(d => d.id);

            HasOptional(p => p.user).WithMany(p => p.User_role).HasForeignKey(p => p.user_id).WillCascadeOnDelete(false);

            HasOptional(p => p.role).WithMany(p => p.Role_user).HasForeignKey(p => p.role_id).WillCascadeOnDelete(false);

        }

    }

注意事项

    通常我们开发的会定义一个基类BaseDbContext,实现表的增删查改,在定义一个MainBCUnitOfWork类去集成BaseDbContext,这样 Enable-Migrations命令在多个Dbcontext时候会报错,需要指定为哪一个Dbcontext执行数据迁移,具体解决方法是:Enable-Migrations -ContextTypeName MainBCUnitOfWork

 这里简单介绍了ef+mysql遇到的坑,如果您的程序能够正常运行,我们接着往下说GraphQL,

 关于什么是GraphQL,小白可以去百度一下,这里假设读者对此有一定的认识,我们需要引用GraphQL.Net,源码地址是https://github.com/ckimes89/graphql-net,nuget直接搜索GraphQL.Net,安装。在Repository层创建用于查询的   首先定义IGraphQlRepository接口,

 public interface IGraphQlRepository: IRepository<Entity>

    {

        IDictionary<string, object> List(string query);

    }

再创建GraphQlRepository类实现IGraphQlRepository接口

public class GraphQlRepository: Repository<Entity>, IGraphQlRepository

    {

        private  GraphQL<MainBCUnitOfWork> gql ;

        public GraphQlRepository(MainBCUnitOfWork unitOfWork) : base(unitOfWork)

        {

            InitGraphQL();

        }

        private void InitGraphQL()

        {

            LoggerFactory.GetInstance().Info("create graphql start");

            var schema = GraphQL<MainBCUnitOfWork>.CreateDefaultSchema(() => UnitOfWork as MainBCUnitOfWork);

            schema.AddScalar(new { year = 0, month = 0, day = 0 }, ymd => new DateTime(ymd.year, ymd.month, ymd.day));

            var user = schema.AddType<User>();

            user.AddField(u => u.id);

            user.AddField(u => u.name);

            user.AddField(u => u.nick);

            user.AddField(u => u.phone);

            user.AddField(u => u.email);

            user.AddField(u => u.User_role);

            user.AddField("total", (db, u) => db.User.Count());

            user.AddField("user_role_id", (db, u) => u.User_role.FirstOrDefault().role_id);

            var user_role = schema.AddType<UserRole>();

            user_role.AddField(u => u.id);

            user_role.AddField(u => u.role);

            user_role.AddField(u => u.role_id);

            user_role.AddField(u => u.role);

            var role = schema.AddType<Role>();

            role.AddField(u => u.id);

            role.AddField(u => u.name);

            role.AddField(u => u.Role_action);

            var roleAction = schema.AddType<RoleAction>();

            roleAction.AddField(u => u.id);

            roleAction.AddField(u => u.action_type);

            roleAction.AddField(u => u.navigation);

            var navigation = schema.AddType<Navigation>();

            navigation.AddField(u => u.id);

            navigation.AddField(u => u.action_type);

            navigation.AddField(u => u.is_sys);

            navigation.AddField(u => u.name);

            navigation.AddField(u => u.title);

            schema.AddListField("user_roles", db => db.User_role);

            //schema.AddListField("users", db => db.User);

            schema.AddField("user", new { id = Guid.Empty}, (db, args) => db.User.FirstOrDefault(u => u.id == args.id));

            schema.AddListField("users", new { id = Guid.Empty, name = string.Empty }, (db, args) => db.User.AsQueryable().Where(u => u.id == args.id||u.name== args.name));

            schema.AddListField("usersByGuid", new { guid = Guid.Empty },

                  (db, args) => db.User.AsQueryable().Where(a => a.id == args.guid));

            schema.Complete();

            gql = new GraphQL<MainBCUnitOfWork>(schema);

            LoggerFactory.GetInstance().Info("create graphql end "+ gql.GetHashCode());

        }

        public IDictionary<string, object> List(string query)

        {

            // var results = gql.ExecuteQuery("{ user(id:\"3c263730-34aa-4927-b78c-1f9da348980f\") { id, name,nick,user_role {id,role_id} } }");

            var results = gql.ExecuteQuery(query);

            return results;

        }

    }

在构造器中初始化schema,每次查询都需要通过schema定义的属性组合sql语句,此段初始化代码本想放到静态变量中,这样不用每次查询都要初始化schema,但是GraphQL在每次完成查询之后DbContext会被销毁,此时执行gql.ExecuteQuery会抛出异常,构造器中初始化schema总给人一种不舒服的感觉,等找到更好的办法再说吧。

定义IGraphQlAppService接口

  public interface IGraphQlAppService

    {

        IDictionary<string, object> List(string query);

    }

定义GraphQlAppService实现该接口

public class GraphQlAppService : IGraphQlAppService

    {

        private readonly IGraphQlRepository graphQlRepository;

        public GraphQlAppService(IGraphQlRepository _graphQlRepository)

        {

            graphQlRepository = _graphQlRepository;

        }

        public IDictionary<string, object> List(string query)

        {

            return graphQlRepository.List(query);

        }

    }

我用的是微软的Unity容器,

            container.RegisterType<IGraphQlAppService, GraphQlAppService>();

            container.RegisterType<IGraphQlRepository, GraphQlRepository>();

OK,完成注册。

我用的是webapi实现前后台数据的交互,webapi来做GraphQL!对,没看错,好别扭的感觉,好吧,谁让这是我的第一个GraphQL呢,

新建一个webapi控制器,

        [Route("Api/V3/List")]

        [HttpGet]

        public JObject List()

        {

            var graphQlAppService = IoCFactory.Instance.CurrentContainer.Resolve<IGraphQlAppService>();

            var query = HttpContext.Current.Request["query"].ToString();

            var results = graphQlAppService.List(query);

            var actual = JObject.FromObject(results, Serializer);

            return actual;

        }

此时一个简单的GraphQL写完,下面看怎么调用

GraphQl+ef+mysql打造属于自己的数据请求网关

url=http://192.168.5.153:9001/Api/V3/List?query={ user(id:"3c263730-34aa-4927-b78c-1f9da348980f") { id, name,nick,phone,user_role_id,} }

查询id="3c263730-34aa-4927-b78c-1f9da348980f" 的用户id, name,nick,phone,user_role_id信息。

此时去掉user_role_id

GraphQl+ef+mysql打造属于自己的数据请求网关

url=http://192.168.5.153:9001/Api/V3/List?query={user(id:"3c263730-34aa-4927-b78c-1f9da348980f") { id, name,nick,phone} }

这样就很神奇了,通过统一的查询入口,得到不同的结果值,是不是有点颠覆rest风格api。接着看

GraphQl+ef+mysql打造属于自己的数据请求网关

这个查询结果的url=http://192.168.5.153:9001/Api/V3/List?query={ user(id:"3c263730-34aa-4927-b78c-1f9da348980f"){id,name,nick,phone,user_role_id,user_role {id,role_id,role{id,name,role_action{action_type,navigation{action_type,name,title}}}} } }

      通过自定义query参数实现了按需查找结果,至此一个完整的GraphQL的demo已经成型,这种获取数据的方式相当前卫,但却不一定是最好的方式,比如如何管理权限,与EF的紧密耦合会导致领域模型暴露出去。配合node实现轻量级的数据访问网关确实是一个很不错的选择,但用到c#实现起来未免有些怪异。GraphQL为我们提供了一种很友好,灵活的数据交互方式,感觉这是未来编程的一个趋势。

总结:

     个人比较欣赏用nodejs去做数据网关(单纯的存取数据),GraphQL看上去是一个不错的选择,但是没有做过性能测试,不太清楚高并发会对数据库有和影响,下一步准备用c#简单做个性能测试。本篇文章仅仅是对GraohQL的一种探索,不敢断言其应用场景。文章有不对的地方,欢迎指正。