How do I serialize a class with properties declared as interfaces? - c#

Here is my list of classes:-
public interface IUniquelyIdentifiable
{
string AuthorName { get; set; }
}
public interface IUniquelyIdentifiable1
{
string CategoryName { get; set; }
}
public interface IUniquelyIdentifiable2
{
string PublisherName { get; set; }
}
[Serializable]
public class Book
{
//BookId, Category, Title, Author, Publisher, Description, Price, ISBN, PublicationDate.
public IUniquelyIdentifiable Author { get; set; }
public IUniquelyIdentifiable1 Category { get; set; }
public IUniquelyIdentifiable2 Publisher { get; set; }
public int BookId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int ISBN { get; set; }
public int Price { get; set; }
public string PublicationDate { get; set; }
}
[Serializable]
class Author : IUniquelyIdentifiable
{
//AuthorId, AuthorName, DateOfBirth, State, City, Phone
public int AuthorId { get; set; }
public string AuthorName { get; set; }
public string DateOfBirth { get; set; }
public string State { get; set; }
public string City { get; set; }
public int Phone { get; set; }
}
[Serializable]
class Category : IUniquelyIdentifiable1
{
//CategoryId, CategoryName, Description
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
}
[Serializable]
class Publisher : IUniquelyIdentifiable2
{
//PublisherId, PublisherName, DateOfBirth, State, City, Phone.
public int PublisherId { get; set; }
public string PublisherName { get; set; }
public string DateOfBirth { get; set; }
public string State { get; set; }
public string City { get; set; }
public int Phone { get; set; }
}
below is the method which is trying to serialize the objects created of the above classes:-
public static void XmlSerializeMyObject()
{
XmlSerializer writer = new XmlSerializer(typeof(Book));
//overview.title = "Serialization Overview";
var path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "//SerializationOverview.xml";
FileStream file = File.Create(path);
writer.Serialize(file,bookList);
file.Close();
}
As you can see I even used the attribute [Serializable] but still getting the error that I cant serialize interfaces.
Also I just want serialize the objects of the given classes and not interfaces.

See the comments at the end. My first solution answers the question directly but I don't recommend doing it unless you have no choice. Short version - I recommend solving the problem by using the concrete types Author, Category, and Publisher instead of interfaces in the Book class.
In order to serialize a type there must be some way of determining what the concrete types of the members are. It's possible that something could serialize an instance of Book using an implementation of IUniquelyIdentifiable that is unknown to the application deserializing it.
You can modify your Book class like this:
[Serializable][DataContract][KnownType(typeof(Author))]
[KnownType(typeof(Category))]
[KnownType(typeof(Publisher))]
public class Book
{
[DataMember]public IUniquelyIdentifiable Author { get; set; }
[DataMember]public IUniquelyIdentifiable1 Category { get; set; }
[DataMember]public IUniquelyIdentifiable2 Publisher { get; set; }
[DataMember]public int BookId { get; set; }
[DataMember]public string Title { get; set; }
[DataMember]public string Description { get; set; }
[DataMember]public int ISBN { get; set; }
[DataMember]public int Price { get; set; }
[DataMember]public string PublicationDate { get; set; }
}
Then use a DataContractSerializer to serialize. Here's an example:
using (var sw = new StringWriter())
{
using (var xw = new XmlTextWriter(sw))
{
var book = new Book();
book.Author = new Author { AuthorName = "Bob" };
book.Category = new Category { CategoryId = 5 };
book.Publisher = new Publisher { City = "Clearwater" };
var serializer = new DataContractSerializer(typeof(Book));
serializer.WriteObject(xw, book);
var output = sw.ToString();
Assert.IsNotNull(sw);
}
}
This answers the question, but it doesn't solve any problems. In fact, it creates a new problem.
If you just declare the Author, Category, and Publisher properties of Book as concrete types, then you're constrained to using those types. The compiler will show an error if you try to set that property using any class that isn't Author.
But if you add the KnownType attribute as above, the problem is even worse because it's hidden. Now you can set Author to anything implementing IUniquelyIdentifiable. But when you do that (perhaps in some other part of your application) you have no way of knowing that it will fail when serialized. The constraint is still there - you still have to use Author. The difference is that now you get a runtime exception instead of a compile error.
You could instead specify a list of known types to the DataContractSerializer. That gives you a way to specify more types, even using reflection to get a list of types that implement the interface.
But it's still problematic. It's a hidden constraint. You're saying that the type of the property is IUniquelyIdentifiable. According to proper OOP design and the Liskov substitution principle you should be able to use any implementation of that interface. But in reality you can't use any implementation. You have to use one that may or may not be labeled as a "known" type somewhere else (or in multiple places) in your code. Someone could break your application at any time without causing a compile error.
Based on that I'd say to only use the above methods if you have no choice, like if you have to serialize something that you didn't design. If you're writing your own classes then I would just declare Book using the concrete types Author, Category, and Publisher.

