ODP.NET UPDATE… RETURNING INTO… multiple rows, Parameter type

怎甘沉沦 提交于 2019-12-10 11:57:20

问题


i was wondering if the following scheme of action is possible:

i am implementing a simple data entry application. each editable record resides in a table. the user has to edit multiple records at the same time (to reduce data entry time). what i try to do at the moment is to implement some sort of locking mechanism in the records.

the first thing i thought of was to execute (ExecuteNonQuery) an UPDATE some_table SET status = 'locked' WHERE rownum = 1 RETURNING id INTO :locked_id with an output parameter, giving me the ID of the locked record. i may execute another SELECT statment to read whatever other information i want, basing on this ID.

while the above approach seems to work for a single locked record, i don't really know how to do this for multiple returned rows. - what if, in the above example, the WHERE clause was "rownum < 4" instead of "rownum = 1"?

what my OracleParameter should be like? when i specify my return oracle parameter like this (works with a single row),

OracleParameter p = cmd.Parameters.Add("ID", OracleDbType.Int32, 10, 0, ParameterDirection.Output);

ORA-24369 is given. i tried using ArrayBindCount and arrays as .Value property of the parameter, but to no avail.

also, do you find anything fundamentally wrong with the whole particular manner of locking?

thank you

edit: some clarifications - 1. there is a column called user_name, which can hold e.g. the name of the logged in user - i update it along with the locking UPDATE 2. there is another column that i call locked_timestamp, which holds the time when the record got locked. there could be a background process that would run during the night and would reset the locked records back to "unlocked" - given that this will not happen frequently, and the proportion of records that stay "locked" because of crashes etc. is small in comparison to the volume of the edited records 3. the concurrent updates are supposed to be handled automatically by Oracle, and yes, I use transactions during each UPDATE - so there is little chance that a record gets locked by two users simultaneously - or that's what I hear (will try this out, once i find how to UPDATE...RETURNING.. multiple rows)


回答1:


finally, after hours of searching and playing with code, i came to the following conclusions (apart from the headache):

i got what i wanted using combination from

  1. a hint here, which suggested to wrap the UPDATE..RETURNING statement into an anonymous PL/SQL block (start with BEGIN and end with END;) - this went without explanation and I still don't know exactly why the behaviour is different
  2. code snippet in Oracle documentation about OracleCommand, specifically the part about binding PL/SQL associative arrays with BULK COLLECT INTO (couldn't get the simple array binding to work..):

try
{
    conn.Open();
    transaction = conn.BeginTransaction();

    cmd = new OracleCommand();
    cmd.Connection = GetConnection();

    cmd.CommandText =
        "BEGIN UPDATE some_table " +
        "SET status = 'locked', " +
        "    locked_tstamp = SYSDATE, " +
        "    user_name = '" + user + "' " +
        "WHERE rownum <= 4 " +
        "RETURNING id BULK COLLECT INTO :id; END;";

    cmd.CommandType = CommandType.Text;

    cmd.BindByName = true;
    cmd.ArrayBindCount = 4;

    p = new OracleParameter();
    p.ParameterName = "id";
    p.Direction = ParameterDirection.Output;
    p.OracleDbType = OracleDbType.Int64;
    p.Size = 4;
    p.ArrayBindSize = new int[] { 10, 10, 10, 10 };
    p.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
    cmd.Parameters.Add(p);

    int nRowsAffected = cmd.ExecuteNonQuery();

    // nRowsAffected is always -1 here
    // we can check the number of "locked" rows only by counting elements in p.Value (which is returned as OracleDecimal[] here)
    // note that the code also works if less than 4 rows are updated, with the exception of 0 rows
    // in which case an exception is thrown - see below
    ...
}
catch (Exception ex)
{
    if (ex is OracleException && !String.IsNullOrEmpty(ex.Message) && ex.Message.Contains("ORA-22054")) // precision underflow (wth)..
    {
        Logger.Log.Info("0 rows fetched");
        transaction.Rollback();
    }
    else
    {
        Logger.Log.Error("Something went wrong during Get : " + ex.Message);
        ret = null;
        transaction.Rollback();
    }
}
finally
{
    // do disposals here
}
...




回答2:


Here's a few things to consider regarding this record locking scheme:

  1. How do you know who's got the record locked? I suspect that at some point you'll want to know.
  2. If a session crashes, how do records get unlocked? Surely they're not going to stay locked until the end of time?!?
  3. How are simultaneous lock attempts handled? Let's say I start a transaction to lock record #12345. My app sees that that status is "unlocked" (or NULL, or whatever), and immediately does an UPDATE and COMMIT to "lock" the record. Meanwhile, Susie in the cube next door does the same thing - reads the database, see's it's unlocked, and does the same UPDATE and COMMIT. OK now - 1) what's the status of the record, 2) who REALLY has it locked, and 3) whose subsequent changes are going to be overwritten - and I'm telling you, it'd BETTER be Susie or I'm comin' down there to TALK REALLY LOUDLY ABOUT THIS!!!

Just a few points to ponder.

Share and enjoy.



来源:https://stackoverflow.com/questions/10124548/odp-net-update-returning-into-multiple-rows-parameter-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!