Data Access Patterns

Efficient Data Access Patterns in MemberJunction

This article explains several approaches for accessing related data across multiple tables in MemberJunction applications, with recommendations for different scenarios.

Introduction

When building applications with MemberJunction, developers often need to access data that spans multiple related tables. This is a common requirement in data-rich applications where entities have complex relationships. This guide explores the most efficient approaches for these scenarios.

Understanding the Challenge

Consider this common scenario: You have a record ID in your UI application and need to retrieve related data that's several tables away. For example, in a conversation log system:

  • You start with a ConversationDetail record
  • You need to access data in ConversationDetailPrompt
  • Then you need details from the related Prompt table

While you could use multiple RunView calls or create a stored procedure, there are more efficient patterns available within MemberJunction.

Solution Options

Option 1: Use RunViews for Parallel Execution

The RunViews method (plural) offers a significant advantage over multiple RunView (singular) calls:

// Instead of multiple individual calls:
const conversationDetails = await entityMgr.runView('ConversationDetailView', {...});
const promptDetails = await entityMgr.runView('PromptView', {...});

// Use a single RunViews call:
const results = await entityMgr.runViews([
  { viewName: 'ConversationDetailView', params: {...} },
  { viewName: 'PromptView', params: {...} }
]);

Key Benefits:

  • Single network payload to the server
  • Queries execute in parallel against the database
  • Excellent for unrelated or loosely related queries
  • Reduces round-trip latency

Option 2: Create a Custom Query with RunQuery

For more complex data access patterns, you can create a custom query:

// Define a custom query in the Queries entity
// Then execute it:
const results = await entityMgr.runQuery('GetConversationWithPromptDetails', {
  conversationDetailId: 123
});

This approach allows you to:

  • Create precisely optimized queries for your specific needs
  • Denormalize data when appropriate
  • Handle complex join scenarios efficiently

Option 3: Client-Side Processing

For moderate amounts of data that don't change frequently during a session:

// Pull a reasonable amount of data
const allConversations = await entityMgr.runView('ConversationDetailExtendedView', {...});

// Then filter/process on the client
const filteredResults = allConversations.filter(c => c.someCondition === true);

This approach recognizes that:

  • Network bandwidth and client-side processing are relatively inexpensive
  • Reduces round trips to the server
  • Works well for data sets of reasonable size

Option 4: Datasets for Mostly Static Data

For data that rarely changes (like lookup tables, type definitions, etc.):

// Load a dataset (which caches internally)
const typeDataset = await datasetMgr.getDataset('TypeDefinitions');

// Access the cached data 
const types = typeDataset.getItem('ConversationTypes').records;

Key Benefits:

  • Smart client-side caching
  • Automatic cache invalidation based on update timestamps
  • Significant performance improvements for relatively static data
  • Perfect for type tables and reference data

Recommendations by Scenario

ScenarioRecommended Approach
Related data spanning multiple tablesRunQuery with custom query
Multiple independent queriesRunViews for parallel execution
Frequently accessed reference dataDataset with caching
Moderate data volume with client filteringClient-side processing
Complex, one-off queriesCustom stored procedure

Implementation Notes

When using Datasets:

  • Ensure entities have a date column indicating when records were last updated
  • Explore existing Dataset and DatasetItem records in the database to understand implementation patterns
  • Consider Dataset management for any application data that changes infrequently

Conclusion

MemberJunction provides multiple efficient patterns for accessing related data. By selecting the appropriate pattern for your specific scenario, you can optimize both performance and developer experience in your applications.