[{"data":1,"prerenderedAt":857},["ShallowReactive",2],{"blog:2015:table-per-hierarchy-in-azure-table-storage":3,"blogMore-Development":843,"comments-table-per-hierarchy-in-azure-table-storage":856},{"id":4,"title":5,"body":6,"category":826,"commentCount":827,"date":828,"description":12,"excerpt":829,"extension":830,"filenames":831,"hidden":832,"image":831,"meta":833,"minutes":77,"navigation":101,"path":834,"seo":835,"showCategory":831,"stem":836,"tags":837,"updated":831,"url":840,"wordCount":841,"__hash__":842},"content\u002Fblog\u002F2015\u002Ftable-per-hierarchy-in-azure-table-storage.md","Table per hierarchy in Azure Table Storage",{"type":7,"value":8,"toc":824},"minimark",[9,13,16,19,197,200,203,329,332,574,577,580,732,735,811,814,820],[10,11,12],"p",{},"If you’re coming from an ORM background to Azure Table Storage, you might be wondering how to map class hierarchies to tables.",[10,14,15],{},"Documentation on the topic is hard to find unless you know the magic class name EntityResolver which you can discover by digging into the Azure Client for .NET source code.",[10,17,18],{},"Let’s say we have a basic blog-style system (minimal fields shown):",[20,21,26],"pre",{"className":22,"code":23,"language":24,"meta":25,"style":25},"language-csharp shiki shiki-themes everforest-light dracula","public class Content {\n  public string Id { get; set; }\n  public string Title { get; set }\n}\n\npublic class BlogPost : Content {\n  public List\u003Cstring> Topics { get; set; }\n}\n\npublic class Page : Content {\n  public String Slug { get; set; }\n}\n","csharp","",[27,28,29,50,75,90,96,103,122,147,152,157,173,192],"code",{"__ignoreMap":25},[30,31,34,38,42,46],"span",{"class":32,"line":33},"line",1,[30,35,37],{"class":36},"s9HRq","public",[30,39,41],{"class":40},"smiwp"," class",[30,43,45],{"class":44},"sPLAf"," Content",[30,47,49],{"class":48},"s6Vpi"," {\n",[30,51,53,56,60,63,66,69,72],{"class":32,"line":52},2,[30,54,55],{"class":36},"  public",[30,57,59],{"class":58},"sXAHl"," string",[30,61,62],{"class":48}," Id { ",[30,64,65],{"class":58},"get",[30,67,68],{"class":48},"; ",[30,70,71],{"class":58},"set",[30,73,74],{"class":48},"; }\n",[30,76,78,80,82,85,87],{"class":32,"line":77},3,[30,79,55],{"class":36},[30,81,59],{"class":58},[30,83,84],{"class":48}," Title { ",[30,86,65],{"class":58},[30,88,89],{"class":48},"; set }\n",[30,91,93],{"class":32,"line":92},4,[30,94,95],{"class":48},"}\n",[30,97,99],{"class":32,"line":98},5,[30,100,102],{"emptyLinePlaceholder":101},true,"\n",[30,104,106,108,110,113,116,120],{"class":32,"line":105},6,[30,107,37],{"class":36},[30,109,41],{"class":40},[30,111,112],{"class":44}," BlogPost",[30,114,115],{"class":48}," : ",[30,117,119],{"class":118},"snuxY","Content",[30,121,49],{"class":48},[30,123,125,127,130,133,136,139,141,143,145],{"class":32,"line":124},7,[30,126,55],{"class":36},[30,128,129],{"class":118}," List",[30,131,132],{"class":48},"\u003C",[30,134,135],{"class":58},"string",[30,137,138],{"class":48},"> Topics { ",[30,140,65],{"class":58},[30,142,68],{"class":48},[30,144,71],{"class":58},[30,146,74],{"class":48},[30,148,150],{"class":32,"line":149},8,[30,151,95],{"class":48},[30,153,155],{"class":32,"line":154},9,[30,156,102],{"emptyLinePlaceholder":101},[30,158,160,162,164,167,169,171],{"class":32,"line":159},10,[30,161,37],{"class":36},[30,163,41],{"class":40},[30,165,166],{"class":44}," Page",[30,168,115],{"class":48},[30,170,119],{"class":118},[30,172,49],{"class":48},[30,174,176,178,181,184,186,188,190],{"class":32,"line":175},11,[30,177,55],{"class":36},[30,179,180],{"class":118}," String",[30,182,183],{"class":48}," Slug { ",[30,185,65],{"class":58},[30,187,68],{"class":48},[30,189,71],{"class":58},[30,191,74],{"class":48},[30,193,195],{"class":32,"line":194},12,[30,196,95],{"class":48},[10,198,199],{},"The trick is to create an instance of EntityResolver where T is your base class, e.g. Content. Strangely EntityResolver’s signature requires T implement new() so you can’t make your base class abstract.",[10,201,202],{},"Firstly we need to add to our base class an identifier for the type (in ORM terminology a type discriminator). Then we override that in the sub-types to ensure new instances get the correct type set on insertion.",[20,204,205],{"className":22,"code":23,"language":24,"meta":25,"style":25},[27,206,207,217,233,245,249,253,267,287,291,295,309,325],{"__ignoreMap":25},[30,208,209,211,213,215],{"class":32,"line":33},[30,210,37],{"class":36},[30,212,41],{"class":40},[30,214,45],{"class":44},[30,216,49],{"class":48},[30,218,219,221,223,225,227,229,231],{"class":32,"line":52},[30,220,55],{"class":36},[30,222,59],{"class":58},[30,224,62],{"class":48},[30,226,65],{"class":58},[30,228,68],{"class":48},[30,230,71],{"class":58},[30,232,74],{"class":48},[30,234,235,237,239,241,243],{"class":32,"line":77},[30,236,55],{"class":36},[30,238,59],{"class":58},[30,240,84],{"class":48},[30,242,65],{"class":58},[30,244,89],{"class":48},[30,246,247],{"class":32,"line":92},[30,248,95],{"class":48},[30,250,251],{"class":32,"line":98},[30,252,102],{"emptyLinePlaceholder":101},[30,254,255,257,259,261,263,265],{"class":32,"line":105},[30,256,37],{"class":36},[30,258,41],{"class":40},[30,260,112],{"class":44},[30,262,115],{"class":48},[30,264,119],{"class":118},[30,266,49],{"class":48},[30,268,269,271,273,275,277,279,281,283,285],{"class":32,"line":124},[30,270,55],{"class":36},[30,272,129],{"class":118},[30,274,132],{"class":48},[30,276,135],{"class":58},[30,278,138],{"class":48},[30,280,65],{"class":58},[30,282,68],{"class":48},[30,284,71],{"class":58},[30,286,74],{"class":48},[30,288,289],{"class":32,"line":149},[30,290,95],{"class":48},[30,292,293],{"class":32,"line":154},[30,294,102],{"emptyLinePlaceholder":101},[30,296,297,299,301,303,305,307],{"class":32,"line":159},[30,298,37],{"class":36},[30,300,41],{"class":40},[30,302,166],{"class":44},[30,304,115],{"class":48},[30,306,119],{"class":118},[30,308,49],{"class":48},[30,310,311,313,315,317,319,321,323],{"class":32,"line":175},[30,312,55],{"class":36},[30,314,180],{"class":118},[30,316,183],{"class":48},[30,318,65],{"class":58},[30,320,68],{"class":48},[30,322,71],{"class":58},[30,324,74],{"class":48},[30,326,327],{"class":32,"line":194},[30,328,95],{"class":48},[10,330,331],{},"Let’s say we want to store all of these in a table called ‘content’. We would typically write a small helper class to handle the cloud table and storage, e.g.",[20,333,335],{"className":22,"code":334,"language":24,"meta":25,"style":25},"public class Content {\n  public string Id { get; set; }\n  public string Title { get; set }\n  public virtual string ContentType { get; set; }\n}\n\npublic class BlogPost : Content {\n  public List\u003Cstring> Topics { get; set; }\n  public override string ContentType {\n    get { return \"blog\"; }\n    set { }\n  }\n}\n\npublic class Page : Content {\n  public String Slug { get; set; }\n  public override string ContentType {\n    get { return \"page\"; }\n    set { }\n  }\n}\n",[27,336,337,347,363,375,395,399,403,417,437,449,473,481,486,491,496,511,528,539,557,564,569],{"__ignoreMap":25},[30,338,339,341,343,345],{"class":32,"line":33},[30,340,37],{"class":36},[30,342,41],{"class":40},[30,344,45],{"class":44},[30,346,49],{"class":48},[30,348,349,351,353,355,357,359,361],{"class":32,"line":52},[30,350,55],{"class":36},[30,352,59],{"class":58},[30,354,62],{"class":48},[30,356,65],{"class":58},[30,358,68],{"class":48},[30,360,71],{"class":58},[30,362,74],{"class":48},[30,364,365,367,369,371,373],{"class":32,"line":77},[30,366,55],{"class":36},[30,368,59],{"class":58},[30,370,84],{"class":48},[30,372,65],{"class":58},[30,374,89],{"class":48},[30,376,377,379,382,384,387,389,391,393],{"class":32,"line":92},[30,378,55],{"class":36},[30,380,381],{"class":36}," virtual",[30,383,59],{"class":58},[30,385,386],{"class":48}," ContentType { ",[30,388,65],{"class":58},[30,390,68],{"class":48},[30,392,71],{"class":58},[30,394,74],{"class":48},[30,396,397],{"class":32,"line":98},[30,398,95],{"class":48},[30,400,401],{"class":32,"line":105},[30,402,102],{"emptyLinePlaceholder":101},[30,404,405,407,409,411,413,415],{"class":32,"line":124},[30,406,37],{"class":36},[30,408,41],{"class":40},[30,410,112],{"class":44},[30,412,115],{"class":48},[30,414,119],{"class":118},[30,416,49],{"class":48},[30,418,419,421,423,425,427,429,431,433,435],{"class":32,"line":149},[30,420,55],{"class":36},[30,422,129],{"class":118},[30,424,132],{"class":48},[30,426,135],{"class":58},[30,428,138],{"class":48},[30,430,65],{"class":58},[30,432,68],{"class":48},[30,434,71],{"class":58},[30,436,74],{"class":48},[30,438,439,441,444,446],{"class":32,"line":154},[30,440,55],{"class":36},[30,442,443],{"class":36}," override",[30,445,59],{"class":58},[30,447,448],{"class":48}," ContentType {\n",[30,450,451,454,457,460,464,468,471],{"class":32,"line":159},[30,452,453],{"class":58},"    get",[30,455,456],{"class":48}," { ",[30,458,459],{"class":40},"return",[30,461,463],{"class":462},"sciFF"," \"",[30,465,467],{"class":466},"sJQOs","blog",[30,469,470],{"class":462},"\"",[30,472,74],{"class":48},[30,474,475,478],{"class":32,"line":175},[30,476,477],{"class":58},"    set",[30,479,480],{"class":48}," { }\n",[30,482,483],{"class":32,"line":194},[30,484,485],{"class":48},"  }\n",[30,487,489],{"class":32,"line":488},13,[30,490,95],{"class":48},[30,492,494],{"class":32,"line":493},14,[30,495,102],{"emptyLinePlaceholder":101},[30,497,499,501,503,505,507,509],{"class":32,"line":498},15,[30,500,37],{"class":36},[30,502,41],{"class":40},[30,504,166],{"class":44},[30,506,115],{"class":48},[30,508,119],{"class":118},[30,510,49],{"class":48},[30,512,514,516,518,520,522,524,526],{"class":32,"line":513},16,[30,515,55],{"class":36},[30,517,180],{"class":118},[30,519,183],{"class":48},[30,521,65],{"class":58},[30,523,68],{"class":48},[30,525,71],{"class":58},[30,527,74],{"class":48},[30,529,531,533,535,537],{"class":32,"line":530},17,[30,532,55],{"class":36},[30,534,443],{"class":36},[30,536,59],{"class":58},[30,538,448],{"class":48},[30,540,542,544,546,548,550,553,555],{"class":32,"line":541},18,[30,543,453],{"class":58},[30,545,456],{"class":48},[30,547,459],{"class":40},[30,549,463],{"class":462},[30,551,552],{"class":466},"page",[30,554,470],{"class":462},[30,556,74],{"class":48},[30,558,560,562],{"class":32,"line":559},19,[30,561,477],{"class":58},[30,563,480],{"class":48},[30,565,567],{"class":32,"line":566},20,[30,568,485],{"class":48},[30,570,572],{"class":32,"line":571},21,[30,573,95],{"class":48},[10,575,576],{},"With that change, you can start inserting rows into Azure Table Storage. Querying them back results in Content types, and then saving those back again results in data loss!",[10,578,579],{},"We must help the CloudTable client materialize the correct results by creating an EntityResolver:",[20,581,583],{"className":22,"code":582,"language":24,"meta":25,"style":25},"EntityResolver\u003CContent> contentResolver(partitionKey, rowKey, timestamp, properties, etag) {\n  var contentType = properties[\"ContentType\"].StringValue;\n  switch (contentType) {\n    case \"blog\": return new BlogPost();\n    case \"page\": return new Page();\n    default: throw new NotSupportedException(String.Format(\"Unknown ContentType '{0}'\", contentType));\n  }\n}\n",[27,584,585,604,638,646,670,690,724,728],{"__ignoreMap":25},[30,586,587,590,592,594,597,601],{"class":32,"line":33},[30,588,589],{"class":118},"EntityResolver",[30,591,132],{"class":48},[30,593,119],{"class":118},[30,595,596],{"class":48},"> ",[30,598,600],{"class":599},"sS4Kt","contentResolver",[30,602,603],{"class":48},"(partitionKey, rowKey, timestamp, properties, etag) {\n",[30,605,606,609,612,615,619,622,624,627,629,632,635],{"class":32,"line":52},[30,607,608],{"class":58},"  var",[30,610,611],{"class":48}," contentType ",[30,613,614],{"class":36},"=",[30,616,618],{"class":617},"sSKRk"," properties",[30,620,621],{"class":48},"[",[30,623,470],{"class":462},[30,625,626],{"class":466},"ContentType",[30,628,470],{"class":462},[30,630,631],{"class":48},"].",[30,633,634],{"class":617},"StringValue",[30,636,637],{"class":48},";\n",[30,639,640,643],{"class":32,"line":77},[30,641,642],{"class":40},"  switch",[30,644,645],{"class":48}," (contentType) {\n",[30,647,648,651,653,655,657,660,662,665,667],{"class":32,"line":92},[30,649,650],{"class":40},"    case",[30,652,463],{"class":462},[30,654,467],{"class":466},[30,656,470],{"class":462},[30,658,659],{"class":48},": ",[30,661,459],{"class":40},[30,663,664],{"class":40}," new",[30,666,112],{"class":118},[30,668,669],{"class":48},"();\n",[30,671,672,674,676,678,680,682,684,686,688],{"class":32,"line":98},[30,673,650],{"class":40},[30,675,463],{"class":462},[30,677,552],{"class":466},[30,679,470],{"class":462},[30,681,659],{"class":48},[30,683,459],{"class":40},[30,685,664],{"class":40},[30,687,166],{"class":118},[30,689,669],{"class":48},[30,691,692,695,697,700,702,705,708,711,714,716,719,721],{"class":32,"line":105},[30,693,694],{"class":40},"    default",[30,696,659],{"class":48},[30,698,699],{"class":40},"throw",[30,701,664],{"class":40},[30,703,704],{"class":118}," NotSupportedException",[30,706,707],{"class":48},"(String.",[30,709,710],{"class":599},"Format",[30,712,713],{"class":48},"(",[30,715,470],{"class":462},[30,717,718],{"class":466},"Unknown ContentType '{0}'",[30,720,470],{"class":462},[30,722,723],{"class":48},", contentType));\n",[30,725,726],{"class":32,"line":124},[30,727,485],{"class":48},[30,729,730],{"class":32,"line":149},[30,731,95],{"class":48},[10,733,734],{},"Which passes into operations that materialize results. Note that some signatures don’t accept a resolver, so find one that does even if it means supplying a default OperationContent. For example:",[20,736,738],{"className":22,"code":737,"language":24,"meta":25,"style":25},"var query = table.CreateQuery\u003CContent>().Where(c => c.PartitionKey == yearMonth);\nvar results = query.ExecuteQuery(query.AsTableQuery(), contentResolver, myRequestOptions, myOperationContext);\n",[27,739,740,787],{"__ignoreMap":25},[30,741,742,745,748,750,753,756,758,760,763,766,768,772,775,778,781,784],{"class":32,"line":33},[30,743,744],{"class":58},"var",[30,746,747],{"class":48}," query ",[30,749,614],{"class":36},[30,751,752],{"class":48}," table.",[30,754,755],{"class":599},"CreateQuery",[30,757,132],{"class":48},[30,759,119],{"class":118},[30,761,762],{"class":48},">().",[30,764,765],{"class":599},"Where",[30,767,713],{"class":48},[30,769,771],{"class":770},"s7cAX","c",[30,773,774],{"class":36}," =>",[30,776,777],{"class":48}," c.",[30,779,780],{"class":617},"PartitionKey",[30,782,783],{"class":36}," ==",[30,785,786],{"class":48}," yearMonth);\n",[30,788,789,791,794,796,799,802,805,808],{"class":32,"line":52},[30,790,744],{"class":58},[30,792,793],{"class":48}," results ",[30,795,614],{"class":36},[30,797,798],{"class":48}," query.",[30,800,801],{"class":599},"ExecuteQuery",[30,803,804],{"class":48},"(query.",[30,806,807],{"class":599},"AsTableQuery",[30,809,810],{"class":48},"(), contentResolver, myRequestOptions, myOperationContext);\n",[10,812,813],{},"Given that these entity resolvers are essential to correctly materializing your results without data loss, it’s worth wrapping the CloudTable client with the necessary setup\u002Ftable-creation\u002Fentity resolver.",[10,815,816],{},[817,818,819],"em",{},"[)amien",[821,822,823],"style",{},"html pre.shiki code .s9HRq, html code.shiki .s9HRq{--shiki-default:#F57D26;--shiki-dark:#FF79C6}html pre.shiki code .smiwp, html code.shiki .smiwp{--shiki-default:#F85552;--shiki-dark:#FF79C6}html pre.shiki code .sPLAf, html code.shiki .sPLAf{--shiki-default:#3A94C5;--shiki-dark:#8BE9FD}html pre.shiki code .s6Vpi, html code.shiki .s6Vpi{--shiki-default:#5C6A72;--shiki-dark:#F8F8F2}html pre.shiki code .sXAHl, html code.shiki .sXAHl{--shiki-default:#3A94C5;--shiki-dark:#FF79C6}html pre.shiki code .snuxY, html code.shiki .snuxY{--shiki-default:#3A94C5;--shiki-default-font-style:inherit;--shiki-dark:#8BE9FD;--shiki-dark-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sciFF, html code.shiki .sciFF{--shiki-default:#8DA101;--shiki-dark:#E9F284}html pre.shiki code .sJQOs, html code.shiki .sJQOs{--shiki-default:#8DA101;--shiki-dark:#F1FA8C}html pre.shiki code .sS4Kt, html code.shiki .sS4Kt{--shiki-default:#8DA101;--shiki-dark:#50FA7B}html pre.shiki code .sSKRk, html code.shiki .sSKRk{--shiki-default:#35A77C;--shiki-dark:#F8F8F2}html pre.shiki code .s7cAX, html code.shiki .s7cAX{--shiki-default:#5C6A72;--shiki-default-font-style:inherit;--shiki-dark:#FFB86C;--shiki-dark-font-style:italic}",{"title":25,"searchDepth":52,"depth":52,"links":825},[],"Development",0,"2015-06-27T09:24:17+00:00","[object Object]","md",null,false,{},"\u002Fblog\u002F2015\u002Ftable-per-hierarchy-in-azure-table-storage",{"title":5,"description":12},"blog\u002F2015\u002Ftable-per-hierarchy-in-azure-table-storage",[838,839],".NET","Azure","\u002Fblog\u002F2015\u002Ftable-per-hierarchy-in-azure-table-storage\u002F",666,"80NQqm_GCFhyFuETSqsqeIuOWDuB8dSKz4CQdkIUxwk",[844,848,852],{"title":845,"date":846,"url":847},"Transactions in the MongoDB EF Core Provider","2025-10-25","\u002Fblog\u002F2025\u002Fmongodb-explicit-transactions\u002F",{"title":849,"date":850,"url":851},"Queryable Encryption with the MongoDB EF Core Provider","2025-09-22","\u002Fblog\u002F2025\u002Fmongodb-queryable-encryption\u002F",{"title":853,"date":854,"url":855},"Lazy Loading with EF Core Proxies","2025-04-02","\u002Fblog\u002F2025\u002Fef-proxies\u002F",[],1780900526592]