1

I am looking for some sort of tool to help with calling Oracle stored procedures from .Net code. We have a large legacy database that is frankly a bit messy (no id fields, large composite keys and duplicate data). Currently, we have to do do all data access via stored procedures through a custom library that is old and buggy and I would like to replace it.

I have some experience with ORM tools like nHibernate but after playing around with it a bit in our environment it doesn't really seem to be the best option for working with a legacy database like this.

Does anyone know of a good tool that will allow stored procedures to be called easily and map the results into sets/collections of objects? A nice bonus would be the ability to handle connections transactions as well.

Thanks

zaq
  • 2,571
  • 3
  • 31
  • 39

5 Answers5

2

The new Oracle beta Entity Framework driver lets you do that. You can map SPs into the model and either to entities (if they return the equivalent of a table) or create a "complex type" which is a class built around what the SP returns.

I don't know how many SPs your calling, but for the ones I've tried it's worked out.

Another option is to write your own library that just calls the procedures and returns the results as .net classes, but that will require a lot of setup work on your part in terms of repetitive code (mapping parameters to procedures in Oracle gets tedious real fast).

edit - Here's a config file entry to use a stored procedure where the results are coming from a cursor that's an OUT parameter.

  <oracle.dataaccess.client>
    <settings>
      <add name="ENVMSTR.P_ORG_UNIT_R_BY_STAFF.RefCursor.RESULT_CURSOR_P" value="implicitRefCursor bindinfo='mode=Output'" />
    </settings>
  </oracle.dataaccess.client>

edit 2 - And the stored procedure in question:

create or replace
PROCEDURE                 P_ORG_UNIT_R_BY_STAFF 
(
  STAFF_ID_P IN NUMBER
, RESULT_CURSOR_P OUT SYS_REFCURSOR  
) AS 
BEGIN
  OPEN RESULT_CURSOR_P FOR
    select *
      from dept_organizational_unit
      start with deptorgunit_cd = (select deptorgunit_cd from staff where staff_id = STAFF_ID_P)
      connect by prior deptorgunit_parent_cd = deptorgunit_cd;
END P_ORG_UNIT_R_BY_STAFF;
Tridus
  • 5,021
  • 1
  • 19
  • 19
  • I've actually played around with this already and had some issues getting the mapping to work. When I try to "add function import" and click "get column information" I get the error "the selected stored procedure returns no columns" But it returns a cursor and I can call it via regular ado.net. – zaq Aug 25 '11 at 01:32
  • @zaq There's some things you need to add into your web.config/app.config in order to make stored procedures work. You should read the readme for info. For one of the ones I'm using, I'll update the answer with the necessary config. – Tridus Aug 25 '11 at 11:40
  • It's still not working for me. I found some documentation on the Oracle forums that indicates each column that is returned by the cursor needs to be defined in the web.config. If that's the case, then it's every bit as tedious as mapping parameters in code. I hope they improve things in the production release. – zaq Aug 25 '11 at 17:08
  • @zaq It says that but I didn't have to do it for mine. Not really sure why. But yeah, I really wish they'd do something about needing the extra config entries for this stuff. – Tridus Aug 25 '11 at 17:30
  • Hmm, that's nice that you didn't have to do it. Any chance you can post what your stored procedure looks like? – zaq Aug 25 '11 at 17:35
  • @zaq Added it to the answer. In my case I'm doing a select * in the procedure, so I was able to tell EF that it just returns an entity that it already knew about. That might be what lets me bypass the configuration for columns situation. I was satisfied with the result so I didn't investigate it further. – Tridus Aug 25 '11 at 17:56
  • Ah, ok the select * makes sense. Thanks for all your input. I think this will be the route to go in the future but for now it's just not quite mature enough for what we are doing. I'll give you credit for the answer since I think it could be very useful for someone else. – zaq Aug 25 '11 at 18:21
  • It is true. You don't need the detailed app.config if you map the result to an entity... only a complex type needs it. – Christian Shay Dec 09 '11 at 20:53
