|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionI am a huge fan of LINQ to SQL feature of the .NET Framework 3.5. If you don't know what LINQ to SQL is, please read the white paper here. I like the way in which it makes database coding simple and easy. Developers do not have to use different programming models (CLR functions and SQL Stored Procedures) and switch between different programming languages (C#/VB.NET and T-SQL). LINQ to SQL designer in Visual Studio 2008 also makes our life even easier - we can just drag and drop the tables/Views/Store Procedures to the LINQ to SQL designer surface. The designer will automatically generate the .NET classes to represent these database entities even including the relationships between the tables. ProblemMy project includes several layers, some layers require the business objects to be serialized before sending to another layer. Right now, I am using strong-typed datasets to present more than two hundred tables in the database, but I still had to manually create nearly 100 serializable business objects in order to represent the entities. LINQ to SQL has given me the hope to minimize the effort to create these business object classes. Those entity classes created by LINQ to SQL designer look like a very good candidate for the business objects that my project needs. But very quickly, I realized that I was wrong.
I am going to use the Northwind database and a First, let's create a Create a [WebMethod]
public Product
The execution of this System.InvalidOperationException: There was an error generating the XML document. --->
System.InvalidOperationException:
A circular reference was detected while serializing an
object of type Product.
at System.Xml.Serialization.XmlSerializationWriter.WriteStartElement
(String name, String ns,
Object o, Boolean writePrefixed, XmlSerializerNamespaces xmlns)
at Microsoft.Xml.Serialization.GeneratedAssembly.
XmlSerializationWriter1.Write3_Product
(String n, String ns, Product o, Boolean isNullable, Boolean needType)
at Microsoft.Xml.Serialization.GeneratedAssembly.
XmlSerializationWriter1.Write2_Supplier
(String n, String ns, Supplier o, Boolean isNullable, Boolean needType)
at Microsoft.Xml.Serialization.GeneratedAssembly.
XmlSerializationWriter1.Write3_Product
(String n, String ns, Product o, Boolean isNullable, Boolean needType)
at Microsoft.Xml.Serialization.GeneratedAssembly.
XmlSerializationWriter1.Write4_Product
(Object o)
at Microsoft.Xml.Serialization.GeneratedAssembly.ProductSerializer.Serialize
(Object objectToSerialize, XmlSerializationWriter writer)
at System.Xml.Serialization.XmlSerializer.Serialize
(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces,
String encodingStyle, String id)
--- End of inner exception stack trace ---
at System.Xml.Serialization.XmlSerializer.Serialize
(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces,
String encodingStyle, String id)
at System.Xml.Serialization.XmlSerializer.Serialize(TextWriter textWriter, Object o)
at System.Web.Services.Protocols.XmlReturnWriter.Write
(HttpResponse response, Stream outputStream, Object returnValue)
at System.Web.Services.Protocols.HttpServerProtocol.WriteReturns
(Object[] returnValues, Stream outputStream)
at System.Web.Services.Protocols.WebServiceHandler.WriteReturns(Object[] returnValues)
at System.Web.Services.Protocols.WebServiceHandler.Invoke()
The reason behind this exception is that the There are two workarounds, please read Rick Strahl's excellent Web log here. The first workaround is to change the association to internal in order to make the XML serialization work but somehow compromise the LINQ to SQL functionality. The second one is to use WCF serialization by setting What I need for my project are:
SolutionSo here is my solution - LinqSqlSerialization.dll class library. It contains two classes: public class SerializableEntity
How to Use LinqSqlSerialization ClasslibraryWhat you need to do is simply adding the LinqSqlSerialization.dll to your project references. The following are some samples to show you how easy it is to serialize and deserialize the LINQ to SQL classes in your project. XML Serialization Sample///
/// Single Object Serialization
///
NorthWindDataContext db = new NorthWindDataContext();
Product product = db.Products.Single
Binary Serialization Sample///
/// Single Object Binary Serialization
///
NorthWindDataContext db = new NorthWindDataContext();
Product product = db.Products.Single />(prod => prod.ProductID == 2);
//convert to SerializableEntity
SerializableEntity<Product>
Web Service SampleAdd the [WebMethod]
public SerializableEntity
On the client side, when the client generated the proxy classes from WSDL, these proxy classes have the same name as the entity classes generated by LINQ to SQL designer, and also have the same properties except those association properties. You can use the proxy classes like this: localhost.Service service= new localhost.Service();
localhost.Product product = service.GetProduct();
Console.WriteLine(product.ProductID); //output is 2
How It Works???The core of my solution is [Serializable] //the attribute is required by the binary serialization
[XmlSchemaProvider("MySchema")] //use customized schema for XML serialization
public class SerializableEntity
Generics is my favorite feature of .NET Framework. We can use Generics to define a set of actions or behaviors for certain types. In this case, I implemented a Generic The //One default constructor, which is required by the XML and Binary serialization.
public SerializableEntity() { }
private T _entity;
//One parameterized constructor and one public property (Entity)
//are used for passing and reading value to and from the SerializableEntity class
public SerializableEntity(T entity)
{
this.Entity = entity;
}
public T Entity
{
get { return _entity; }
set { _entity = value; }
}
Use Reflection to read and write the values from and to LINQ to SQL entity objects for both binary and XML serializations. //
//Binary Serialization.
//Implementation for ISerializable interface
//
#region ISerializable Members
//this constructor is required for deserialization
public SerializableEntity(SerializationInfo info, StreamingContext context)
{
_entity = new T();
PropertyInfo[] properties = _entity.GetType().GetProperties();
SerializationInfoEnumerator enumerator = info.GetEnumerator();
while (enumerator.MoveNext())
{
SerializationEntry se = enumerator.Current;
foreach (PropertyInfo pi in properties)
{
if (pi.Name == se.Name) {
pi.SetValue(_entity, info.GetValue(se.Name, pi.PropertyType), null);
}
}
}
}
//this method is the implementation of ISerializable.GetObjectData member
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
PropertyInfo[] infos = _entity.GetType().GetProperties();
foreach (PropertyInfo pi in infos)
{
bool isAssociation = false;
foreach (object obj in pi.GetCustomAttributes(true))
{
if (obj.GetType() == typeof(System.Data.Linq.Mapping.AssociationAttribute))
{ isAssociation = true; break; }
}
if (!isAssociation) {
if (pi.GetValue(_entity, null) != null) {
info.AddValue(pi.Name, pi.GetValue(_entity, null));
}
}
}
}
#endregion
//
//XML Serialization.
//Implementation for IXmlSerializable interface
//
#region IXmlSerializable Members
//this method is the implementation of IXmlSerializable.GetSchema member.
//It always returns null.
public System.Xml.Schema.XmlSchema GetSchema() { return null; }
//this method is the implementation of IXmlSerializable.ReadXml member
public void ReadXml(System.Xml.XmlReader reader)
{
_entity = new T();
PropertyInfo[] pinfos = _entity.GetType().GetProperties();
if (reader.LocalName == typeof(T).Name)
{
reader.MoveToContent();
string inn = reader.ReadOuterXml();
System.IO.StringReader sr=new System.IO.StringReader(inn);
System.Xml.XmlTextReader tr = new XmlTextReader(sr);
tr.Read();
while (tr.Read())
{
string elementName = tr.LocalName;
string value = tr.ReadString();
foreach (PropertyInfo pi in pinfos)
{
if (pi.Name == elementName)
{
TypeConverter tc = TypeDescriptor.GetConverter(pi.PropertyType);
pi.SetValue(_entity, tc.ConvertFromString(value), null);
}
}
}
}
}
//this method is the implementation of IXmlSerializable.WriteXml member
public void WriteXml(System.Xml.XmlWriter writer)
{
PropertyInfo[] pinfos = _entity.GetType().GetProperties();
foreach (PropertyInfo pi in pinfos)
{
bool isAssociation = false;
foreach (object obj in pi.GetCustomAttributes(true))
{
if (obj.GetType() == typeof(System.Data.Linq.Mapping.AssociationAttribute))
{
isAssociation = true;
break;
}
}
if (!isAssociation)
{
if (pi.GetValue(_entity, null) != null)
{
writer.WriteStartElement(pi.Name);
writer.WriteValue(pi.GetValue(_entity, null));
writer.WriteEndElement();
}
}
}
}
//return xsd type
private static string GetXsdType(string nativeType)
{
string[] xsdTypes = new string[]{"boolean", "unsignedByte",
"dateTime", "decimal", "Double",
"short", "int", "long", "Byte", "Float", "string", "unsignedShort",
"unsignedInt", "unsignedLong", "anyURI"};
string[] nativeTypes = new string[]{"System.Boolean", "System.Byte",
"System.DateTime", "System.Decimal",
"System.Double", "System.Int16", "System.Int32", "System.Int64",
"System.SByte", "System.Single", "System.String", "System.UInt16",
"System.UInt32", "System.UInt64", "System.Uri"};
for (int i = 0; i < nativeTypes.Length; i++)
{
if (nativeType == nativeTypes[i]) { return xsdTypes[i]; }
}
return "";
}
#endregion
Because I am using a single private static readonly string ns = "http://tempuri.org/";
// This is the method named by the XmlSchemaProviderAttribute applied to the type.
public static XmlQualifiedName MySchema(XmlSchemaSet xs)
{
// This method is called by the framework to get the schema for this type.
System.Text.StringBuilder sb = new System.Text.StringBuilder();
System.IO.StringWriter sw = new System.IO.StringWriter(sb);
XmlTextWriter xw = new XmlTextWriter(sw);
// generate the schema for type T
xw.WriteStartDocument();
xw.WriteStartElement("schema");
xw.WriteAttributeString("targetNamespace", ns);
xw.WriteAttributeString("xmlns", "http://www.w3.org/2001/XMLSchema");
xw.WriteStartElement("complexType");
xw.WriteAttributeString("name", typeof(T).Name);
xw.WriteStartElement("sequence");
PropertyInfo[] infos = typeof(T).GetProperties();
foreach (PropertyInfo pi in infos)
{
bool isAssociation = false;
foreach (object a in pi.GetCustomAttributes(true))
{
//check whether the property is an Association
if (a.GetType() == typeof(System.Data.Linq.Mapping.AssociationAttribute))
{
isAssociation = true;
break;
}
}
//only use the property which is not an Association
if (!isAssociation)
{
xw.WriteStartElement("element");
xw.WriteAttributeString("name", pi.Name);
if (pi.PropertyType.IsGenericType) {
Type[] types = pi.PropertyType.GetGenericArguments();
xw.WriteAttributeString("type", "" + GetXsdType(types[0].FullName));
} else {
xw.WriteAttributeString("type", "" +
GetXsdType(pi.PropertyType.FullName));
} xw.WriteEndElement();
}
}
xw.WriteEndElement();
xw.WriteEndElement();
xw.WriteEndElement();
xw.WriteEndDocument();
xw.Close();
XmlSerializer schemaSerializer = new XmlSerializer(typeof(XmlSchema));
System.IO.StringReader sr = new System.IO.StringReader(sb.ToString());
XmlSchema s = (XmlSchema)schemaSerializer.Deserialize(sr);
xs.XmlResolver = new XmlUrlResolver();
xs.Add(s);
return new XmlQualifiedName(typeof(T).Name, ns);
}
The NorthWindDataContext db = new NorthWindDataContext();
var products = from p in db.Products
Select p;
Because most of the query results are implemented by the public static class EnumerableExtension
{
public static List
I can use it in the LINQ to SQL query to return a typed list of serializable entities directly. It makes the code compact and easy-to-understand. Now with this History
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||