{"id":3654002,"date":"2023-05-04T08:00:15","date_gmt":"2023-05-04T12:00:15","guid":{"rendered":"https:\/\/spin.atomicobject.com\/?p=3654002"},"modified":"2023-05-03T11:26:57","modified_gmt":"2023-05-03T15:26:57","slug":"ef-code-first-migrations","status":"publish","type":"post","link":"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/","title":{"rendered":"Detecting Errors in Entity Framework Code First Migrations"},"content":{"rendered":"<p>Our team uses Entity Framework&#8217;s code first migrations to manage updates to our database schema. Having <a href=\"https:\/unit-testing-timeoutexception\/\">not worked much with EF before<\/a>, we didn&#8217;t understand the mechanics of managing these migrations well. That, along with a lack of care in reviewing and validating new migrations, led to an embarrassing number of broken migrations reaching our main development branch.<\/p>\n<p>I&#8217;ve learned more about EF code first migrations, since then. However, we still make mistakes that are difficult to catch with code review alone. In this post, I&#8217;ll explain the different error cases we&#8217;ve encountered. I&#8217;ll also talk about how we&#8217;ve shifted some of the responsibility for detecting these onto our test pipelines. That increases our confidence in the migrations we let reach our main branch.<\/p>\n<h2>Generating and Applying Migrations<\/h2>\n<p>I have since read through more EF guides and experimented more with the tool. Here is my current understanding of generating and applying migrations:<\/p>\n<ol>\n<li>Before creating a new migration, changes are made to the models and model configuration classes.<\/li>\n<li>Running <code>dotnet ef migration add MyNewMigration<\/code> adds a new Migration class and updates the <code>DbContextModelSnapshot<\/code> with the new changes to the model\/model configuration.\n<ul>\n<li>The <code>Up<\/code> and <code>Down<\/code> methods of the generated Migration class apply schema changes to the development DB via <code>dotnet ef database update<\/code>.<\/li>\n<li>The model snapshot comes into play during the <em>next<\/em> migration. EF references the DB model snapshot to determine what schema changes can achieve the <em>new<\/em> updates to the models\/model configurations.<\/li>\n<li>And if that next migration needed to be removed while a developer was iterating on the schema changes, the previous migration&#8217;s <code>BuildTargetModel<\/code> function would restore the model snapshot<\/li>\n<\/ul>\n<\/li>\n<li>Finally, when the time comes to put out a new release, <a href=\"https:\/\/learn.microsoft.com\/en-us\/ef\/core\/managing-schemas\/migrations\/applying?tabs=dotnet-core-cli#sql-scripts\" target=\"_blank\" rel=\"noopener\">the recommended way<\/a> to apply changes to the production database is by generating and running SQL scripts.<\/li>\n<\/ol>\n<p>This EF doc about <a href=\"https:\/\/learn.microsoft.com\/en-us\/ef\/ef6\/modeling\/code-first\/migrations\/teams\" target=\"_blank\" rel=\"noopener\">Code First Migrations in Team <\/a>Environments is a great resource for learning more about generating and applying migrations. It also provides concrete examples of some issues I&#8217;ll identify next.<\/p>\n<h2>Error Cases Encountered<\/h2>\n<p>With that context, let&#8217;s look at a few error cases we&#8217;ve encountered on our project. Additionally, I&#8217;ll review the EF CLI commands we run in our pipeline to detect these issues proactively. Note the omission in the following examples of any project-specific configuration or arguments such as connection string or EF project name.<\/p>\n<h3>The migration is not regenerated with model\/model configuration changes.<\/h3>\n<p>You&#8217;ll often need to change the model configuration based on feedback during code review or while iterating. If you then forget to regenerate the migration, the migration and model snapshot won&#8217;t contain those latest changes to the model.<\/p>\n<p>But, say the snapshot and model are drastically out of sync, perhaps missing an entire table or column. Then your application code would fail while your test suite is running. However, something more subtle like adding a unique constraint or a new index might go undetected. That could slip through to the main development branch. In that case, the migration SQL scripts generated to apply changes to the production DB would <em>not<\/em> contain all the desired changes.<\/p>\n<p>Either way, you can detect this issue by generating a temporary test migration and inspecting its contents. If the current model and model configuration are in sync with the <code>DbContextModelSnapshot<\/code>, the generated test migration should be empty. To perform this validation, we checked in a target migration file we know to contain empty <code>Up<\/code> and <code>Down<\/code> methods. We then generate a new migration and compare the files.<\/p>\n<pre><code class=\"language-bash\">\r\n# generate a new migration\r\ndotnet ef migrations add EmptyMigration\r\n\r\n# search the current directory for the generated file (ignoring the timestamp on the file name)\r\nGENERATED=\"$(find . -type f -iname \"*EmptyMigration.cs\")\"\r\nif [[ $GENERATED == \"\" ]]; then\r\n    echo \"Couldn't find the generated empty migration file\"\r\n    exit 1\r\nfi\r\n\r\n# diff the files - ignoring any whitespace diff\r\nEMPTY=true\r\ndiff --ignore-blank-lines --ignore-all-space .\/TargetMigration.cs $GENERATED || EMPTY=false\r\n\r\nif [[ $EMPTY != \"true\" ]]; then\r\n    echo \"Did not get an empty migration! Verify that the current model snapshot is up to date.\"\r\n    echo \"Generated Migration Contents:\"\r\n    cat $GENERATED\r\nfi\r\n\r\nif [[ $EMPTY != \"true\" ]]; then\r\n    exit 1\r\nfi\r\n<\/code><\/pre>\n<h3>The latest migration&#8217;s <code>BuildTargetModel<\/code> is out of sync with the current model snapshot.<\/h3>\n<p>If two developers work on unrelated schema changes in parallel, each developer&#8217;s migrations will not include the other developer&#8217;s changes. For the developer whose code changes reach the main development branch first, this is no problem. The model snapshot and the migration they added will contain identical schema configurations.<\/p>\n<p>Then, when the second developer&#8217;s code is ready to go in, their changes to the model snapshot can be applied during the merge. In that case, the model snapshot will be consistent with the model\/model configurations. However, if the second developer fails to regenerate their migration after the first developer&#8217;s migration goes in, the second developer&#8217;s <code>BuildTargetModel<\/code> snapshot won&#8217;t contain the schema changes added by the first developer&#8217;s migration.<\/p>\n<p>In this scenario, the <code>Up<\/code> and <code>Down<\/code> methods of each migration are valid. So, this only becomes a source of annoyance when another developer adds another migration <em>after<\/em> the second developer&#8217;s. If that third developer gets it right on the first try, no problem. Otherwise, developer three will encounter trouble if they must revert to an earlier migration and remove their own incorrect migration. Reverting the new migration will go fine, but <em>removing<\/em> the new migration will cause problems. That&#8217;s because the last migration&#8217;s invalid <code>BuildTargetModel<\/code> will be applied to the overall model snapshot.<\/p>\n<p>To detect this kind of inconsistency between the model snapshot and the last migration&#8217;s <code>BuildTargetModel<\/code>, extend the above script with the following:<\/p>\n<pre><code class=\"language-bash\">\r\n# check model snapshot content before removing test migration\r\nCONTENTS_BEFORE=$(cat MyProject\/DbContextModelSnapshot.cs)\r\n\r\n# remove the test migration\r\ndotnet ef migrations remove\r\n\r\nCONTENTS_AFTER=$(cat MyProject\/DbContextModelSnapshot.cs)\r\n\r\nSNAPSHOTS_MATCH=true\r\ndiff --ignore-blank-lines --ignore-all-space &lt;(echo \"$CONTENTS_BEFORE\" ) &lt;(echo \"$CONTENTS_AFTER\") || SNAPSHOTS_MATCH=false\r\nif [[ $SNAPSHOTS_MATCH != \"true\" ]]; then\r\n    echo \"Latest migration target model != current model snapshot!\"\r\n    exit 1\r\nfi\r\n<\/code><\/pre>\n<h3>There&#8217;s invalid syntax in the generated migration SQL scripts.<\/h3>\n<p>The final error we&#8217;ve run into is invalid syntax found in generated migration SQL scripts. Running <code>dotnet ef migrations script<\/code> generates SQL scripts used to deploy database changes to the production database. By default, each migration is wrapped in a transaction. So, if there is a syntax error (from custom SQL added) in the migration script, the problematic migration won&#8217;t be applied. But subsequent migrations will (assuming they don&#8217;t conflict with the skipped migration).<\/p>\n<p>For our team, this issue manifested when a trailing semi-colon was omitted from custom SQL added to one of our migrations. When the <code>--idempotent<\/code> option is used, the SQL to apply a migration is run conditionally <em>if<\/em> the migration in question is not found in the <code>__EFMigrationsHistory<\/code> table. So, all schema changes (including custom SQL added to migrations) are wrapped in if blocks. This caused our application code to begin to fail due to an expected field on the model missing from the production DB.<\/p>\n<p>To check for this issue, we updated our validate migrations script to run the run SQL scripts from the migrations.<\/p>\n<pre><code class=\"language-bash\">\r\n# generate sql scripts from migrations\r\ndotnet ef migrations script --idempotent --output migrations.sql\r\n\r\nNO_SNEAKY_SYNTAX_ERRORS=true\r\npsql ON_ERROR_STOP=1 -f migrations.sql || NO_SNEAKY_SYNTAX_ERRORS=false\r\n\r\nif [[ $NO_SNEAKY_SYNTAX_ERRORS != \"true\" ]]; then\r\n    echo \"Got an error running generated migrations.sql - double check any custom SQL added in your migration (remember your trailing semi-colons!!)\"\r\n    exit 1\r\nfi\r\n<\/code><\/pre>\n<h2>Benefits of EF code first migrations<\/h2>\n<p>Improving our understanding of EF code first migrations has allowed us to set up some guardrails in our test pipeline. That lets us detect and prevent the kind of errors I&#8217;ve outlined. Again, I highly recommend EF&#8217;s <a href=\"https:\/\/learn.microsoft.com\/en-us\/ef\/ef6\/modeling\/code-first\/migrations\/teams\" target=\"_blank\" rel=\"noopener\">Code First Migrations in Team Environments<\/a> article for further reading and guidance on resolving these errors.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Our team uses Entity Framework&#8217;s code first migrations to manage updates to our database schema. Having not worked much with EF before, we didn&#8217;t understand the mechanics of managing these migrations well. That, along with a lack of care in reviewing and validating new migrations, led to an embarrassing number of broken migrations reaching our [&hellip;]<\/p>\n","protected":false},"author":575,"featured_media":175203,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[23446],"tags":[1188],"series":[],"class_list":["post-3654002","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-c","tag-database"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Detecting Errors in Entity Framework Code First Migrations<\/title>\n<meta name=\"description\" content=\"Add a &#039;validate migrations&#039; step to your test pipeline to detect and prevent subtle errors in EF Code First migrations.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Detecting Errors in Entity Framework Code First Migrations\" \/>\n<meta property=\"og:description\" content=\"Add a &#039;validate migrations&#039; step to your test pipeline to detect and prevent subtle errors in EF Code First migrations.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/\" \/>\n<meta property=\"og:site_name\" content=\"Atomic Spin\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/atomicobject\" \/>\n<meta property=\"article:published_time\" content=\"2023-05-04T12:00:15+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/spin.atomicobject.com\/wp-content\/uploads\/atomic-teams-JillDeVriesPhotography-Sept2019-295-2-scaled.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1707\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Anjali Munasinghe\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@atomicobject\" \/>\n<meta name=\"twitter:site\" content=\"@atomicobject\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Anjali Munasinghe\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/ef-code-first-migrations\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/ef-code-first-migrations\\\/\"},\"author\":{\"name\":\"Anjali Munasinghe\",\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/#\\\/schema\\\/person\\\/0a923dd1460661e963e91c2c44c8fb28\"},\"headline\":\"Detecting Errors in Entity Framework Code First Migrations\",\"datePublished\":\"2023-05-04T12:00:15+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/ef-code-first-migrations\\\/\"},\"wordCount\":1088,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/atomicobject.com\\\/\"},\"image\":{\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/ef-code-first-migrations\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/spin.atomicobject.com\\\/wp-content\\\/uploads\\\/atomic-teams-JillDeVriesPhotography-Sept2019-295-2-scaled.jpg\",\"keywords\":[\"database\"],\"articleSection\":[\"C#\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/spin.atomicobject.com\\\/ef-code-first-migrations\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/ef-code-first-migrations\\\/\",\"url\":\"https:\\\/\\\/spin.atomicobject.com\\\/ef-code-first-migrations\\\/\",\"name\":\"Detecting Errors in Entity Framework Code First Migrations\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/ef-code-first-migrations\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/ef-code-first-migrations\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/spin.atomicobject.com\\\/wp-content\\\/uploads\\\/atomic-teams-JillDeVriesPhotography-Sept2019-295-2-scaled.jpg\",\"datePublished\":\"2023-05-04T12:00:15+00:00\",\"description\":\"Add a 'validate migrations' step to your test pipeline to detect and prevent subtle errors in EF Code First migrations.\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/spin.atomicobject.com\\\/ef-code-first-migrations\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/ef-code-first-migrations\\\/#primaryimage\",\"url\":\"https:\\\/\\\/spin.atomicobject.com\\\/wp-content\\\/uploads\\\/atomic-teams-JillDeVriesPhotography-Sept2019-295-2-scaled.jpg\",\"contentUrl\":\"https:\\\/\\\/spin.atomicobject.com\\\/wp-content\\\/uploads\\\/atomic-teams-JillDeVriesPhotography-Sept2019-295-2-scaled.jpg\",\"width\":2560,\"height\":1707,\"caption\":\"3 Characteristics of Successful Teams\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/#website\",\"url\":\"https:\\\/\\\/spin.atomicobject.com\\\/\",\"name\":\"Atomic Spin\",\"description\":\"Atomic Object\u2019s blog on everything we find fascinating.\",\"publisher\":{\"@id\":\"https:\\\/\\\/atomicobject.com\\\/\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/spin.atomicobject.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/#organization\",\"name\":\"Atomic Object\",\"url\":\"https:\\\/\\\/spin.atomicobject.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/spin.atomicobject.com\\\/wp-content\\\/uploads\\\/AO-Logo-Emblem-Color.png\",\"contentUrl\":\"https:\\\/\\\/spin.atomicobject.com\\\/wp-content\\\/uploads\\\/AO-Logo-Emblem-Color.png\",\"width\":258,\"height\":244,\"caption\":\"Atomic Object\"},\"image\":{\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/atomicobject\",\"https:\\\/\\\/x.com\\\/atomicobject\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/spin.atomicobject.com\\\/#\\\/schema\\\/person\\\/0a923dd1460661e963e91c2c44c8fb28\",\"name\":\"Anjali Munasinghe\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/742381b462f14dacf8facf847fac57b85089c47247c28f1f7520be4069238b1b?s=96&d=blank&r=pg\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/742381b462f14dacf8facf847fac57b85089c47247c28f1f7520be4069238b1b?s=96&d=blank&r=pg\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/742381b462f14dacf8facf847fac57b85089c47247c28f1f7520be4069238b1b?s=96&d=blank&r=pg\",\"caption\":\"Anjali Munasinghe\"},\"description\":\"Software Consultant &amp; Developer at Atomic Object Ann Arbor. Member of Cell Three of the Accelerator Program. Happy to be here.\",\"url\":\"https:\\\/\\\/spin.atomicobject.com\\\/author\\\/anjali-munasinghe\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Detecting Errors in Entity Framework Code First Migrations","description":"Add a 'validate migrations' step to your test pipeline to detect and prevent subtle errors in EF Code First migrations.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/","og_locale":"en_US","og_type":"article","og_title":"Detecting Errors in Entity Framework Code First Migrations","og_description":"Add a 'validate migrations' step to your test pipeline to detect and prevent subtle errors in EF Code First migrations.","og_url":"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/","og_site_name":"Atomic Spin","article_publisher":"https:\/\/www.facebook.com\/atomicobject","article_published_time":"2023-05-04T12:00:15+00:00","og_image":[{"width":2560,"height":1707,"url":"https:\/\/spin.atomicobject.com\/wp-content\/uploads\/atomic-teams-JillDeVriesPhotography-Sept2019-295-2-scaled.jpg","type":"image\/jpeg"}],"author":"Anjali Munasinghe","twitter_card":"summary_large_image","twitter_creator":"@atomicobject","twitter_site":"@atomicobject","twitter_misc":{"Written by":"Anjali Munasinghe","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/#article","isPartOf":{"@id":"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/"},"author":{"name":"Anjali Munasinghe","@id":"https:\/\/spin.atomicobject.com\/#\/schema\/person\/0a923dd1460661e963e91c2c44c8fb28"},"headline":"Detecting Errors in Entity Framework Code First Migrations","datePublished":"2023-05-04T12:00:15+00:00","mainEntityOfPage":{"@id":"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/"},"wordCount":1088,"commentCount":0,"publisher":{"@id":"https:\/\/atomicobject.com\/"},"image":{"@id":"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/#primaryimage"},"thumbnailUrl":"https:\/\/spin.atomicobject.com\/wp-content\/uploads\/atomic-teams-JillDeVriesPhotography-Sept2019-295-2-scaled.jpg","keywords":["database"],"articleSection":["C#"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/","url":"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/","name":"Detecting Errors in Entity Framework Code First Migrations","isPartOf":{"@id":"https:\/\/spin.atomicobject.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/#primaryimage"},"image":{"@id":"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/#primaryimage"},"thumbnailUrl":"https:\/\/spin.atomicobject.com\/wp-content\/uploads\/atomic-teams-JillDeVriesPhotography-Sept2019-295-2-scaled.jpg","datePublished":"2023-05-04T12:00:15+00:00","description":"Add a 'validate migrations' step to your test pipeline to detect and prevent subtle errors in EF Code First migrations.","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/spin.atomicobject.com\/ef-code-first-migrations\/#primaryimage","url":"https:\/\/spin.atomicobject.com\/wp-content\/uploads\/atomic-teams-JillDeVriesPhotography-Sept2019-295-2-scaled.jpg","contentUrl":"https:\/\/spin.atomicobject.com\/wp-content\/uploads\/atomic-teams-JillDeVriesPhotography-Sept2019-295-2-scaled.jpg","width":2560,"height":1707,"caption":"3 Characteristics of Successful Teams"},{"@type":"WebSite","@id":"https:\/\/spin.atomicobject.com\/#website","url":"https:\/\/spin.atomicobject.com\/","name":"Atomic Spin","description":"Atomic Object\u2019s blog on everything we find fascinating.","publisher":{"@id":"https:\/\/atomicobject.com\/"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/spin.atomicobject.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/spin.atomicobject.com\/#organization","name":"Atomic Object","url":"https:\/\/spin.atomicobject.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/spin.atomicobject.com\/#\/schema\/logo\/image\/","url":"https:\/\/spin.atomicobject.com\/wp-content\/uploads\/AO-Logo-Emblem-Color.png","contentUrl":"https:\/\/spin.atomicobject.com\/wp-content\/uploads\/AO-Logo-Emblem-Color.png","width":258,"height":244,"caption":"Atomic Object"},"image":{"@id":"https:\/\/spin.atomicobject.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/atomicobject","https:\/\/x.com\/atomicobject"]},{"@type":"Person","@id":"https:\/\/spin.atomicobject.com\/#\/schema\/person\/0a923dd1460661e963e91c2c44c8fb28","name":"Anjali Munasinghe","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/742381b462f14dacf8facf847fac57b85089c47247c28f1f7520be4069238b1b?s=96&d=blank&r=pg","url":"https:\/\/secure.gravatar.com\/avatar\/742381b462f14dacf8facf847fac57b85089c47247c28f1f7520be4069238b1b?s=96&d=blank&r=pg","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/742381b462f14dacf8facf847fac57b85089c47247c28f1f7520be4069238b1b?s=96&d=blank&r=pg","caption":"Anjali Munasinghe"},"description":"Software Consultant &amp; Developer at Atomic Object Ann Arbor. Member of Cell Three of the Accelerator Program. Happy to be here.","url":"https:\/\/spin.atomicobject.com\/author\/anjali-munasinghe\/"}]}},"_links":{"self":[{"href":"https:\/\/spin.atomicobject.com\/wp-json\/wp\/v2\/posts\/3654002","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/spin.atomicobject.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/spin.atomicobject.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/spin.atomicobject.com\/wp-json\/wp\/v2\/users\/575"}],"replies":[{"embeddable":true,"href":"https:\/\/spin.atomicobject.com\/wp-json\/wp\/v2\/comments?post=3654002"}],"version-history":[{"count":0,"href":"https:\/\/spin.atomicobject.com\/wp-json\/wp\/v2\/posts\/3654002\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/spin.atomicobject.com\/wp-json\/wp\/v2\/media\/175203"}],"wp:attachment":[{"href":"https:\/\/spin.atomicobject.com\/wp-json\/wp\/v2\/media?parent=3654002"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/spin.atomicobject.com\/wp-json\/wp\/v2\/categories?post=3654002"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/spin.atomicobject.com\/wp-json\/wp\/v2\/tags?post=3654002"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/spin.atomicobject.com\/wp-json\/wp\/v2\/series?post=3654002"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}