sroes
sroes

Reputation: 15053

WCF serialization problems in combination with LINQ to SQL single-table inheritance

In a project I'm using LINQ to SQL and WCF.

I have the following single-table inheritance:

Single-table inheritance

When I call a WCF method (GetMediaForItem) which return's a list of ItemMedia (the base type) objects, I get the exception A first chance exception of type 'System.Runtime.Serialization.SerializationException' occurred in System.Runtime.Serialization.dll.

But when I call a WCF method (GetYouTubeVideosForItem) which returns a list of YouTubeVideo's (one of the derived types), it works fine.

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MediaService : IMediaService
{
    ...

    // this throws a System.Runtime.Serialization.SerializationException
    public List<ItemMedia> GetMediaForItem(int itemId)
    {
        using (var context = _db.CreateContext())
        {
            context.DeferredLoadingEnabled = false;

            return (from i in context.ItemMedias
                    where i.ItemID == itemId
                    orderby i.Order
                    select i).ToList();
        }
    }

    // this works fine
    public List<YouTubeVideo> GetYouTubeVideosForItem(int itemId)
    {
        using (var context = _db.CreateContext())
        {
            context.DeferredLoadingEnabled = false;

            return (from i in context.ItemMedias.OfType<YouTubeVideo>()
                    where i.ItemID == itemId
                    orderby i.Order
                    select i).ToList();
        }
    }
}

The service interface:

[ServiceContract]
public interface IMediaService
{
    [OperationContract]
    [WebInvoke(
        Method = "GET",
        UriTemplate = "Media?itemId={itemId}",
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json,
        BodyStyle = WebMessageBodyStyle.Bare
    )]
    List<ItemMedia> GetMediaForItem(int itemId);

    [OperationContract]
    [WebInvoke(
        Method = "GET",
        UriTemplate = "YouTubeVideos?itemId={itemId}",
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json,
        BodyStyle = WebMessageBodyStyle.Bare
    )]
    List<YouTubeVideo> GetYouTubeVideosForItem(int itemId);
}

And here are the generated models:

