質問

I'm working on a SQLFormat-function, that work like the normal String.Format.

And now I'm stuck on being reliant on which index the current Format is in.

For example, we have this SQL-query (Psuedo, not actual usage).

SELECT *
FROM users
WHERE userID = {0:SQL;userID}
AND 10 != {1:SQL;strangeID}
AND 100 = {0:SQL;userID}

This is being formatted like this right now:

SELECT *
FROM users
WHERE userID = @p0 /* userID */
AND 10 != @p1 /* strangeID */
AND 100 = @p2 /* userID */

Where it outputs @p2, I want it to reuse @p0 (since I've already added that parameter)

Here's my class that does the formatting. Currently I'm using an static int for the indexing

I haven't found out if I can get the current parameter-index).

One way would be to skip the formatting, but I need this, so I can skip replacing the tokens myself. (I think that String.Format is faster than code I could produce :P)

UPDATED: I have updated the code with my current code, I prepare all the tokens on beforehand and push in the parameter-names as values instead of the values.

public class SqlFormat : IFormatProvider, ICustomFormatter
{
    public object GetFormat(Type formatType)
    {
        if (formatType == typeof(ICustomFormatter))
            return this;
        return null;
    }

    internal int IndexCounter = 0;
    Dictionary<string, int> dict = new Dictionary<string, int>();

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        if (!this.Equals(formatProvider))
            return null;
        if (string.IsNullOrWhiteSpace(format))
            format = "SQL;field";

        var formats = format.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
        format = formats[0];

        if (format == "SQL")
        {
            string ind = IndexCounter.ToString();
            if (dict.ContainsKey(formats[1])) ind = dict[formats[1]].ToString();
            else dict.Add(formats[1], IndexCounter++);
            var pName = arg;
            return pName + (formats.Length > 1 ? " /* " + formats[1] + " */" : "");
        }
        else
        {
            return HandleOtherFormats(format, arg);
        }
    }

    public string HandleOtherFormats(string format, object arg)
    {
        return string.Format(format, arg);
    }
}

Example usage:

var sql = @"SELECT {0:SQL;userID}, {0:SQL;userID}";

var retSql = String.Format(new SqlFormat(), sql, new[] { 650 });
Console.WriteLine(retSql);

This returns SELECT @p0 /* userID */, @p1 /* userID */

instead of

SELECT @p0 /* userID */, @p0 /* userID */

Is there some way that I can get the current index? 0 in this case.

役に立ちましたか?

解決

Looking at your example and running it here was an interesting lesson about stuff I hadn't seen before, so I may be completely off..

the 0 in the {0: looks to my like the index of the placeholder to insert. Your format always has only one value, so it will always use and need only a 0.

The internal (not static!) int is obviously nonsense. And the idea that you want to get at the current index seems wrong, too.

To me the solution would be that you either actually do provide two values, the number and the name not just the name. Can't help you with the syntax. Or, and that would be better I think, that you deduct the number from the name.

You could build a dictionary and pull the number from it, whenever you meet a name that is already in the dictionary. The other names get added with an incremented number.

Here is a quick one; seems to work..:

    public class SqlFormat : IFormatProvider, ICustomFormatter
    {
        public object GetFormat(Type formatType)
        {
            if (formatType == typeof(ICustomFormatter))
                return this;
            return null;
        }

        internal int IndexCounter = 0;
        Dictionary<string, int> dict = new Dictionary<string, int>();

        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            if (!this.Equals(formatProvider))
                return null;

            if (string.IsNullOrEmpty(format))
                format = "SQL";

            var formats = format.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            format = formats[0];

            if (format == "SQL")
            {

                string ind = IndexCounter.ToString();
                if (dict.ContainsKey(formats[1])) ind = dict[formats[1]].ToString();
                else dict.Add(formats[1], IndexCounter++);
                var pName = "@p" + ind;

                return pName + (formats.Length > 1 ? " /* " + formats[1] + " */" : "");
            }
            else
            {
                return HandleOtherFormats(format, arg);
            }
        }

        public string HandleOtherFormats(string format, object arg)
        {
            return string.Format(format, arg);
        }

他のヒント

First, you shan't use string.IsNullOrEmpty, replace it with string.IsNullOrWhiteSpace, as a string containing spaces will not be considered as empty.

Second, you shan't use parameters the way you do, it is a security concern for which SQL injection might occur. Add parameters object to your command instead.

Third, using parameter objects, you will be able to reuse same parameter twice, as long as you make use of named-parameters.

using (var cnx = new SqlConnection(connectionString)) {
    cnx.Open();

    var cmd = cnx.CreateCommand();
    cmd.CommandText = sql; // sql is actually your string containing named-parameters
    var param1 = cmd.CreateParameter();
    param1.DbType = DbType.Int32;
    param1.ParameterDirection = ParameterDirection.Input;
    param1.Name = "@p0";
    param1.Value = value;

    cmd.ExecuteQuery(); // Make sure to use proper method call here.
}

DISCLAIMER

Code sample not compiled out of the top of my head. Provided as-is for example purpose only.

This is standard behavior with SQL libraries in general. The library doesn't know that you'll always use the same value for the 1st and 3rd parameters.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top