Любое решение или обходной путь для сериализации SQLParameter в WCF? - PullRequest
3 голосов
/ 01 апреля 2009

Первоначально я определил в своем MessageContract коллекцию SQLParameters, чтобы создать простое выполнение хранимой процедуры с помощью службы WCF. Видимо, тип SQLParameter не сериализуем, поэтому мне нужно несколько советов о том, как действовать здесь.

Возможно ли все-таки как-то использовать SQLParameter как часть моего контракта WCF, или мне нужно сделать что-то еще, например, создать собственный класс с такими же свойствами, как у SQLParameter, а затем создать SQLParameters в другом месте моего кода?

UPDATE:
Для дальнейшего понимания того, почему возникает такая ситуация, изначально клиент Windows Form подключался напрямую к базе данных, чтобы получить DataSet для целей отчетности, используя обычные объекты ADO.NET. Теперь клиент хочет, чтобы общий веб-сервис обрабатывал все отчеты. Это лучшее, что я могу придумать, чтобы справиться с этим без особых изменений.

Ответы [ 4 ]

3 голосов
/ 16 марта 2014

Я был относительно не в восторге от принятого ответа:

Возможно, вы захотите еще раз провести рефакторинг, чтобы уменьшить число или повысить уровень абстракции. Но если нет, тогда вы должны сделать эквивалент извлечения всех этих методов в один или несколько интерфейсов. Эти интерфейсы станут ServiceContracts для вашей службы WCF. Переместите методы в новые сервисы для реализации этих сервисных контрактов, и вы почти закончили.

По сути, это правильный ответ для простой предопределенной бизнес-логики; однако, с различными уровнями абстракции, скажем, службой, которая требуется для выполнения ad-hoc sql запросов, нельзя просто обеспечить этот уровень гибкости с помощью предварительно определенных вызовов службы.

Чтобы запросы ad-hoc работали в среде службы WCF, необходимо передать параметры, чтобы защитить систему и предотвратить различные векторы атак в стиле SQL-инъекций.

На всякий случай я создал сервис, который в качестве бизнес-требования должен абстрагировать слой данных от клиента и позволить сторонним организациям взаимодействовать с рядом баз данных в разрозненных системах БД.

Для этой системы я использовал описанный выше подход Крейга Х и создал класс SerializableSqlParam для передачи в качестве объекта списка в мою службу.

Преимущество моего SerializableSqlParam класса заключается в следующем:

  1. Прямая сериализация и приведение типов класса SqlParameter.
  2. Сериализованные объекты хранятся в строковом формате UTF-16, что позволяет SQL-серверу сохранять объекты .
  3. Правильное использование AssemblyQualifiedName для десериализации объектов, не находящихся в непосредственной сборке.
  4. Завершить сортировку параметров класса SqlParameter.

Общее использование выглядит следующим образом:

SerializedSqlParam sp = new SerializedSqlParam(new SqlParameter("@id", 1));

//or through typecasting:

SqlParameter parameter = new SqlParameter("@id", 1);
SerializedSqlParam sp = (SerializedSqlParam) parameter;

Для десериализации просто сделайте следующее:

SqlParameter parameter = sp.GetSqlParameter();

//or through typecasting

SqlParameter parameter = (SqlParameter) sp;

Вот мой класс. Я уверен, что есть вещи, которые можно исправить / улучшить; однако, это просто чтобы донести концепцию. Надеемся, что другие читатели найдут это полезным!

SerializedSqlParam.cs

[DataContract]
public class SerializedSqlParam
{
    [Browsable(false)]
    [DataMember]
    public string CompareInfo { get; set; } 

    [RefreshProperties(RefreshProperties.All)]
    [DataMember]
    public string Direction { get; set; }

    [DataMember]
    public bool IsNullable { get; set; }

    [Browsable(false)]
    [DataMember]
    public int LocaleId { get; set; }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    [DataMember]
    public int Offset { get; set; }

    [DataMember]
    public string ParameterName { get; set; }

    [DefaultValue(0)]
    [DataMember]
    public byte Precision { get; set; }

    [DefaultValue(0)]
    [DataMember]
    public byte Scale { get; set; }

    [DataMember]
    public int Size { get; set; }

    [DataMember]
    public string SourceColumn { get; set; }

    [DataMember]
    public bool SourceColumnNullMapping { get; set; }

    [DataMember]
    public string SourceVersion { get; set; }

    [DataMember]
    public string SqlDbType { get; set; }

    [DataMember]
    public string TypeName { get; set; }

    [DataMember]
    public string UdtTypeName { get; set; }

    [DataMember]
    public string Value { get; set; }

    [DataMember]
    public string ValueType { get; protected set; }