[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.ItemMedia")]
[global::System.Data.Linq.Mapping.InheritanceMappingAttribute(Code="media", Type=typeof(ItemMedia), IsDefault=true)]
[global::System.Data.Linq.Mapping.InheritanceMappingAttribute(Code="video", Type=typeof(YouTubeVideo))]
[global::System.Data.Linq.Mapping.InheritanceMappingAttribute(Code="image", Type=typeof(Image))]
public partial class ItemMedia : INotifyPropertyChanging, INotifyPropertyChanged
{

    private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);

    private int _ID;

    private int _ItemID;

    private string _MediaType;

    private int _Order;

    private System.Nullable<System.DateTime> _AddedOn;

    private System.Nullable<int> _AddedBy;

    private System.Nullable<System.DateTime> _ChangedOn;

    private System.Nullable<int> _ChangedBy;

    private EntityRef<Item> _Item;

#region Extensibility Method Definitions
partial void OnLoaded();
partial void OnValidate(System.Data.Linq.ChangeAction action);
partial void OnCreated();
partial void OnIDChanging(int value);
partial void OnIDChanged();
partial void OnItemIDChanging(int value);
partial void OnItemIDChanged();
partial void OnMediaTypeChanging(string value);
partial void OnMediaTypeChanged();
partial void OnOrderChanging(int value);
partial void OnOrderChanged();
partial void OnAddedOnChanging(System.Nullable<System.DateTime> value);
partial void OnAddedOnChanged();
partial void OnAddedByChanging(System.Nullable<int> value);
partial void OnAddedByChanged();
partial void OnChangedOnChanging(System.Nullable<System.DateTime> value);
partial void OnChangedOnChanged();
partial void OnChangedByChanging(System.Nullable<int> value);
partial void OnChangedByChanged();
#endregion

    public ItemMedia()
    {
        this._Item = default(EntityRef<Item>);
        OnCreated();
    }

    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ID", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
    public int ID
    {
        get
        {
            return this._ID;
        }
        set
        {
            if ((this._ID != value))
            {
                this.OnIDChanging(value);
                this.SendPropertyChanging();
                this._ID = value;
                this.SendPropertyChanged("ID");
                this.OnIDChanged();
            }
        }
    }

    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ItemID", DbType="Int")]
    public int ItemID
    {
        get
        {
            return this._ItemID;
        }
        set
        {
            if ((this._ItemID != value))
            {
                if (this._Item.HasLoadedOrAssignedValue)
                {
                    throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
                }
                this.OnItemIDChanging(value);
                this.SendPropertyChanging();
                this._ItemID = value;
                this.SendPropertyChanged("ItemID");
                this.OnItemIDChanged();
            }
        }
    }

    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_MediaType", DbType="VarChar(10)", IsDiscriminator=true)]
    public string MediaType
    {
        get
        {
            return this._MediaType;
        }
        set
        {
            if ((this._MediaType != value))
            {
                this.OnMediaTypeChanging(value);
                this.SendPropertyChanging();
                this._MediaType = value;
                this.SendPropertyChanged("MediaType");
                this.OnMediaTypeChanged();
            }
        }
    }

    [global::System.Data.Linq.Mapping.ColumnAttribute(Name="[Order]", Storage="_Order", DbType="Int NOT NULL")]
    public int Order
    {
        get
        {
            return this._Order;
        }
        set
        {
            if ((this._Order != value))
            {
                this.OnOrderChanging(value);
                this.SendPropertyChanging();
                this._Order = value;
                this.SendPropertyChanged("Order");
                this.OnOrderChanged();
            }
        }
    }

    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_AddedOn", DbType="DateTime")]
    public System.Nullable<System.DateTime> AddedOn
    {
        get
        {
            return this._AddedOn;
        }
        set
        {
            if ((this._AddedOn != value))
            {
                this.OnAddedOnChanging(value);
                this.SendPropertyChanging();
                this._AddedOn = value;
                this.SendPropertyChanged("AddedOn");
                this.OnAddedOnChanged();
            }
        }
    }

    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_AddedBy", DbType="Int")]
    public System.Nullable<int> AddedBy
    {
        get
        {
            return this._AddedBy;
        }
        set
        {
            if ((this._AddedBy != value))
            {
                this.OnAddedByChanging(value);
                this.SendPropertyChanging();
                this._AddedBy = value;
                this.SendPropertyChanged("AddedBy");
                this.OnAddedByChanged();
            }
        }
    }

    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ChangedOn", DbType="DateTime")]
    public System.Nullable<System.DateTime> ChangedOn
    {
        get
        {
            return this._ChangedOn;
        }
        set
        {
            if ((this._ChangedOn != value))
            {
                this.OnChangedOnChanging(value);
                this.SendPropertyChanging();
                this._ChangedOn = value;
                this.SendPropertyChanged("ChangedOn");
                this.OnChangedOnChanged();
            }
        }
    }

    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ChangedBy", DbType="Int")]
    public System.Nullable<int> ChangedBy
    {
        get
        {
            return this._ChangedBy;
        }
        set
        {
            if ((this._ChangedBy != value))
            {
                this.OnChangedByChanging(value);
                this.SendPropertyChanging();
                this._ChangedBy = value;
                this.SendPropertyChanged("ChangedBy");
                this.OnChangedByChanged();
            }
        }
    }
            [global::System.Data.Linq.Mapping.AssociationAttribute(Name="Item_ItemMedia", Storage="_Item", ThisKey="ItemID", OtherKey="ID", IsForeignKey=true)]
    public Item Item
    {
        get
        {
            return this._Item.Entity;
        }
        set
        {
            Item previousValue = this._Item.Entity;
            if (((previousValue != value) 
                        || (this._Item.HasLoadedOrAssignedValue == false)))
            {
                this.SendPropertyChanging();
                if ((previousValue != null))
                {
                    this._Item.Entity = null;
                    previousValue.ItemMedias.Remove(this);
                }
                this._Item.Entity = value;
                if ((value != null))
                {
                    value.ItemMedias.Add(this);
                    this._ItemID = value.ID;
                }
                else
                {
                    this._ItemID = default(int);
                }
                this.SendPropertyChanged("Item");
            }
        }
    }

    public event PropertyChangingEventHandler PropertyChanging;

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void SendPropertyChanging()
    {
        if ((this.PropertyChanging != null))
        {
            this.PropertyChanging(this, emptyChangingEventArgs);
        }
    }

    protected virtual void SendPropertyChanged(String propertyName)
    {
        if ((this.PropertyChanged != null))
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

public partial class YouTubeVideo : ItemMedia
{

    private string _YouTubeVideoID;

#region Extensibility Method Definitions
partial void OnLoaded();
partial void OnValidate(System.Data.Linq.ChangeAction action);
partial void OnCreated();
partial void OnYouTubeVideoIDChanging(string value);
partial void OnYouTubeVideoIDChanged();
#endregion

    public YouTubeVideo()
    {
        OnCreated();
    }

    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_YouTubeVideoID", DbType="NVarChar(15)")]
    public string YouTubeVideoID
    {
        get
        {
            return this._YouTubeVideoID;
        }
        set
        {
            if ((this._YouTubeVideoID != value))
            {
                this.OnYouTubeVideoIDChanging(value);
                this.SendPropertyChanging();
                this._YouTubeVideoID = value;
                this.SendPropertyChanged("YouTubeVideoID");
                this.OnYouTubeVideoIDChanged();
            }
        }
    }
}

public partial class Image : ItemMedia
{

    private string _ImageName;

    private string _ImageData;

#region Extensibility Method Definitions
partial void OnLoaded();
partial void OnValidate(System.Data.Linq.ChangeAction action);
partial void OnCreated();
partial void OnImageNameChanging(string value);
partial void OnImageNameChanged();
partial void OnImageDataChanging(string value);
partial void OnImageDataChanged();
#endregion

    public Image()
    {
        OnCreated();
    }

    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ImageName", DbType="NVarChar(MAX)")]
    public string ImageName
    {
        get
        {
            return this._ImageName;
        }
        set
        {
            if ((this._ImageName != value))
            {
                this.OnImageNameChanging(value);
                this.SendPropertyChanging();
                this._ImageName = value;
                this.SendPropertyChanged("ImageName");
                this.OnImageNameChanged();
            }
        }
    }

    [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ImageData", DbType="NVarChar(MAX)")]
    public string ImageData
    {
        get
        {
            return this._ImageData;
        }
        set
        {
            if ((this._ImageData != value))
            {
                this.OnImageDataChanging(value);
                this.SendPropertyChanging();
                this._ImageData = value;
                this.SendPropertyChanged("ImageData");
                this.OnImageDataChanged();
            }
        }
    }
}

Upvotes: 1

Views: 819

Answers (1)

Kirk Broadhurst
Kirk Broadhurst

Reputation: 28738

WCF, and all serialization process in .NET, has some tricks involved with objects that implement multiple classes. I believe you'd find similar a exception working with just a single object in this case.

The reason is that the YouTubeVideo is an ItemMedia item - i.e. it is valid to return a YouTubeVideo from your method - but if we were to call GetType on the item it would give a System.Type of YouTubeVideo. The types don't match, which confuses the deserialization process.

When an object is serialized in .NET the result includes the given type of the object - think of it as an instruction for the framework to rehydrate the object into the correct type - and in this case the type will be YouTubeVideo (as that's what GetType returns). Your deserialization code is expecting an ItemMedia type object so it's complains with an exception like the one you are seeing.

You can fix this problem using the KnownTypeAttribute. In this case you would want to add the attribute to the ItemMedia class, so that the deserializer will understand that YouTubeVideo is known to be an extension of that type.

[KnownType(typeof(YouTubeVideo))]
public partial class ItemMedia
{

Put this in a partial class rather than your generated code, as it's likely to be destroyed by future code generation if kept in the generated class.

Upvotes: 1

Related Questions