protobuf-net uses nested protobuf constructs to support inheritance. However, can it be made to push properties into a flat target class that has the same properties as the inherited "serialized" version?
See the test example below. Needless to say the result of the Flat namespace is null for both properties.
Possible solution: copy data into flat.B first on a property by property basis.
Note: this is not the prefered option.
using System;
namespace hierarchy
{
using ProtoBuf;
[ProtoContract]
public class A
{
[ProtoMember(1)]
public string prop1 { get; set; }
}
[ProtoContract]
public class B : A
{
public B()
{
}
[ProtoMember(1)]
public string prop2 { get; set; }
public override string ToString()
{
return "prop1=" + prop1 + ", prop2=" + prop2;
}
}
}
namespace flat
{
using ProtoBuf;
[ProtoContract]
public class B
{
[ProtoMember(1)]
public string prop1 { get; set; }
[ProtoMember(2)]
public string prop2 { get; set; }
public override string ToString()
{
return "prop1=" + prop1 + ", prop2=" + prop2;
}
}
}
namespace TestProtoSerialization
{
using ProtoBuf;
using System.IO;
public class Test2
{
public void Test()
{
var hb = new hierarchy.B();
hb.prop1 = "prop1";
hb.prop2 = "prop2";
var ms = new MemoryStream();
Serializer.Serialize<hierarchy.B>(ms, hb);
var flatB = Serializer.Deserialize<flat.B>(ms);
Console.WriteLine(hb.ToString()); // <----- Output: prop1=prop1, prop2=prop2
Console.WriteLine(flatB.ToString()); // <----- Output: prop1=, prop2=
}
}
public class Program
{
private static void Main(string[] args)
{
var o2 = new Test2();
o2.Test();
}
}
}
Not directly, and I'm not sure there is a great need to. Maybe I am missing something in the example...
To pick up on the key point - even forgetting about inheritance you've broken the contract - te fields in your exampl are 1 & 1 in one model and 1 & 2 in the other.
It really depends what your objective is; if you just want to push the data over, then sure you can set up a RuntimeTypeModel that only knows about the derived type (disable automatic configuration and add the fields manually). This will then only work for the derived type (obviously), but will output the data as expected by the flat model:
var model = TypeModel.Create();
model.Add(typeof(B), false)
.Add("prop1", "prop2");
Then use model.Serialize etc.
However, writing a flat conversion method on c#, or using AutoMapper would be more obvious. I would only use the above if my objective is to remove the inheritance from the output, for example for interoperability reasons.
Related
So, what I want is to serialise and deserialise some JSON using a generic field wrapper class.
I do not want to write some custom JsonConverter class.
I do wish to keep it simple and write some implicit operator type conversion. The following minimal piece of C# code is where I am at now .... just enough to demonstrate the issue and no more. Don't overlook the implicit operators.
using Newtonsoft.Json;
using System;
namespace test
{
public struct SofT<T>
{
[JsonProperty]
public T TValue { get; set; }
public static implicit operator SofT<T>(string jtoken)
{
return new SofT<T>()
{
TValue = (T)Convert.ChangeType(jtoken, typeof(T))
};
}
public static implicit operator string(SofT<T> soft)
{
return soft.TValue?.ToString() ?? "";
}
}
[JsonObject(MemberSerialization.OptIn)]
public class Something
{
[JsonProperty]
public SofT<int> TestStructInt { get; set; }
[JsonProperty]
public SofT<decimal> TestStructDecimal { get; set; }
}
public class Program
{
public void Run()
{
var json = "{ \"TestStructInt\" : \"12\", \"TestStructDecimal\" : \"3.45\"}";
var modelDeserialised = JsonConvert.DeserializeObject<Something>(json);
var modelReserialised = JsonConvert.SerializeObject(modelDeserialised);
Console.WriteLine(modelReserialised);
}
static void Main(string[] args)
{
new Program().Run();
}
}
}
The JSON string is deserialised perfectly.
The model object is not reserialised correctly.
The string that is spat out to the console is:
quote {"TestStructInt":{"TValue":12},"TestStructDecimal":{"TValue":3.45}}
The string I expect, or better put want, to be spat out to the console is the same structure in the source JSON, ie:
quote {"TestStructInt":"12"},"TestStructDecimal":"3.45"}}
I am asking for a second pair of eyes to point out my error (and yes I can see the error, the annotation of Value with [JsonProperty], but it seems necessary for default serialisation).
I am calling a stored procedure from Entity Framework and trying to get result of stored procedure in a model-view class but I am getting error while casting list of Result class I got from entity framework -
Below code I tried, but I am getting error while trying to cast, I tried other way also like ConvertAll<> but didn't work -
public List<DepartmentModelView> GetDepartmentData()
{
using (Model1Container obj = new Model1Container())
{
return obj.usp_getDepartment().ToList<usp_getDepartment_Result>().Cast<DepartmentModelView>.ToList();
}
}
This is the auto generated result class in Model.tt
namespace MvcApplication4.Models
{
using System;
public partial class usp_getDepartment_Result
{
public Nullable<int> Depid { get; set; }
public string DepName { get; set; }
}
}
But I want it to be returned in DepartmentModelView class-
public class DepartmentModelView
{
public Nullable<int> Depid { get; set; }
public string DepName { get; set; }
}
Please suggest how could I do this ?
If the rest of your Code works, you can use the Linq-Select-Projection:
public List<DepartmentModelView> GetDepartmentData()
{
using (Model1Container obj = new Model1Container())
{
return obj.usp_getDepartment().ToList<usp_getDepartment_Result>().Select(m=>new DepartmentModelView{Depid=m.Depid, DepName=m.DepName}).ToList();
}
}
You could implement implicit cast in another partial file for usp_getDepartment_Result:
namespace MvcApplication4.Models
{
public partial class usp_getDepartment_Result
{
static public implicit operator DepartmentModelView(usp_getDepartment_Result input)
{
return new DepartmentModelView
{
Depid = input.Depid,
DepName = input.DepName
};
}
}
}
Then your existing code ought to work.
Use AutoMapper (from Nuget). You can create a map from one class to another and you can do all kinds of manipulation during the mapping operation for cases where it isn't a straightforward copy of properties like this one.
And for simple cases like this one, Automapper will autowire up the conversion when it finds properties with the same names and types.
What Utility or Pattern can be used to solve this Issue? I don't know what can be used to assist with this. Can we use some type of pattern?
If you have the following abstract class:
abstract class Foo
{
function void Something()
{
// Get the media type
}
}
And the following classes that derive from that class:
class Foo1 : Foo
{
public string MyId {get;set}
public string MyFile {get;set}
public TxtFile MyTextFile {get;set}
function void myFooFunction()
{
// Save File to Txt
}
}
class Foo2 : Foo
{
public string MyId {get;set}
public string MyFile {get;set}
public XMLFile MyXMLFile {get;set}
function MyOtherFunction()
{
// Save to XML
}
}
Then in Linq (or Similar) within the repository you do something like this:
var a = (from e in db.myTable
where e.myFileType == "XML"
Select e);
Then we have to map this to the correct object. Like this:
Foo newFoo = FooFactory.CreateFooFor(a.myFileType.ToString())
newFoo.MyId = a.id;
newFoo.MyFile = a.myfile;
newFoo.MyXMLFile = a.xml;
The Factory certainly helps, but how do you do this for multiple "FileTypes" like txt for example? The Fields wouldn't match up!
Do I have to write more code that does the same thing??
I feel like there has to be something that can do this.
First, if myFooFunction and MyOtherFunction are both used to save, you can use the strategy pattern and just define and abstract Save() method to implement in derived classes. You might also look at the Template Method pattern.
Although this isn't exactly a pattern, you might also want to apply the "Pull Up Field" refactoring here and put the MyId and MyFile properties in the parent class.
For creation...
The Builder pattern is similar to the factory, but allows for more complex object creation. I don't know how well it fits this simplified example, but it might fit what you are actually trying to do in your real code. Probably not. I just mention it first because it is the closest to factory in my mind.
There are also the Mapper Pattern and the Data Mapper Pattern. You might encapsulate the mapping in an object and have the factory return a mapper:
FooMapper mapper = FooMapperFactory.CreateFooMapperFor(a.myFileType);
Foo newFoo = mapper.CreateFoo(a);
I believe you could solve your problem using generics. I took the liberty of changing around some code. Would this work?
public abstract class Foo
{
public abstract void Save();
public void Something()
{
// Get the media type
}
}
public class FooText : Foo
{
public string MyId { get; set; }
public string MyFile { get; set; }
public string MyTextFile { get; set; }
public override void Save()
{
// Save File to Txt
}
}
public class FooXml : Foo
{
public string MyId { get; set; }
public string MyFile { get; set; }
public string MyXMLFile { get; set; }
public override void Save()
{
// Save to XML
}
}
public class FooFactory<T> where T : Foo, new()
{
public static T CreateFoo()
{
return new T();
}
}
If you consider using reflection on the data that's returned from the database, or perhaps the Adapter pattern you can set up a dynamic way to map fields to each other. Using reflection (the following is pseudo logic as reflection is kind of messy code to provide):
Get a list of PropertyInfo objects for all public properties from the target type
Get a list of PropertyInfo objects for all public properties from the database type
Compare their names/types to create the mapping
Assign values from the database type, using reflection, to the target type
Something like this will do the trick:
public void AssignAndMapTypes<DatabaseType, TargetType>(DatabaseType db, ref TargetType target)
{
var dbType = db.GetType();
var dbTypeProperties = dbType.GetProperties(System.Reflection.BindingFlags.Public);
var targetType = target.GetType();
var targetTypeProperties = targetType.GetProperties(System.Reflection.BindingFlags.Public);
foreach (var prop in targetTypeProperties)
{
var matchingProp = dbTypeProperties.Where(e => { return (string.Compare(e.Name, prop.Name, true) == 0) && (e.PropertyType == prop.PropertyType) }).FirstOrDefault();
if(matchingProp != null)
{
prop.SetValue(target, matchingProp.GetValue(db, null), null);
}
}
}
I'm using protobuf-net version 2.0.0.640 to serialize some data as shown below.
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public interface ITestMessage
{
}
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class MyOrder : ITestMessage
{
public int Amount { get; set; }
}
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class MyOrderWrapper
{
public MyOrder Order { get; set; }
}
[TestMethod]
public void TestOrderSerialize()
{
var order = new MyOrder() {Amount = 10};
var orderWrapper = new MyOrderWrapper() { Order = order };
using (var file = File.Create("C:\\temp\\order.bin"))
{
Serializer.Serialize<MyOrderWrapper>(file, orderWrapper);
}
}
Now, If I declare an inheritance dependency between 'ITestMessage' & 'MyOrder' via code using:
RuntimeTypeModel.Default[typeof(ITestMessage)].AddSubType(2, typeof(MyOrder));
I get the following error when trying to deserialize my prevously saved data.
"No parameterless constructor found for ITestMessage".
[TestMethod]
public void TestOrderDeserialize()
{
RuntimeTypeModel.Default[typeof(ITestMessage)].AddSubType(2, typeof(MyOrder));
MyOrderWrapper orderWrapper;
using (var file = File.OpenRead("C:\\temp\\order.bin"))
{
orderWrapper = Serializer.Deserialize<MyOrderWrapper>(file);
}
}
Can someone please explain why this would happen when 'MyOrderWrapper' does not reference anything higher than 'MyOrder' in the inheritance hirarchy.
Also, why it works when I explictly include '[ProtoInclude(2, typeof(MyOrder))]' on 'ITestMessage'
Thanks
Basically, that is a breaking change as far as the serializer is concerned - at the wire layer, neither "classes" nor "interfaces" exist, so in terms of storage, this is akin to changing the base-type of the class; when serializing, the root type was MyOrder - and during deserialization the root type is ITestMessage. This will not make it happy.
Basically: you can't do that.
Let us say we have a key with values which are polymorphic in their sense. Consider the next sample project:
public class ToBeSerialized
{
[BsonId]
public ObjectId MongoId;
public IDictionary<string, BaseType> Dictionary;
}
public abstract class BaseType
{
}
public class Type1 : BaseType
{
public string Value1;
}
public class Type2:BaseType
{
public string Value1;
public string Value2;
}
internal class Program
{
public static void Main()
{
var objectToSave = new ToBeSerialized
{
MongoId = ObjectId.GenerateNewId(),
Dictionary = new Dictionary<string, BaseType>
{
{"OdEd1", new Type1 {Value1="value1"}},
{
"OdEd2",
new Type1 {Value1="value1"}
}
}
};
string connectionString = "mongodb://localhost/Serialization";
var mgsb = new MongoUrlBuilder(connectionString);
var MongoServer = MongoDB.Driver.MongoServer.Create(mgsb.ToMongoUrl());
var MongoDatabase = MongoServer.GetDatabase(mgsb.DatabaseName);
MongoCollection<ToBeSerialized> mongoCollection = MongoDatabase.GetCollection<ToBeSerialized>("Dictionary");
mongoCollection.Save(objectToSave);
ToBeSerialized received = mongoCollection.FindOne();
}
}
Sometimes when I try to deserialize it, I get deserialization errors like "Unknown discriminator value 'The name of concrete type'". What am I doing wrong? If every value stores a _t why cannot it map it correctly?
Driver should know about all discriminators to deserialize any class without errors. There are two ways to do it:
1.Register it globally during app start:
BsonClassMap.RegisterClassMap<Type1>();
BsonClassMap.RegisterClassMap<Type2>();
2.Or though the BsonKnownTypes attibute:
[BsonKnownTypes(typeof(Type1), typeof(Type2)]
public class BaseType
{
}
If you will use #1 or #2 your deserialization will work correctly.
You have to register which types inherit from BaseClass before attempting to deserialize them. This will happen automatically if you serialize first, which is probably why the error occurs only sometimes.
You can register derived types using an attribute:
[BsonDiscriminator(Required = true)]
[BsonKnownTypes(typeof(DerivedType1), typeof(DerivedType2))]
public class BaseClass { ... }
public class DerivedType1 : BaseClass { ... }