web api 2 学习笔记 (Odata basic)
开发时不同状况下的方案 :
1. 权限管理 , 在expand时要确保被expand的资源是无需访问权限的,因为expand在后台很难做where, 所以针对有权限要求的资源就不允许expand而改用另一些接口,比如 members(5)/orders 而不是members + expand orders
1.1 针对不同权限能expand的column不同也是非常难搞的,基本上就是要在controller 通过分析ODataQueryOptions.selectExpand 才能处理.
2.级联删除,最好是通过sql来做级联删除,除非我们需要做历史垃圾桶记入才用EF实现,EF的级联是需要加载所有的子孙资源出来,才能透过删除parent自能的删除子层,性能不佳.
3.post model validation & logic fill , 简单就是用自带validation的,困难的就开方法, logic fill 也是开方法
4.column 不允许修改就透过开方法拦截saveChange set isModified = false 来实现。
5.并发问题,基本上都是采用乐观并发,就是说大家都可以随时改,但是最终save的时候可能会error。
主要就是看新value是依据哪些资源来确定的,我们就必须确保那些资源都是没人修改过的。(大部分情况下都是依据资源本身,所以EF也只支持到那,但是实际情况下可能会更复杂)
insert也是会有并发问题的,而且并不一定是unique。只要一个action依赖其它资源,在执行action时就要确保依赖的资源是没被修改过的。
返回 HttpResponseMessage
public async Task<HttpResponseMessage> post(Product product) { db.products.Add(product); await db.SaveChangesAsync(); var response = Request.CreateResponse<Product>(HttpStatusCode.OK, product); response.Headers.Add("custome", "haha"); return response; }
Put [ODataRoute("({id})")]public async Task<IHttpActionResult> put(int id, Color color, ODataQueryOptions<Color> options){
if (options.IfMatch != null && !(options.IfMatch.ApplyTo(db.colors.Where(c => c.id == id)) as IQueryable<Color>).Any()) { //concurrency } else { } color.id = id; db.Entry(color).State = EntityState.Modified;
db.Entry(companySQL).CurrentValues.SetValues(companyDTO);
db.Entry(companySQL).OriginalValues["rowVersion"] = companyDTO.rowVersion;
db.Entry(color).Property(c => c.Products_id).IsModified = false;
await db.SaveChangesAsync();
this.detachedAllEntity();
return Ok(color);
}
前台的request 要加header "if-Match": resource["@odata.etag"]
Get navigationProps
[ODataRoute("({id})/product")] [ODataRoute("({id})/sizes")] public async Task<IHttpActionResult> getProp(int id) { //future: 没有实现动态的原因是因为目前没办法做好返回的类型,简单返回object的话,odata to json 时没办法匹配到EDM var navigationPropName = this.Url.Request.RequestUri.Segments.Last(); switch (navigationPropName) { case "sizes": return Ok(db.colors.Where(c => c.id == id).SelectMany(c => c.sizes)); case "product": return Ok((await db.colors.Include(c => c.product).FirstOrDefaultAsync(c => c.id == id)).product); default: return BadRequest(); } }
Single result
public SingleResult<LoginPerson> getLoginPersonByUserObject() { if (!(User is LoginPerson)) throw new AjaxEx("noCookie"); LoginPerson LoginPerson = User as LoginPerson; IQueryable<LoginPerson> query = db.LoginPersons.Include(l => l.roles).Include(l => l.accountBindings).Where(l => l.id == LoginPerson.id); return SingleResult.Create<LoginPerson>(query); }
Get
[ODataRoute("")] [EnableQuery(AllowedQueryOptions = WebApiConfig.ALLOW_QUERY | AllowedQueryOptions.Expand, MaxExpansionDepth = 5)] public IQueryable<Employer> get() { return db.Employers; }
Get + 权限管理
refer : http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options
动态 EnableQuery
不是用 options.ApplyTo , 因为他会破坏 $expand 和 $select,以后可能可以. refer : http://stackoverflow.com/questions/18879779/select-and-expand-break-odataqueryoptions-how-to-fix
[ODataRoute("")] [EnableQuery] //开放到完 public IQueryable<Employer> get(ODataQueryOptions<Employer> options) { //动态验证 //base on 不同权限设置 ODataValidationSettings var settings = new ODataValidationSettings() { AllowedQueryOptions = WebApiConfig.ALLOW_QUERY }; //一定要用 catch try { options.Validate(settings); } catch (ODataException ex) { throw new AjaxEx(ex.Message); } return db.Employers; }
many to many RPC
public class UpdateMMCategorysBody { public List<int> removeIds { get; set; } public List<int> addIds { get; set; } } [ODataRoute("({id})/RPC.updateMMCategorys")] public async Task<IHttpActionResult> updateMMCategorys(int id, UpdateMMCategorysBody data) { var product = await db.products.Include(p => p.categorys).FirstOrDefaultAsync(p => p.id == id); if (product == null) throw new AjaxEx("productNotFound"); for (int i = product.categorys.Count - 1; i >= 0; i--) { var category = product.categorys[i]; if (data.removeIds.Contains(category.id)) { product.categorys.Remove(category); } } var categoryForAdds = await db.categorys.Where(c => data.addIds.Contains(c.id)).ToListAsync(); if (categoryForAdds.Count != data.addIds.Count) throw new AjaxEx("categoryNotFound"); categoryForAdds.ForEach(c => { product.categorys.Add(c); }); await db.SaveChangesAsync(); return Ok("ok"); }
BaseController :
public class BaseController : ODataController { protected DB db = DB.Sharing.getDb(); protected override void Dispose(bool disposing) { DB.Sharing.disposeDb(); DB.Sharing.disposeTansaction(); base.Dispose(disposing); } protected void detachedAllEntity() { var manager = ((IObjectContextAdapter)this.db).ObjectContext.ObjectStateManager; var objectStateEntries = manager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified | EntityState.Unchanged); foreach (var objectStateEntry in objectStateEntries) { db.Entry(objectStateEntry.Entity).State = EntityState.Detached; } } }
POST with model validation
refer :http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api
refer : https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx
[ODataRoute("")] public async Task<IHttpActionResult> post(Employer employer) { if (ModelState.IsValid) { db.Employers.Add(employer); await db.SaveChangesAsync(); return Ok(employer); } else { return BadRequest(ModelState); } }
获取完整的 restful hateoas response
note : builder.EntitySet<Class>("Classs"); <-每一个class都要写,不然expand的话会error哦
$http({ url: "//localhost:11207/api/Employers", headers : { Accept: "application/json;odata.metadata=full" }, method : "GET" });
常用的 url 写法
/api/Products?$filter=colors/any(c:c/id gt 0); 找出有color的product
(本来应该用$count的而不是 gt 0, but refer http://forums.asp.net/t/2070250.aspx Web+API+2+OData+v4+filter+Count+of+a+Collection)
/api/Products?$filter=not(colors/any(c:c/id gt 0)); 找出没有color的product
/api/Colors?$filter=type eq EFDB.ColorType'a'; filter by Enum
/api/comments?$filter=user/EFDB.Salesman/salesmanColumn eq 'abc' filter 抽象类和属性
/api/EmployerTicketSlots?$filter=EFDB.EmployerTicketSlot/id gt 0 , filter抽象类 本来应该不是用 gt 0,but 暂时没找到正规写法
for expand : /api/Singles?$expand=abstracts($expand=EFDB.AA/childs,EFDB.BB/childs) expand 派生类和子层, 可以同时多种派生类哦
/api/Products?createDt gt 2015-10-10T08:00:00.0000000+08:00 filter datetimeoffset,用UTC也可以, js : new Date().toJSON();
/api/Products?$filter=contains(code,'mk100')) filter like
/api/Products?$expand=colors($filter=ID eq 5;$expand=sizes;$orderby=ID desc;$select=name); nested expand
/api/Products?$expand=* 全部都expand, 只是 1 层而已噢
/api/people?$expand=upline($level=2) 直接拿2层upline
/api/people?$expand=directMembers($count=true) $expand也可以$count哦
更新 : 2016-02-12
OData 的继承顺序
parent constructor -> child construtor -> child ExecuteAsync -> child Initialize
constructor 阶段request is null, child ExecuteAsync and Initialize 内部就可以调用base了,顺序就不太重要了.
更新 : 2016-06-03
http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_The_$format_System
http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part2-url-conventions.html
http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part3-csdl.html
http://www.odata.org/documentation/
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 百万级群聊的设计实践
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)
2014-09-28 Entity Framework with MySQL 学习笔记一(insert,update,delete)