You can't serialize interfaces. It doesn't work.
your solution is to change Book's properties to the actual serializable classes:
[Serializable]
public class Book
{
//BookId, Category, Title, Author, Publisher, Description, Price, ISBN, PublicationDate.
public Author Author { get; set; }
public Category Category { get; set; }
public Publisher Publisher { get; set; }
public int BookId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int ISBN { get; set; }
public int Price { get; set; }
public string PublicationDate { get; set; }
}
The question #Richard_Everett linked contain the same answer. Sorry I can't provide any better solution.

Related

How Do I Select an Object by Embedded Object Value 2 Levels Deep in C#?

I have an object that has an embedded list of objects, which itself has an embedded list of object, like so
namespace AppName.Models
{
[BindProperties]
public class WorkOrder
{
public string OrderNumber { get; set; }
public int FinishedGoodsToMake { get; set; }
public int Quantity { get; set; }
public List<FinishedGood> FinishedGoods { get; set; } = new List<FinishedGood>();
}
[BindProperties]
public class FinishedGood
{
public int ID { get; set; }
public string ProductCode { get; set; }
public List<RawMaterial> RawMaterials{ get; set; } = new List<RawMaterial>();
}
[BindProperties]
public class RawMaterial
{
public string ProductCode { get; set; }
public int Status { get; set; }
public string Description { get; set; }
}
}
In the RawMaterial object, the Status can have a value of 0, 1, 2 or 3. I need to process them grouped by this status. I think it would be easiest if I could just loop through statuses and select the object I need. So, let's say the work order has three FinishedGoods and each FinishedGood has four RawMaterials. I need to get a full WorkOrder object, with FinishedGoods and RawMaterials, only where RawMaterial.Status == 0. Is there any way to do this easily?
I could optionally map the object to iterate through each status also. Just looking for the easiest/ most elegant solution here. Thanks!

How to delete an element inside array of a nested document in C# Mongodb strongly typed driver

