web
You’re offline. This is a read only version of the page.
close
Skip to main content

Announcements

News and Announcements icon
Community site session details

Community site session details

Session Id :

How to Solve Circular Model Dependencies in D365FO using Delegates

Bharani Preetham Peraka Profile Picture Bharani Preetham Pe... 3,634 Moderator

Breaking the Loop: How to Solve Circular Dependencies in D365FO Using Delegates


Have you ever been coding in Dynamics 365 Finance and Operations and hit a brick wall where the compiler tells you that you cannot reference a specific model?

Many developers face this exact challenge. Out of frustration, they often end up copying and pasting the same class into multiple models just to make the compiler happy. This leads to code duplication, maintenance nightmares, and messy architecture.

Today, we are going to look at the clean, architectural way to solve this using Delegates.

The "Strike in Mind": When Do You Need a Delegate?

In D365FO, code is separated into packages or "models." The dependency flow is strictly one-way. If Model B references Model A, then Model A is completely blind to Model B. Model A cannot use any tables, classes, or methods that live in Model B. If it tries, you get a circular dependency error.

You need a Delegate when: You are writing code inside a Base Model, and you realize, "I really need to execute a calculation or update that exists in a higher-layer model, but the compiler won't let me call it."

The Radio Station:

Imagine Model A is a radio station, and Model B is a guy with a radio in his house.

The radio station (Model A) doesn't know who is listening. It doesn't know the guy's name, where he lives, or what he is doing. All the station does is broadcast a signal: "Hey, a new Sales Line was just created!"

The guy (Model B) knows all about the radio station. He tunes into that specific frequency. When he hears the broadcast, he jumps up and does his chores (the heavy calculation logic).

By using this setup, the radio station never has to know about the guy, but the guy still gets the job done exactly when the station tells him to.

The Code Flow

Let's build this radio station in X++.

The Broadcaster (Inside Base Model):

First, we create our "radio signal" in the lower model. This is called a Delegate. We define what the signal looks like (in this case, it carries a SalesLine record), and we create a button to broadcast it.

class SalesLineCalcDelegateHandler
{ 
// 1. The Signal: We define a static delegate. It doesn't have any code inside! 
    static delegate void processSalesLineDelegate(SalesLine _salesLine) 
    {
    }

// 2. The Button: A method we can call from anywhere in this base model to broadcast the signal.
    public static void triggerSalesLineProcess(SalesLine _salesLine)
    {
        SalesLineCalcDelegateHandler::processSalesLineDelegate(_salesLine);                       
    }
}

How to use it: Whenever the base model needs that calculation to run, it simply pushes the button: SalesLineCalcDelegateHandler::triggerSalesLineProcess(mySalesLine);

The Listener (Inside the Upper Model)

Now, in higher model (which has all the complex logic), we create the listener.

We use the [SubscribesTo] attribute to tune into the exact class and delegate name we created in the base model. Because this upper model is allowed to reference the base model, it can easily see the radio station.

class SalesLineCalculationProcess
{
    // 1. The Listener: It waits for the base model to fire the delegate.
    [SubscribesTo(classStr(SalesLineCalcDelegateHandler), staticDelegateStr(SalesLineCalcDelegateHandler, processSalesLineDelegate))]
    public static void processSalesLineDelegateHandler(SalesLine _salesLine)
    {
        // When it hears the signal, it instantiates itself and runs the heavy logic
        SalesLineCalculationProcess calcClass = new SalesLineCalculationProcess();
        calcClass.calculateSOLines(_salesLine);
    }

    // 2. The actual logic that the base model couldn't reach.
    private void calculateSOLines(SalesLine _salesLine)
    {
        // All complex X++ logic, API calls, or pricing calculations go here!

        //Logic with _salesLine  
    }
}

Why This is Beautiful Architecture

By utilizing this flow, both models are completely decoupled.

The Base Model compiles perfectly because it is only referencing its own delegate class.

The Upper Model executes the logic flawlessly because it listens to the Base Model.

Zero code duplication!

The next time you find yourself wanting to copy-paste a method into a lower model to avoid a circular dependency error, stop!

Build a radio station instead:)

Happy Learning!


This article is originally posted here.

Comments