    [DataMember]
    public string XmlSchemaCollectionDatabase { get; set; }
    [DataMember]
    public string XmlSchemaCollectionName { get; set; }
    [DataMember]
    public string XmlSchemaCollectionOwningSchema { get; set; }

    public SerializedSqlParam(SqlParameter p)
    {
        this.CopyProperties(p);
        this.SerializeParameterValue(p);
    }

    public static explicit operator SerializedSqlParam(SqlParameter p)
    {
        return new SerializedSqlParam(p);
    }

    public static explicit operator SqlParameter(SerializedSqlParam p)
    {
        return p.GetSqlParameter(p);
    }

    public SqlParameter GetSqlParameter()
    {
        return this.GetSqlParameter(this);
    }

    public SqlParameter GetSqlParameter(SerializedSqlParam serialized)
    {
        SqlParameter p = new SqlParameter();

        p.ParameterName = serialized.ParameterName;
        p.Precision = serialized.Precision;
        p.Scale = serialized.Scale;
        p.Size = serialized.Size;
        p.IsNullable = serialized.IsNullable;
        p.LocaleId = serialized.LocaleId;
        p.Offset = serialized.Offset;
        p.SourceColumn = serialized.SourceColumn;
        p.SourceColumnNullMapping = serialized.SourceColumnNullMapping;

        p.XmlSchemaCollectionDatabase = serialized.XmlSchemaCollectionDatabase;
        p.XmlSchemaCollectionName = serialized.XmlSchemaCollectionName;
        p.XmlSchemaCollectionOwningSchema = serialized.XmlSchemaCollectionOwningSchema;

        p.TypeName = serialized.TypeName;
        p.UdtTypeName = serialized.UdtTypeName;

        p.Direction = (ParameterDirection)Enum.Parse(typeof(ParameterDirection), serialized.Direction);
        p.CompareInfo = (SqlCompareOptions)Enum.Parse(typeof(SqlCompareOptions), serialized.CompareInfo);
        p.SourceVersion = (DataRowVersion)Enum.Parse(typeof(DataRowVersion), serialized.SourceVersion);

        p.Value = this.DeserializeObject(serialized.Value, Type.GetType(serialized.ValueType));

        return p;
    }

    private void SerializeParameterValue(SqlParameter p)
    {
        if (p.Value.GetType().IsSerializable)
        {
            this.ValueType = this.GetTypeAssemblyQualifiedName(p.Value);
            this.Value = this.SerializeObject(p.Value);
        }
        else
        {
            throw new SerializationException("Cannot serialize the parameter value object. Recast that object into a primitive or class that can be serialized.");
        }
    }

    private void CopyProperties(SqlParameter p)
    {
        this.ParameterName = p.ParameterName;
        this.Precision = p.Precision;
        this.Scale = p.Scale;
        this.Size = p.Size;
        this.IsNullable = p.IsNullable;
        this.LocaleId = p.LocaleId;
        this.Offset = p.Offset;
        this.SourceColumn = p.SourceColumn;
        this.SourceColumnNullMapping = p.SourceColumnNullMapping;

        this.XmlSchemaCollectionDatabase = p.XmlSchemaCollectionDatabase;
        this.XmlSchemaCollectionName = p.XmlSchemaCollectionName;
        this.XmlSchemaCollectionOwningSchema = p.XmlSchemaCollectionOwningSchema;

        this.TypeName = p.TypeName;
        this.UdtTypeName = p.UdtTypeName;

        this.Direction = p.Direction.ToString();
        this.CompareInfo = p.CompareInfo.ToString();
        this.SourceVersion = p.SourceVersion.ToString();

        try
        {
            this.SqlDbType = p.SqlDbType.ToString();
        }
        catch
        {
            this.SqlDbType = null;
        }
    }

    private string SerializeObject(object value)
    {
        if (value == null) return null;

        XmlSerializer serializer = new XmlSerializer(value.GetType());
        XmlWriterSettings settings = new XmlWriterSettings();

        settings.Encoding = new UnicodeEncoding(false, false);
        settings.Indent = false;
        settings.OmitXmlDeclaration = false;

        using (StringWriter textWriter = new StringWriter())
        {
            using (XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings))
            {
                serializer.Serialize(xmlWriter, value);
            }
            return textWriter.ToString();
        }
    }

    private object DeserializeObject(string xml, Type type)
    {
        if (string.IsNullOrEmpty(xml)) return null;

        XmlSerializer serializer = new XmlSerializer(type);

        XmlReaderSettings settings = new XmlReaderSettings();
        using (StringReader textReader = new StringReader(xml))
        {
            using (XmlReader xmlReader = XmlReader.Create(textReader, settings))
            {
                return Convert.ChangeType(serializer.Deserialize(xmlReader), type);
            }
        }
    }

    private string GetTypeAssemblyQualifiedName(object obj)
    {
        return obj.GetType().AssemblyQualifiedName.ToString();
    }
}
0 голосов
/ 02 апреля 2009

