microsoft / DacFx

DacFx, SqlPackage, and other SQL development libraries enable declarative database development and database portability across SQL versions and environments. Share feedback here on dacpacs, bacpacs, and SQL projects.
https://aka.ms/sqlpackage-ref
MIT License
314 stars 19 forks source link

Late initialization of ModelTypeClasses (at least) such as PrimaryKeyConstraint.TypeClass?? #394

Open JasonKleban opened 7 years ago

JasonKleban commented 7 years ago

I'm experimenting with a SSDT DeploymentPlanModifier C# project that, for now, enumerates various constraints being dropped and re-added - this is being loaded by the SqlPackage.exe command-line utility with the /Properties:AdditionalDeploymentContributors= Mine.MyContributor switch.

I'm able to repeatedly able to demonstrate a frustrating condition of what I guess is a late initialization of some of the DacFX internals. PrimaryKeyConstraint.TypeClass is null, as is PrimaryKeyConstraint.Columns, when the following code is run at full speed:

[ExportDeploymentPlanModifier("Mine.MyContributor", "1.0.0.0")]
public class MyContributor: DeploymentPlanModifier
{
    protected override void OnExecute(DeploymentPlanContributorContext context)
    {
        // PrimaryKeyConstraint
        PublishMessage(new ExtensibilityError($"PrimaryKeyConstraints", Severity.Message));
        ProcessConstraints(context, PrimaryKeyConstraint.TypeClass, PrimaryKeyConstraint.Columns);
    }
}

If I set a breakpoint in Visual Studio before the ProcessConstraints() call, the debugger breaks there correctly and the Autos show that both PrimaryKeyConstraint.TypeClass and PrimaryKeyConstraint.Columns are null! Adding an explicit Watch shows the same. Eventually (a minute? though not necessarily time-based), and without allowing the execution to Continue, the Autos eventually change to the non-null, expected values! I'm kind of baffled by this - with the debugger paused, I don't think it's possible for some background thread responsible for populating this value to complete its work - so it must be based on some side-effect of me probing the various values through the IDE. If I wait until after these values are populated to Continue execution, the program runs correctly and gives the expected output. If I don't wait, I can step-into ProcessConstraints() with the debugger showing that null was passed in - and this gives unexpectedly blank behavior.

I'm not doing any threading or async operations in ProcessConstraints() (nor anywhere else). Is there some initialization of the DacFX, or Ready event that I must wait for?

Note: I am debugging by having deployed the latest assemblies to C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\130\Extensions\MyContributor and running the correlating version of SqlPackage C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\130\SqlPackage.exe and not using the Visual Studio Hosting process (not that I know that it's interfering with anything).

The code for ProcessConstraints() is uninteresting for this problem, I think. It goes wrong at the very beginning, since typeClass is clearly being passed in as null:

private void ProcessConstraints(DeploymentPlanContributorContext context, ModelTypeClass typeClass, ModelRelationshipClass relationshipClass)
    {
        var droppedPKConstraints =
            context.ComparisonResult.ElementsToDrop
            .Where(ele => ele.ObjectType == typeClass)
            ...
kevcunnane commented 7 years ago

@JasonKleban - thanks for raising this issue. Looking into this, we initialize this in a static constructor for the ModelSchema class. Clearly it's not getting called in this path. The fix internally is very simple and I'll get it in for the next available release.

To workaround this, call any method or property on ModelSchema - in your example, you can replace

ProcessConstraints(context, PrimaryKeyConstraint.TypeClass, PrimaryKeyConstraint.Columns);

with

var pkTypeClass = ModelSchema.PrimaryKeyConstraint;
ProcessConstraints(context, pkTypeClass, PrimaryKeyConstraint.Columns);