I am using the official C# MongoDb strongly typed driver version 2.5.0 to interact with MongoDB.
Consider the following classes:
public class Author
{
public Author()
{
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string BirthDate { get; set; }
public string ScientificDegree { get; set; }
public Library Library { get; set; }
}
public class Library
{
public DateTime DateAdded { get; set; }
public DateTime LastModified { get; set; }
public List<Book> Books { get; set; }
[BsonDefaultValue(0)]
public int ReadCount { get; set; }
}
How to delete a book using it's id from the author library? Here is my code to delete an element from the array directly.
var field = new ExpressionFieldDefinition<Library, List<Book>>(library => library.Books);
var bookFilter = Builders<Book>.Filter.Eq(book => book.Id, bookId);
var update = Builders<Library>.Update.PullFilter(field, bookFilter);
//How to apply the update to the author using author id
You need to get stored Libraryand perform changes to that Library object and update it back to document. Another way you can make use of PullFilter to delete it
var update = Builders<Author>.Update.PullFilter(x=>x.Library.Books,Builders<Book>.Filter.Eq(x=>x.id,bookId));
db.Author.UpdateOneAsync(x => x.Id.Equals(autherId), update).Result;
db.Author is collection instance

Retrieving count from a list of objects

I have a list of comments as a parameter to each lesson on my website. I have tried a number of different ways (shown below) to retrieve the number of comments. The structure of my Model looks like this:
public class EducateLesson
{
[Key]
public int EducateLessonID { get; set; }
public EducateTopics Topic { get; set; }
public string Title { get; set; }
public string Introduction { get; set; }
public string Body { get; set; }
public string VideoURL { get; set; }
public int Likes { get; set; }
public virtual List<Comment> Comments { get; set; }
public void AddComment(Comment c)
{
Comments.Add(c);
}
}
public class Comment
{
[Key]
public int CommentID { get; set; }
public DateTime Date { get; set; }
public string IdentityUserName { get; set; }
public string Text { get; set; }
}
I have noted that the Comment class has no FK to the lesson object here.
These are the methods I've tried to retrieve the count for an individual lesson in the view:
count = (from l in lesson.Comments select l).Count();
#lesson.Comments.Count()
#lesson.Comments.Count
I solved this answer without changing the model. Comments made on this post were not actually beneficial. Firstly I added this to my repository class:
public int CommentCount(int id)
{
EducateLesson lesson = context.EducateLessons.Find(id);
return lesson.Comments.Count;
}
Next I took the count by creating a repository class and using this method for each lesson object:
int count = 0;
EducateRepository EducateRepository = new EducateRepository();
count = EducateRepository.CommentCount(lesson.EducateLessonID);

A simple OOP understanding

I have a class:
public class TaskDiplayModel
{
public int taskId { get; set; }
[DisplayName("Task Description")]
public string description { get; set; }
[DisplayName("Priority")]
public string priority { get; set; }
[DisplayName("State")]
public string state { get; set; }
[DisplayName("Due By")]
public DateTime deadline { get; set; }
[DisplayName("Created By")]
public PersonObject created_by { get; set; }
[DisplayName("Assigned To")]
public PersonObject assigned_to { get; set; }
[DisplayName("Category")]
public string category { get; set; }
[DisplayName("Sub Category")]
public string subCategory { get; set; }
[DisplayName("Created")]
public DateTime createdDate { get; set; }
[DisplayName("Updated")]
public DateTime lastUpdatedDate { get; set; }
[DisplayName("Updated By")]
public PersonObject lastUpdatedBy { get; set; }
}
I then have another class which inherits from this class:
public class TaskModifyModel : TaskDiplayModel
{
public int priorityId { get; set; }
public int stateId { get; set; }
public int categoryId { get; set; }
public int subCategoryId { get; set; }
public SelectList states { get; private set; }
public SelectList priorities { get; private set; }
public SelectList categories { get; private set; }
public SelectList subCategories { get; private set; }
public TaskModifyModel()
{
SetupReferenceData(0);
}
public TaskModifyModel(int taskId)
{
var taskService = new TaskService();
var task = taskService.GetTask(taskId);
}
}
In my application, I create a TaskModifyModel object. However, string fields from the base class are null. I'd expect them to have been created and be String.Empty. But I get exception when I try to access them. Am I missing something?
These are MVC3 Models, by the way.... And code from the classes have been omitted as I think it's irrelevant to the question.
in .NET, the default value for a string is null, not String.Empty, so unless you specifically set the values of the properties to String.Empty, they will remain null.
Assuming you want your string properties to default to an empty string instead of null, you would normally do this either by setting them in the constructor:
public void TaskDiplayModel()
{
description = String.Empty;
priority = String.Empty;
state = String.Empty;
}
Or by using a field-backed property instead of an auto property, and setting the backing field:
private string _description = String.Empty;
[DisplayName("Task Description")]
public string description
{
get { return _description; }
set { _description = value; }
}
Personally I usually use the first option, doing it in the constructor, because it is less to code.
Just to add to rally25rs' excellent answer (+1 from me anyway), given the following abridged version:
public class TaskDiplayModel
{
private string _priority = string.Empty;
public int TaskId { get; set; }
public string Description { get; set; }
public string Priority
{
get { return _priority; }
set { _priority = value; }
}
public TaskDisplayMode()
{
Description = string.Empty;
}
}
public class TaskModifyModel : TaskDisplayMode
{
public int PriorityId { get; set; }
public TaskModifyModel()
:base()
{
PriorityId = 3;
}
}
Then the order upon construction of TaskModifyModel is as follows:
Set _priority to string.Empty, TaskId to 0, PriorityId to 0, and Description to null (if no value is stated, everything gets set to null if a reference, 0 if a number, and structs get set to having their fields full of 0 and null).
If the constructor body of TaskDiplayModel is executed, setting Description to string.Empty.
The constructor body of TaskModifyModel is executed, setting PriorityId to 3.
And so on if the nested inheritance is more complicated. As a rule, you can expect some of this to be optimised, so don't worry about whether it would be faster or slower to take a different approach as to how you set a value - it won't, except in debug runs. It does though explain the rule of avoiding calling virtual methods within constructors - most of the time we don't need to care about the ordering above and just treat the constructor as a single unit after which we have a fully constructed object, but if you start playing with virtual calls then the above ordering becomes very important because at one point PriorityId is 0, and at another its 3.

Ria Services and navigation property issues

I'm encountering an issue using Silverlight4, Ria Services and Entity Framework.
From my sl client I try to get some data through ria services, in my domainService class this method gets called:
public IQueryable<LastMinuteWachtLijstPromotie> GetLastMinuteWachtLijstPromoties(){
IQueryable<LastMinuteWachtLijstPromotie> list = (IQueryable<LastMinuteWachtLijstPromotie>)this.ObjectContext.LastMinuteWachtLijstPromoties.Include("Promotie");
return (from LastMinuteWachtLijstPromotie lwmp in list where lwmp.Actief select lwmp);
}
when I check the contents of the list, in debug mode, it's filled with objects of type LastMinuteWachtLijstPromotie.
these objects have a navigation property to an Object named Promotie.
And i can access the properties of these Promotie objects.
On the silveright client however a method gets invoked when loading is complete:
public void OnLoadEntitiesCompleted(ServiceLoadResult<T> result) {
}
In this method I get all the requested LastMinuteWachtLijstPromotie objects as expected, the property
Promotie however is null.
I have set the [Include] tag on the property Promotie in the auto generated metadata class
and I use the .Include("Promotie")
These same methods are used for different objects from my Domain Model, this works perfectly.
Also, I cannot seem to find differences in the .edmx file with the database mappings and navigation properties.
Has anyone encountered the same issue or know a solution for it?
the metadata classes:
[MetadataTypeAttribute(typeof(LastMinuteWachtLijstPromotie.LastMinuteWachtLijstPromotieMetadata))]
public partial class LastMinuteWachtLijstPromotie
{
// This class allows you to attach custom attributes to properties
// of the LastMinuteWachtLijstPromotie class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class LastMinuteWachtLijstPromotieMetadata
{
// Metadata classes are not meant to be instantiated.
private LastMinuteWachtLijstPromotieMetadata()
{
}
public int AlertId { get; set; }
public string ArtikelNummer { get; set; }
public Nullable<int> ArtikelVariant { get; set; }
public int LastMinuteWachtLijstPromotieId { get; set; }
[Include]
public Promotie Promotie { get; set; }
public int PromotieArtikelId { get; set; }
public int PromotieId { get; set; }
public bool Actief { get; set; }
public DateTime Aanmaakdatum { get; set; }
}
}
[MetadataTypeAttribute(typeof(Promotie.PromotieMetadata))]
public partial class Promotie
{
// This class allows you to attach custom attributes to properties
// of the Promotie class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class PromotieMetadata
{
// Metadata classes are not meant to be instantiated.
private PromotieMetadata()
{
}
public string ActieType { get; set; }
public string AssortimentsManagerNaam { get; set; }
public string AssortimentsManagerTeamIds { get; set; }
[Display(Name = "Commerciele tekst")]
[Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(Nokavision.ReclameFolder.UI.Web.Resources.ValidationResources))]
public string CommercieleTekst { get; set; }
[Display(Name = " ")]
public string CommercieleTekstDetails { get; set; }
[Include]
public Frame Frame { get; set; }
public Nullable<int> FrameId { get; set; }
public Nullable<DateTime> LastMinuteWijzigingsDatum { get; set; }
public string Opmerkingen { get; set; }
[Display(Name = "Op wachtlijst")]
public Nullable<bool> OpWachtLijst { get; set; }
//public Nullable<int> PromotieCopyId { get; set; }
public int PromotieId { get; set; }
[Include]
public EntityCollection<PromotieLeverancier> PromotieLeveranciers { get; set; }
[Include]
public EntityCollection<PromotieMutatie> PromotieMutaties{ get; set; }
//public Nullable<int> PromotieOrigineleId { get; set; }
[Include]
public EntityCollection<PromotieSymbool> PromotieSymbolen { get; set; }
public string Status { get; set; }
[Display(Name = "Promotie inhoud")]
public string PromotieInhoud { get; set; }
[Display(Name = "Promotie eenheid")]
public string PromotieEenheid { get; set; }
[Display(Name = "Promotie prijs")]
public decimal PromotiePrijs { get; set; }
}
}
Add the Composition attribute to the property Promotie property of the LastMinuteWachtLijstPromotieMetadata class. Then it should work.
public partial class LastMinuteWachtLijstPromotie {
internal sealed class LastMinuteWachtLijstPromotieMetadata{
[Include]
[Composition]
public Promotie Promotie { get; set; }
}
}
I know this is an older thread and it may well have been answered elsewhere but I just stumbled upon it and since nobody has provided a link or a better answer.
I'm currently using Silverlight 5 and this is what worked for me (I think the process is the same in SL4 IIRC).
When propegating navigation properties to the client you need to tell RIA services that there is a relationship somewhere using the [Key] and [Association] attributes, this, not unlike the entity framework just describes how to map the relationship to the proper object.
First the metadata classes:
[MetadataTypeAttribute(typeof(Category.CategoryMetadata))]
public partial class Category
{
internal sealed class CategoryMetadata
{
private CategoryMetadata() {
}
[Key]
public int Id { get; set; }
public string NAME { get; set; }
[Association("CategoryToProducts", "Id", "CAT")]
[Include]
public EntityCollection<Product> Products { get; set; }
}
}
[MetadataTypeAttribute(typeof(Order.OrderMetadata))]
public partial class Order
{
internal sealed class OrderMetadata
{
// Metadata classes are not meant to be instantiated.
private OrderMetadata() {
}
[Key]
public int Id { get; set; }
public int PRODID { get; set; }
public DateTime DATE { get; set; }
public bool DONE { get; set; }
public int QTY { get; set; }
[Association("OrderToProduct", "PRODID", "Id", IsForeignKey = true)]
[Include]
public Product Product { get; set; }
}
}
[MetadataTypeAttribute(typeof(Product.ProductMetadata))]
public partial class Product
{
internal sealed class ProductMetadata
{
private ProductMetadata() {
}
[Key]
public int Id { get; set; }
public int CAT { get; set; }
public string NAME { get; set; }
public string DESC { get; set; }
public decimal PRICE { get; set; }
public int QTY { get; set; }
public long UPC { get; set; }
[Association("ProdToCat", "CAT", "Id", IsForeignKey = true)]
[Include]
public Category Category { get; set; }
[Association("ProductToOrders", "Id", "PRODID")]
[Include]
public EntityCollection<Order> Orders { get; set; }
}
}
Now we need to tell RIA services we want it to load the association:
(Note: Intellisense says it's a dot separated list of property names to include, however I tried something like .Include("Category.SubCategory") and this failed with an exception... though .Include("Category").Include("SubCategory") worked like a charm!)
public IQueryable<Product> GetProducts() {
return this.ObjectContext.Products.Include("Category");
}
I can now access my "Category" property from the Silverlight client and it is not NULL :)
Same as SilverX: just had the issue, solved it and thought it could be useful to someone.
I too had all the configuration stuff correct ([Include] for RIA S, Include() for EF) but a navigation property was still null on the Silverlight side.
Turns out the domain service method was using the [Invoke] attribute (and returning a IEnumerable<T>). Removing this attribute solved the issue.
(just for the record, [Invoke] was being used because the method had a List<Entity> parameter)

Categories

Resources