1

IF a commercial library is an option we are really happy with Devart (see http://www.devart.com/dotconnect/oracle/features.html)... they support LINQ and PLINQ and EF and Stored Procedure, REF Cursors etc. - from Oracle 7.3 till 11g / .NET 2 and up / 32 + 64 Bit...

not affiliated, just a happy customer...

Yahia
  • 69,653
  • 9
  • 115
  • 144
  • Unfortunately right now a commercial library is not an option, though it looks excellent. – zaq Aug 25 '11 at 18:23
1

If you just want SPROC handling with parametrization and materialization (data into objects), dapper-dot-net is simple, minimalist, and should work fine on Oracle; for example:

var user = cnn.Query<User>("spGetUser", new {Id = 1}, 
    commandType: CommandType.StoredProcedure).First();

points:

  • spGetUser is the name of the sproc
  • it is invoked as a sproc via commandType
  • the parameters are deduced from the object passed in; in this case it is assumed that there is a parameter named Id that takes an integer, with the value of 1 passed in
  • a direct column-to-property map is applied, constructing a User object for each row returned
  • in this case we're also using LINQ-to-Objects to illustrate reading 1 row simply

note that mapping multiple data grids and horizontal partitioning (into different objects in a related graph) are also supported.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I think there is a special config we need for oracle as well the `:` param thingy – Sam Saffron Aug 25 '11 at 02:37
  • Looks interesting, I may look into this package. – zaq Aug 25 '11 at 18:25
  • Dapper does not support Oracle Ref Cursors out of the gate, you've been warned. See Sams answer for the details http://stackoverflow.com/questions/7390015/using-dapper-with-oracle-stored-procedures-which-return-cursors – Stephen Patten Jan 10 '12 at 17:17
0

For those of you trying to get Entity Framework function imports working with Oracle, here is a walkthrough you can use: http://www.oracle.com/webfolder/technetwork/tutorials/obe/db/dotnet/EntityFrameworkOBE/EntityFrameworkOBE.htm

There is also a magazine article that I wrote that includes a walkthough: http://www.oracle.com/technetwork/issue-archive/2011/11-sep/o51odt-453447.html

Note: At the time of this posting, these walkthoughs do not work due to some changes in the app.config keywords that occured in the beta3. See the readme file in the ODP.NET directory of the beta3.

The keywords that have been changed that affect the walkthoroughs can be modified as follows:

NATIVE_DATA_TYPE to NATIVEDATATYPE

and

PROVIDER_DB_TYPE to PROVIDERDBTYPE

See the readme file in the ODT directory for additional caveats regarding this. In the (future) production release (not available at the time of this posting) the Oracle Developer Tools for Visual Studio online help has a section called "Using Entity Framework". This section contains caveats about mapping to Stored Procedures and Functions. Please read this documentation.

For more information on the format of this app.config metadata, refer to ODP.NET online help in the “Implicit REF CURSOR Binding Support” section.

Note that this detailed app.config meta data is only needed if you are mapping with a complex type as the result. You won't need it if you return an entity.

Unfortunately, the process of modifying the app.config is fraught with the potential to make mistakes. Any mistake will result in the "Get Column Information" button on the Import Function wizard doing nothing. We are aware of this and are planning a configuration tool in a future release.

Christian Shay

Oracle

Christian Shay
  • 2,570
  • 14
  • 24
0

You'd better use a code generator for your data access layer. Writing it is quite easy. It could generate even CRUD PL/SQL.

Here you have a small example:

Private Sub GeneraCapaAccesoDato(ByVal tipo As Integer, ByVal modo As String)
    Dim sb As New StringBuilder()
    Dim Tabla As String = lbTabla.SelectedValue
    Dim dt As DataTable = RecuperarDatosTabla(Tabla)
    sb.Append(String.Format("public int {1}(OracleConnection con, BE{0} oBE{0})", Tabla, modo))
    sb.AppendLine("{")
    sb.AppendLine("int Resultado;")
    sb.AppendLine(String.Format("OracleCommand cmd = new OracleCommand(""Pa_{0}_{1}"", con);", Tabla, modo))
    sb.AppendLine("cmd.CommandType = CommandType.StoredProcedure;")
    sb.AppendLine("")
    Dim i As Integer
    Row = dt.Select()
    Dim NomTabla As String
    Dim Tamaño As Integer
    Dim scala As Integer
    Dim TipoDato As String = "Ninguno"
    Dim precision As Integer
    Dim aux As Object
    Dim aux1 As Object
    Dim llave As Integer
    For i = tipo To Row.Count() - 1
        llave = Int32.Parse(Row(i).Item(7))
        NomTabla = Row(i).Item(0).ToString()
        Tamaño = Integer.Parse(Row(i).Item(2).ToString())
        aux = Row(i).Item(4).ToString()
        scala = Integer.Parse(If(aux = "", 0, aux))

        aux1 = Row(i).Item(3).ToString()
        precision = Integer.Parse(If(aux1 = "", 0, aux1))

        If scala > 0 Then
            If scala >= 0 And scala <= 15 Then
                TipoDato = "OracleDbType.Double"
            End If
        ElseIf Row(i).Item(1).ToString() = "NUMBER" Then
            If precision < 2 Then
                TipoDato = "OracleDbType.Int16"
            ElseIf precision >= 2 And precision <= 9 Then
                TipoDato = "OracleDbType.Int32"
            ElseIf precision >= 10 And precision <= 18 Then
                TipoDato = "OracleDbType.Int64"
            End If
        Else
            If Row(i).Item(1).ToString() = "DATE" Then
                TipoDato = "OracleDbType.Date "
            End If
            If Row(i).Item(1).ToString() = "VARCHAR2" Then
                TipoDato = "OracleDbType.Varchar2  "
            End If
            If Row(i).Item(1).ToString() = "CHAR" Then
                TipoDato = "OracleDbType.Char "
            End If

        End If
        sb.AppendLine(String.Format("OracleParameter Par{1} = cmd.Parameters.Add(""P_{0}"",{2});", _
                                    NomTabla, If(tipo = 0, i + 1, i), TipoDato))

        If Row(i).Item(1).ToString() = "VARCHAR2" Or Row(i).Item(1).ToString() = "CHAR" Then
            sb.AppendLine(String.Format("Par{1}.Size = {0};", Tamaño, If(tipo = 0, i + 1, i)))
        End If

        sb.AppendLine(String.Format("Par{0}.Direction = ParameterDirection.Input;", If(tipo = 0, i + 1, i)))
        sb.AppendLine(String.Format("Par{0}.Value = oBE{2}.{1};", If(tipo = 0, i + 1, i), NomTabla, Tabla))
        sb.AppendLine("")
        TipoDato = ""

    Next
    sb.AppendLine("Resultado = cmd.ExecuteNonQuery();")
    sb.AppendLine("return Resultado;")
    sb.AppendLine("}")
    rtbVisor.Text = sb.ToString()
End Sub
Private Function RecuperarDatosTabla(ByVal NombreTabla As String) As DataTable
    sb = New StringBuilder
    sb.Append(" select COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE, NULLABLE, DATA_DEFAULT ,column_id")
    sb.Append(" from(USER_TAB_COLUMNS)")
    sb.Append(String.Format(" where TABLE_NAME = '{0}' order by table_name,column_id ", NombreTabla))
    Using con As New OracleConnection(strConexion)
        con.Open()
        dt = New DataTable
        Dim da As OracleDataAdapter = New OracleDataAdapter(sb.ToString(), con)
        da.Fill(dt)
    End Using
    Return dt
End Function
Nathan
  • 2,705
  • 23
  • 28