0

Summary

In our application we use Entity Framework 6 and SQL Server 2016. When importing into the EF model a stored procedure with complex logic, in particular when using temp tables, we put on top:

SET FMTONLY OFF

Which causes the stored procedure to run the full logic and hence return the correct output format, which allows the EF model to generate a complex type correctly.

In general, it works OK. However, Microsoft documentation https://learn.microsoft.com/en-us/sql/t-sql/statements/set-fmtonly-transact-sql?view=sql-server-ver15 states: Do not use this feature. So I'm wondering what the recommended way is to import a stored procedure and generate a complex type correctly.

More details

Firstly, I'm confused by this note Do not use this feature on the Microsoft website about FMTONLY. Isn't it the same command (with option ON) called by the EF model updater? This would mean the EF model updater uses a feature which should not be used.

Secondly, I assume that this EF model updater behaviour aims to prevent an unwanted DB data manipulation when a stored procedure is executed with all input arguments equal NULL. But the actual behaviour does not seemed aligned with this assumption.

Consider a simple stored procedure below where I tried to avoid using SET FMTONLY OFF. I analysed the exact behaviour using SQL profiler while updating the EF model from Visual Studio (2013, CU 5, Pro).

SET NOCOUNT ON;

IF (1=0) --FOR EF edmx update this will actually execute
BEGIN
    DECLARE @tempTable TABLE
                       (
                           [id] [int], 
                           [date_local] [datetime2](7), 
                           [value] [float] NULL
                       )

    SELECT * FROM @tempTable
    RETURN 0
END

CREATE TABLE #TempValue
(
    [id] [int] NOT NULL,
    [date_local] [datetime2](7) NOT NULL,
    [value] [float] NULL

    CONSTRAINT [PK_TempValue] 
        PRIMARY KEY CLUSTERED ([id] ASC, [date_local] ASC)
                    WITH (PAD_INDEX =OFF, STATISTICS_NORECOMPUTE = OFF, 
                          IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
                          ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]

INSERT INTO #TempValue (id, date_local, value)
    SELECT @id, *
    FROM OPENJSON(@valueJson)
    WITH (
          date_local datetime2(7) '$.dateTime',
          value float '$.value'
         );

SELECT * FROM #TempValue

Points:

  1. The EF model updater calls it, goes inside IF (1=0) (as expected with SET FMTONLY ON), and runs SELECT * from @tempTable correctly.

  2. It ignores return 0 and continues to run the rest of the stored procedure body.

  3. It skips CREATE TABLE #TempValue but then tries to run INSERT INTO #TempValue which of course doesn't exist, so it stops here and doesn't execute SELECT * FROM #TempValue, which would also fail.

In this case the complex type was generated correctly, because the last successful select was SELECT * from @tempTable. But I feel this was just luck because there are no subsequent selects which actually run. To me, this whole behaviour is flawed, in particular the skipping of return 0 and CREATE TABLE #TempValue but then allowing INSERT INTO; if you allow INSERT INTO surely you're not preserving the DB data?

So the 2 workarounds we tried that actually work are as follows:

  1. Put SET FMTONLY OFF on top. It works but I'm a bit worried by this Do not use this feature on the Microsoft website.

  2. Comment out whole SP body and add DECLARE @tempTable table ... SELECT * from @tempTable as in example above. After updating EF model remove the added chunk and uncomment the original code. Do this every time EF model is updated, so it's not very efficient.

What is a proper and recommended way to do it?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
PiotrS
  • 180
  • 3
  • 16
  • You haven't posted anything related to EF, calling a stored procedure from EF or mapping a stored procedure to entities – Panagiotis Kanavos Sep 10 '20 at 11:53
  • @PanagiotisKanavos This is about creating a correct EF model, not about calling the SP, hence I haven't posted anything about calling it. We use database-first approach so the mapping to entities is done by the EF model updater, based on the actual SP in the DB. – PiotrS Sep 10 '20 at 12:00
  • And that has nothing to do with the contents of the stored procedure. Only the *resulting columns* and the entity properties. Is the *real* question how to reverse engineer the classes from the stored procedure results? – Panagiotis Kanavos Sep 10 '20 at 12:04
  • @PanagiotisKanavos I'd argue it has much to do with the content of the stored procedure, because depending what commands and flow you use inside the SP the EF model updater gets different results. I'm not trying to reverse engineer the classes; I'm trying to understand the EF model updater behaviour and what is the recommended way to build a complex type (being output of the SP) in the EF model, which then translates to classes. – PiotrS Sep 10 '20 at 12:12
  • I already explained that - it has nothing to do with the stored procedure code. This isn't an argument. Even back when a visual model designer existed (it doesn't any more) stored procedures had to be mapped by hand - twice. Which was one of the main reasons the visual designers were removed. That double mapping (relational to "entity" model and entity to objects) served no purpose – Panagiotis Kanavos Sep 10 '20 at 14:29
  • 1
    All ORMs do one thing - Map Object entities to Relational constructs. On the object side are entities, not tables. Because the code deals with *entities*, not tables. Never mind that eg ViewModels have little to do with the tables used to fill them. What's available now is reverse-engineering the database into classes. You'd have to add Erik Jenssen's [EF Core Power Tools](https://marketplace.visualstudio.com/items?itemName=ErikEJ.EFCorePowerTools) or Michael Sawczyn's [EF Visual Editor](https://marketplace.visualstudio.com/items?itemName=michaelsawczyn.EFDesigner) to get a visual designer – Panagiotis Kanavos Sep 10 '20 at 14:31
  • As for the logic the tools use - it's yours to customize. By default they'll map the result sets they can find. You can customize the code generation scripts to generate any shape you want though. BTW you can reverse-engineer a database with EF and EF Core's own tools from the command line. No tool will treat a *stored procedure* as a table though, they're typically treated as "actions" to call on an object – Panagiotis Kanavos Sep 10 '20 at 14:32
  • As for your actual question - the docs answer it already. Don't use `SET FMTONLY`. Use the other stored procedures. It's quite possible the old model updater was upgraded to use those stored procedures before it was discontinued. Perhaps the SQL Server versions it targeted back then didn't have this feature. If the [modern EF Core reverse engineering tools](https://learn.microsoft.com/en-us/ef/core/managing-schemas/scaffolding?tabs=dotnet-core-cli) use it, it's probably a bug – Panagiotis Kanavos Sep 10 '20 at 14:36
  • I checked the [source on Github](https://github.com/dotnet/efcore/blob/release/5.0-rc2/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerDatabaseModelFactory.cs) and there's no mention of `SET FMTONLY` at all. The database model factory uses the schema tables to find schema information. EF Core added support for [reverse engineering of views](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.x/#reverse-engineering-of-database-views) in EF Core 3 – Panagiotis Kanavos Sep 10 '20 at 14:46
  • @PanagiotisKanavos I'm not using EF Core, I'm using EF 6. Also I'm using standard built in tools in Visual Studio: right click in model diagram -> Update model from database -> Add stored procedure; while doing this I monitor what it does using SQL profiler and I can see it behaves exactly like with `SET FMTONLY ON`. So perhaps I should just use completely different way to generate the class that stores the output, i.e. don't rely on this built in tool? – PiotrS Sep 10 '20 at 15:28

0 Answers0