Звучит так, будто вы слишком много пытаетесь выбрать легкий путь. Я бы реорганизовал те методы, которые использовались для прямого доступа к базе данных, в первую очередь с помощью рефакторинга «Extract Method». Это оставит вас с (большим) количеством маленьких методов, каждый из которых принимает набор параметров и возвращает DataSet, и каждый с определенной целью.

Возможно, вы захотите еще раз провести рефакторинг, чтобы уменьшить количество или повысить уровень абстракции. Но если нет, тогда вы должны сделать эквивалент извлечения всех этих методов в один или несколько интерфейсов. Эти интерфейсы станут ServiceContracts для вашей службы WCF. Переместите методы в новые сервисы для реализации этих сервисных контрактов, и вы почти закончили.

Это, конечно, лучше работает с автоматизированными модульными тестами и хорошим охватом кода. Это обеспечит уровень уверенности, необходимый для того, чтобы сделать что-то столь радикальное.

0 голосов
/ 08 сентября 2011

Я только что создал простую оболочку сериализации для sqlparameter

#region

using System;
using System.Data;
using System.Data.SqlClient;
using System.Xml.Serialization;

#endregion

[Serializable]
public class SQLParamSerializationWrapper
{
    #region Constants and Fields

    private SqlParameter param;

    #endregion

    #region Constructors and Destructors

    public SQLParamSerializationWrapper()
    {
        //paramless constructor for serialization
        this.param = new SqlParameter();
    }

    public SQLParamSerializationWrapper(SqlParameter param)
    {
        this.SQLParam = param;
    }

    #endregion

    #region Properties

    public DbType DbType
    {
        get
        {
            return this.SQLParam.DbType;
        }
        set
        {
            this.SQLParam.DbType = value;
        }
    }

    public ParameterDirection Direction
    {
        get
        {
            return this.SQLParam.Direction;
        }
        set
        {
            this.SQLParam.Direction = value;
        }
    }

    public string ParameterName
    {
        get
        {
            return this.SQLParam.ParameterName;
        }
        set
        {
            this.SQLParam.ParameterName = value;
        }
    }

    [XmlIgnore]
    public SqlParameter SQLParam
    {
        get
        {
            return this.param;
        }
        set
        {
            this.param = value;
        }
    }

    public int Size
    {
        get
        {
            return this.SQLParam.Size;
        }
        set
        {
            this.SQLParam.Size = value;
        }
    }

    public object Value
    {
        get
        {
            return this.SQLParam.Value;
        }
        set
        {
            this.SQLParam.Value = value;
        }
    }

    #endregion
}

Затем вы можете использовать его следующим образом

Сериализация (я использую список параметров): -

List<SQLParamSerializationWrapper> procParams = new List<SQLParamSerializationWrapper>();
            SqlParameter startdate = new SqlParameter("dateStart", new DateTime(2011, 9, 5));
            SqlParameter enddate = new SqlParameter("dateEnd", new DateTime(2011, 9, 6));

            SQLParamSerializationWrapper startDateWrapper = new SQLParamSerializationWrapper(startdate);
            SQLParamSerializationWrapper endDateWrapper = new SQLParamSerializationWrapper(enddate);

            procParams.Add(startDateWrapper);
            procParams.Add(endDateWrapper);

            string paramsAsXML = "";

            using (var sw = new StringWriter())
            {
                using (var xw = XmlWriter.Create(sw))
                {
                    XmlSerializer xs = new XmlSerializer(procParams.GetType());
                    xs.Serialize(xw, procParams);
                }
                paramsAsXML = sw.ToString();
            }

десериализация: -

var procParams = new List<SqlParameter>();

StringReader sr = new StringReader(parm.Value);
                        // Create an instance of the XmlSerializer specifying type.
                        XmlSerializer deserializer = new XmlSerializer(typeof(List<SQLParamSerializationWrapper>));

                        List<SQLParamSerializationWrapper> sqlParamWrapper = (List<SQLParamSerializationWrapper>)deserializer.Deserialize(sr);

                        foreach (var param in sqlParamWrapper)
                        {
                            procParams.Add(param.SQLParam);
                        }
0 голосов
/ 01 апреля 2009

Во-первых, если вы хотите получить доступ к базе данных через WCF, тогда ADO.NET Data Services - гораздо лучший вариант.

Но нет; вы не можете сериализовать SqlParameter через WCF; вам нужно будет заключить его в другое представление. Обратите внимание, что IMO довольно опасно выставлять логику вашей базы данных так близко к границе WCF - я бы просто использовал методы WCF, которые абстрагируют это - то есть

[OperationContract]
Customer[] FindCustomers(string id, string name, string location, ...);

Тогда у вас есть строго контролируемый сервисный интерфейс.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...