用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写完,下面看怎么调用
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
url=http://192.168.5.153:9001/Api/V3/List?query={user(id:"3c263730-34aa-4927-b78c-1f9da348980f") { id, name,nick,phone} }
这样就很神奇了,通过统一的查询入口,得到不同的结果值,是不是有点颠覆rest风格api。接着看
这个查询结果的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的一种探索,不敢断言其应用场景。文章有不对的地方,欢迎指正。