How to serialize circular referenced ef poco class using protobuf-net - c#

I'm using marc gravell's protobuf-net and ef core in my project.
long story short, I'm using Inverseproperty attribute on my POCO class which causes a circular reference when I fetch results from database which causes me trauble when I try to serialize data using protobuf net.
I'm currenyl serializing data with Jsonconvert by setting ReferenceLoopHandling = ReferenceLoopHandling.Ignore and returning a json string to the client to keep the app in a working state but do not want to use this method as it doesnot make any sense.
I would like to know if it is possible to either prevent EF core generating circular reference when using Inverseproperty attribute or if protobuf-net has an ignore referenceloop handling feature when serializing data..
a simplified version of my poco class is like this:
[ProtoContract]
[Table("CATEGORIES_M")]
public class CATEGORIES_M
{
public CATEGORIES_M()
{
CATEGORIES_M_COLLECTION = new HashSet<CATEGORIES_M>();
//Product = new HashSet<Product>();
CM_DATE = DateTime.Now;
}
[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[ProtoMember(1)]
public int CM_ROWID { get; set; }
[ProtoMember(2)]
public string CM_NAME { get; set; }
[ProtoMember(3)]
public int? CM_PARENT_REFNO { get; set; }
[ProtoMember(4)]
[ForeignKey(nameof(CM_PARENT_REFNO))]
[InverseProperty(nameof(CATEGORIES_M_COLLECTION))]
public CATEGORIES_M CATEGORIES_M_PARENT { get; set; }
[ProtoMember(5)]
[InverseProperty(nameof(CATEGORIES_M_PARENT))]
public ICollection<CATEGORIES_M> CATEGORIES_M_COLLECTION { get; set; }
}
any help is appreciated

Protobuf-net does not have good support for this scenario. V2 has some limited reference tracking capabilities, but these are deprecated in V3 because it caused more problems than it solved. My suggestions, as the library author:
serialize a simple tree model, and build your real model afterwards from it, or
use a different tool

Backreferences (parent level) can be tagged with [ProtoIgnore] to avoid circular references. That might change the behavior as a client might expect values there. Though, usually the client has the parent objects already and you might just need a key here. If that's the case, add an additional serializable property for the key value and mark as [ProtoMember(nn)], then.
Hint: Write test cases and use the Serialize class's static methods to check the behavior and get useful exceptions before trying to debug your server code.

Related

Ignoring / Not Mapping certain POCO properties in NEST 2.0

I am having a hard time finding good detail on NEST 2.0, the wrapper for Elasticsearch 2.2 I am using. My question is this: Can I do a bulk insert on an object (class with public data members) and map that to Elasticsearch where only the mapped fields between my C# class and the ES server mapping will save? And it will not add the additional fields in my class I do not want?
Right now I have a class of with strings and doubles and lists of other classes in it. I want to save the strings and doubles, but NOT include the Lists on my bulk inserts to Elasticsearch. It wants to save every piece of data in every field of my class. Is there a class member attribute or some other way to say "do not add this field if it has no mapping" that I have missed? I hope so.
You can ignore properties of your POCO in a number of ways with NEST 2.x. Let's use the following POCO as an example
using Nest;
using Newtonsoft.Json;
[ElasticsearchType(Name = "company")]
public class Company
{
public string Name { get; set; }
[String(Ignore = true)]
public string IgnoreViaAttribute { get; set; }
public string IgnoreViaSettings { get;set; }
[JsonIgnore]
public string IgnoreViaSerializerSpecificAttribute { get; set; }
}
1.Using the Ignore property on a derived ElasticsearchPropertyAttribute type (in our example, StringAttribute on IgnoreViaAttribute property) applied to the property that should be ignored on the POCO
2.Using the .InferMappingFor<TDocument>(Func<ClrTypeMappingDescriptor<TDocument>, IClrTypeMapping<TDocument>> selector) on the connection settings
var settings = new ConnectionSettings(new Uri("http://localhost:9200"))
.InferMappingFor<Company>(i => i
.Ignore(p => p.IgnoreViaSettings)
);
var client = new ElasticClient(settings);
These settings are cached per instance of ConnectionSettings.
3.Using an ignore attribute applied to the POCO property that is understood by the IElasticsearchSerializer used, and inspected inside of the CreatePropertyMapping() on the serializer. In the case of the default JsonNetSerializer, this is the Json.NET JsonIgnoreAttribute. In our example, this is demonstrated by the attribute applied to the IgnoreViaSerializerSpecificAttribute property.
What I found by digging around a bit and testing a small class is that the following structure did indeed hide the attributes when you post a class with NEST 2.0 C#. If you do this just above the class member you wish to ignore when doing a bulk add that covers it.
[String(Ignore = true)]

Serialize embedded class / object independently or as string?

I am implementing a mongodb cache for this asp.net webapi output cache (I agree redis would be better / faster but for now, I need a mongodb implementation!)
Anyway,
I have a CachedItem class that holds my key / value:
[BsonIgnoreExtraElements]
public class CachedItem
{
[BsonElement("key")]
public string Key { get; set; }
[BsonElement("value")]
public object Value { get; set; }
}
Value is an object, that could be anything, we don't control that.
In one of my tests, I have a very simple poco:
public class UserFixture
{
public UserFixture()
{
Id = Guid.NewGuid();
}
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
When this is set to the Value it is serialized and persisted.
When I try to retrieve it, it fails to deserialize, as it has automatically grabbed the "Id" property.
An error occurred while deserializing the Id property of class WebAPI.OutputCache.MongoDb.Tests.UserFixture: Cannot deserialize Guid from BsonType ObjectId
Obviously, I can't decorate UserFixture
Is there any way I can tell MongoDB driver to basically serialize CachedItem.Value, as say, a string?
I could use JSON.net to do this before saving, and deserialize it on the way back out, but I was hoping to avoid this.
It's also on GitHub
That link should take you straight to the relevent commit if you'd like to try the failing test.
You can of course tell MongoDB to serialize your class as a string by building your own custom BsonSerializer. I have found it easier to inherit from their BsonStringSerializer. You also need to register that serializer with your specific type. (I suggest using a BsonSerializationProvider for that)
What you do need to think about is how to represent all your possible data as a string so you could deserialize it back to your application (Consider for example that you probably need to save the type information).

MongoDb C# failed to serialize the response

Class (Entity)
public class Entity
{
public ObjectId Id { get; set; }
public Entity()
{
Id = ObjectId.GenerateNewId();
}
}
Class (Member)
public class Member : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string MobileNumber { get; set; }
}
Action
public dynamic Get()
{
var response = UnitOfWork.MemberRepository.GetMembers();
return response;
}
I'm building a API using .NET WebAPI and using mongodb as a datastore, I'm have some troubles serializing the responding object from the database.
Can't understand why, search the internet a while and found similar problems with no solutions. Either I'm a bad google searcher or there answer is hidden somewhere deep:)
Full stack trace: http://pastie.org/8389787
This is little guessing, but the code really isn't too telling.
I'm pretty sure this is because the C# Mongo driver's BsonDocument exposes a ton of properties like AsBoolean, AsInt, AsString, etc. Calling those getters on data that isn't convertible to the respective type causes an exception. While I don't see them in the stack trace, that might be a compiler optimization.
One solution is to make the code strongly-typed (if it isn't already). I don't know what UnitOfWork.MemberRepository.GetMembers(); is, but it hides what you're doing and it's also not clear what it returns. You're losing a lot of the advantages of the C# driver. The Collection<T> class is pretty much a repository pattern already by the way.
A cleaner approach (they aren't mutually exclusive) is to not serialize the database object to the outside world, but use DTO for the WebAPI side and translate between them, for instance using AutoMapper. I would always do this, because you're throwing an object that might be decorated with DB-Attributes in a serializer you don't know - that could lead to all sorts of problems. Also, you often want to hide certain information from the outside, or make it read-only.
Another option is to use ServiceStack.Text as a JSON-serializer instead, which tends to cause less trouble in my experience.

InvalidDataContractException on WP7 application on class that is serializable

I'm having an strange error when trying to save an object into isolated storage. I have a class that has some properties, here's the code :
[DataContract]
public class ExerciseStatistic
{
[XmlIgnore]
public int CorrectAnswers
{
get
{
return Attempts.Where(a => a.AttemptAnswerIsCorrect).Count();
}
}
[XmlIgnore]
public int IncorrectAnswers
{
get
{
return Attempts.Where(a => !a.AttemptAnswerIsCorrect).Count();
}
}
[XmlIgnore]
public int AnswerAttempts
{
get { return Attempts.Count; }
}
public List<AnswerAttempt> Attempts { get; set; }
public ExerciseStatistic()
{
Attempts = new List<AnswerAttempt>();
}
}
public class AnswerAttempt
{
public DateTime AttemptDate { get; set; }
public string AttemptTargetName { get; set; }
public string AttemptName { get; set; }
public bool AttemptAnswerIsCorrect { get; set; }
}
However, when trying to save it with this sentence :
IsolatedStorageSettings.ApplicationSettings["a"] = new ExerciseStatistic()
{
Attempts = new List<AnswerAttempt>()
{
new AnswerAttempt()
{
AttemptAnswerIsCorrect = true,
AttemptDate = DateTime.Now,
AttemptName = "lala",
AttemptTargetName = "lala2"
},
new AnswerAttempt()
{
AttemptAnswerIsCorrect = false,
AttemptDate = DateTime.Now,
AttemptName = "lalab",
AttemptTargetName = "lalab2"
}
}
};
I'm getting an exception like this one (i changed a bit the signature of the code with fake names, but for the example it serves its purpose) :
Type 'XX.Model.FirstClass.SecondClass' cannot be serialized. Consider
marking it with the DataContractAttribute attribute, and marking all
of its members you want serialized with the DataMemberAttribute
attribute.
I don't understand why the serializer is trying to serialize an object of my model (which is not serializable) when the class that I'm giving it doesn't have any references to that kind of type... what am i missing? -> nope, i don't want to add datacontract attributes to classes that i don't need and am not planning to serialize, so please don't answer with this :)
You might experience this problem if you work through the reference procedure in "Walkthrough: Consuming OData with MVVM for Windows Phone" at http://msdn.microsoft.com/en-us/library/hh394007(v=VS.92).aspx
When you get to the point where you call :
Return DataServiceState.Serialize(_context, collections);
You might get an InvalidDataContractException with the message:
Type 'DataBoundApp1.Northwind.NorthwindEntities' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.
Thanks to the answer by Daniel Perez, I was able to resolve this problem and I am documenting my steps to clarify the solution for others:
Show hidden files in Solution Explorer
Open the file "Reference.cs" (under your Service Reference, expand
Reference.datasvcmap)
If your Data Service Context class is missing the [DataContract]
attribute, add it as shown here:
.
namespace OCC.WindowsPhone.OrlandoCodeCampService
{
[DataContract] <--- I ADDED THIS
public partial class OrlandoCodeCampEntities : global::System.Data.Services.Client.DataServiceContext
{..}
Once I added the DataContract attribute, the problem went away!
It seems to me you try to exclude properties from serialization by using XmlIgnore.
From the documentation:
You can opt out members from serialization by using the IgnoreDataMemberAttribute.
so try using IgnoreDataMemberAttribute instead of XmlIgnore to opt out members from serialization.
I also had some troubles with DataContract in the very same situation as you, therefore I reverted to plain old XML serialization to strings, which i then stored in isolated storage. This also eases debugging.
I don't think that this is a proper answer, but it's what i had to do in order to fix it.
After changing some more the code, i realised that this was failing EVEN if I wasn't saving anything to the isolated storage. Just declaring a DataContract attribute on the type made the error arise. I must think that WP7's framework at some point is parsing all classes that have this attribute, and for some strange and obscure reason (which i can't find) it's looking for them in other classes as well. I added the DataContract attributes in the classes that the framework is complaning about, and also some KnownType attributes as well, and everything started to run smoothly... weird weird... if someone can shed some light into this, i'd be happy (i hate it when i solve a problem but without knowing the exact cause)

WCF: generic list serialized to array

So I am working with WCF and my services return types that contain generic lists. WCF is currently converting these to arrays over the wire. Is there a way I configure WCF to convert them back to lists afterwards? I know there is a way by clicking advanced when you add a service reference but I am looking for a solution in configuration files or something similar.
[DataContract(IsReference = true)]
public class SampleObject
{
[DataMember]
public long ID { get; private set; }
[DataMember]
public ICollection<AnotherObject> Objects { get; set; }
}
It is very odd, also, because one service returns it as a list and the other as an array and I am pretty sure they are configured identically.
At the advanced tab when adding your service reference you can set this option as well. standard Arrays are set.
I think this is dent with purely from the way that the client tool generates the contracts from the WSDL. In my case, I made a reusable .dll that contains my [OperationContract] and [DataContract] classes, and use it from both the client and the server, instead of generating one with SvcUtil. This way I preserve my lists of generics.
In addition, take care not to have both arrays and generics in the classes from which you serialize the instances with WCF, because you'll get a problem during deserialization : everything will be converted either to ArrayOf (if you don't change the configuration) or to Collection Type.
As result you will get errors during deserialization from the WCF code trying to assign an array where you wait a Collection and conversely.
This was just my 2cent advice from what I learned during a small project with WCF. :)
I found a solution that was much simpler and worked well enough for me, although it might not work for others. I simply switched from using ICollection (IList also produces this result) to List. It worked fine after that.
Solution from Here.
I also found a possible configuration solution from Here near the bottom.
<CollectionMappings>
<CollectionMapping TypeName="ChangeTracker.ChangeTrackingCollection'1" Category="List" />
</CollectionMappings>
Instead of use ICollection<AnotherObject> in your data contract, that will be generated in client application as a AnotherObject[].
Try this:
define a new data contract
[CollectionDataContract]
public class AnotherObjectCollection : List<AnotherObject> {}
in your code:
DataContract(IsReference = true)]
public class SampleObject
{
[DataMember]
public long ID { get; private set; }
[DataMember]
public AnotherObjectCollection Objects { get; set; }
}
in Visual Studio (same to svcUtil), the client proxy code will appear like this:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.CollectionDataContractAttribute(Name="AnotherObjectCollection", Namespace="http://schemas.datacontract.org/2004/07/SampleObject", ItemName="AnotherObject")]
[System.SerializableAttribute()]
public class AnotherObjectCollection : System.Collections.Generic.List<AnotherObject> {}
DataContract(IsReference = true)]
public class SampleObject
{
[DataMember]
public long ID { get; private set; }
[DataMember]
public AnotherObjectCollection Objects { get; set; }
}
This also works for built-in .NET types.
antonio

Categories

Resources