I was trying the new syntax for C# 9 but I failed to use the Data members.
Documents say the syntax is:
public data class Person { string FirstName; string LastName; }
However, I faced the following compile errors:
CS0116: A namespace cannot directly contain members such as fields or methods
IDE1007: The name 'data' does not exist in the current context.
I have tried other syntaxes and they all worked. So, I am sure that I am using C#-9
Update: This question is suggested as a possible answer.
But, it is not my answer and I believe the accepted answer in that link is wrong. Record types and data members are two different things. Data members are a new way to define immutable types, while the record types are Value Objects.
Documents suggest that in the data member classes you just need to define the properties like private fields, so the Person is equal to:
public data class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
data classes are now called record. They are the same, immutable objects which behave like value types (exhibiting structural or in other words value based equality).
Regarding your code in OP,
public data class Person { string FirstName; string LastName; }
could be rewritten as
public record Person(string FirstName, string LastName);
The above syntax uses Primary Constructor . If you would like to skip the primary constructor, you could also declare records as
public record Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
In either cases, the Properties, FirstName and LastName could be assigned only during initialization (either via Constructor or object initializers) and not after that.
Related
I saw somewhere that with the Go MongoDB driver it is possible to save a document with the order number instead of the field name.
They end up with this in the database:
{
"3": "foo",
"10": 1,
"33": 123456
"107": {
"2": "bar",
"1": "foo"
}
}
I like the idea!
So, I tried to find a way to do the same with the MongoDB C# driver.
I have the code below but I am not sure what I should bring from the protobut-net to get the member order number.
var pack = new ConventionPack();
pack.AddMemberMapConvention("numbered", m => m.SetElementName( WHAT TO PUT HERE ));
ConventionRegistry.Register("numbered", pack, type => true);
The SetElementName takes a string parameter.
How can I grab the order number of a member from protobuf-net?
Something like ...Member.Order.ToString()
I don't know if this whole thing is a great idea but I want to test it.
Thanks
-- UPDATE --
Just to add more information. I am using inheritance for my models to use generics.
[BsonDiscriminator("Base", RootClass = true)]
[DataContract]
public abstract class Base
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[ProtoMember(1)]
public string Id { get; set; }
[BsonDateTimeOptions]
[ProtoMember(2)]
public DateTime CreatedDate { get; private set; } = DateTime.UtcNow;
[BsonDateTimeOptions]
[ProtoMember(3)]
public DateTime UpdatedDate { get; set; } = DateTime.UtcNow;
}
[ProtoContract]
public class Todo : Base
{
[ProtoMember(10)]
public string Title { get; set; }
[ProtoMember(20)]
public string Content { get; set; }
[ProtoMember(30)]
public string Category { get; set; }
}
And I added this line as shown in the protobuf-net documentation:
RuntimeTypeModel.Default[typeof(Base)].AddSubType(42, typeof(Todo));
So with that and what Marc showed to get the member's number, I end up having a custom Convention Class in MongoDB with <T> so I can use it for other objects:
public class NumberedElementNameConvention<T> : ConventionBase, IMemberMapConvention where T : Base
{
public void Apply(BsonMemberMap memberMap)
{
var members = RuntimeTypeModel.Default[typeof(T)].GetFields();
foreach (var member in members)
{
memberMap.SetElementName(member.FieldNumber.ToString());
}
}
}
And the registration of this Convention is done like so:
var pack = new ConventionPack { new NumberedElementNameConvention<Todo>() };
ConventionRegistry.Register("NumberedName", pack, type => true);
After running this I get this error:
Grpc.AspNetCore.Server.ServerCallHandler[6]
Error when executing service method 'CreateOne'.
MongoDB.Bson.BsonSerializationException: The property 'UpdatedDate' of type 'Nnet.Models.Base' cannot use element name '30' because it is already being used by property 'CreatedDate'...
Also, when I run the code below I am expecting to get all members of the Todo object.
var members = RuntimeTypeModel.Default[typeof(Todo)].GetFields();
foreach (var member in members)
{
Console.WriteLine($"{member.FieldNumber}: {member.Member.Name}");
}
However, I am not getting those inherited from the Base object:
❯ dotnet run
10: Title
20: Content
30: Category
The field metadata for protobuf-net is available from the RuntimeTypeModel API, for example:
var members = RuntimeTypeModel.Default[yourType].GetFields();
foreach (var member in members)
{
Console.WriteLine($"{member.FieldNumber}: {member.Member.Name}");
}
The .FieldNumber gives the protobuf field-number, and .Member gives the MemberInfo of the corresponding field or property. You may want to do some level of caching if the m => m.SetElementName( WHAT TO PUT HERE ) is evaluated lots of times for the same m, so you don't perform unnecessary work - but: before you do, just add some logging to the lambda first, and see how often it gets called: if it isn't too often, maybe don't worry about it.
Note that there is also a lookup on MetaType that allows query by MemberInfo:
var member = RuntimeTypeModel.Default[yourType][memberInfo];
Re the edit; in this region:
var members = RuntimeTypeModel.Default[typeof(T)].GetFields();
foreach (var member in members)
{
memberMap.SetElementName(member.FieldNumber.ToString());
}
I believe you're meant to identify the relevant field from memberMap - i.e. in this context you're only talking about one field at the time; I suspect what is happening is that for each member in turn you're changing the element name multiple times, leaving it at the last protobuf field defined.
Separately, there's a complication of inheritance; protobuf-net doesn't implement inheritance in a flat way - instead, the base type is also expected to be a [ProtoContract] and is meant to define a [ProtoInclude(...)] for each derived type; the field numbers are type-specific, meaning: both the base type and the derived type can legally have a field 1. If you need to describe inheritance, and you are determined to use protobuf-net's model, then you would need to handle this; for example, you could use the [ProtoInclude(...)] number as a prefix on each, so Base.Id is "1", and if we imagine that Todo has field 5 in the [ProtoInclude(...)], then Todo.Title could be "5.10".
Alternatively: if you're not actively using protobuf-net: maybe just use your own attribute for the numbers? or there's usually an inbuilt attribute that the serializer you've chosen would use directly.
Okay now! So after a some investigation I end up with this simple way to do it with Marc's help. In MongoDB instead of using attributes to decorate models and its properties, it is possible to use code within BsonClassMap. Within that class I add the foreach loop that Marc provided and the right parameters, we can now have numbers instead names.
On the Client side and Server side it is this same code:
//Base Model ClassMap
BsonClassMap.RegisterClassMap<Base>(cm =>
{
cm.AutoMap();
foreach (var member in RuntimeTypeModel.Default[typeof(Base)].GetFields())
{
cm.MapMember(typeof(Base).GetMember(member.Member.Name)[0])
.SetElementName(member.FieldNumber.ToString())
.SetOrder(member.FieldNumber);
}
});
//Todo Model ClassMap
BsonClassMap.RegisterClassMap<Todo>(cm =>
{
cm.AutoMap();
foreach (var member in RuntimeTypeModel.Default[typeof(Todo)].GetFields())
{
cm.MapMember(typeof(Todo).GetMember(member.Member.Name)[0])
.SetElementName(member.FieldNumber.ToString())
.SetOrder(member.FieldNumber);
}
});
it's a little ugly but you can rework it.
One thing to note is that MongoDB has the control over the Id. In the database anything that represent the object id become _id. Same thing when you insert a new document in the database a _t field is added if you use Discriminator (I am not sure if it's full related). Basically, every member beginning with a underscore is reserved. See the image below after running de code:
You can refer to the question above in the update section to see if this result represent the models with the given orders (it does).
Here is the code I use for insertion and queries:
// INSERT
var client = channel.CreateGrpcService<IBaseService<Todo>>();
var reply = await client.CreateOneAsync(
new Todo
{
Title = "Some Title"
}
);
// FIND BY ID
var todoId = new UniqueIdentification { Id = "613c110a073055f0d87a0e27"};
var res = await client.GetById(todoId);
// FIND ONE BY QUERY FILTER REQUEST
...
var filter = Builders<Todo>.Filter.Eq("10", "Some Title");
var filterString = filter.Render(documentSerializer, serializerRegistry);
...
The last one above it's a query with the number ("10") of the property Title. But it's possible in the same way to query with the property name, like so:
// FIND ONE BY QUERY FILTER REQUEST
...
var filter = Builders<Todo>.Filter.Eq(e => e.Title, "Some Title");
var filterString = filter.Render(documentSerializer, serializerRegistry);
...
What is great with this approach is that these BsonClassMap are called once on the Client or/and Server when they are initiated.
I just realize that this might not be a good idea because it is going to be painful to prevent collision between numbers. The order numbers in the code below is possible:
[BsonDiscriminator("Base", RootClass = true)]
[DataContract]
public abstract class Base
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[ProtoMember(1)]
public string Id { get; set; }
[BsonDateTimeOptions]
[ProtoMember(2)]
public DateTime CreatedDate { get; private set; } = DateTime.UtcNow;
[BsonDateTimeOptions]
[ProtoMember(3)]
public DateTime UpdatedDate { get; set; } = DateTime.UtcNow;
}
[ProtoContract]
public class Todo : Base
{
[ProtoMember(1)]
public string Title { get; set; }
[ProtoMember(2)]
public string Content { get; set; }
[ProtoMember(3)]
public string Category { get; set; }
}
but there is going to be three collisions if the foreach loop runs.
Yeah... :/
This is where Marc's second solution comes in, where you put a prefix... I am going to keep the name convention by default.
Cheers!
My database has a table like this:
Cats
- CatId INT PK
- Name VARCHAR(100)
- FavoriteToy VARCHAR(100)
And my code looks like this:
Cat.cs
public int CatId { get; set; }
public string Name { get; set; }
public Toy FavoriteToy {get; set; }
StaticVariables.cs
public enum Toy { Box, Ball, StuffedAnimal }
In a normalized database I would use a lookup table in the database to store all the toys and then the Cats table would just store a ToyId. But for this situation it's a lot easier to just store the FavoriteToy as a string even though it will be redundant.
The problem is I don't know how to convert a string in the database to an enum in code without creating a second FavoriteToyString property and having FavoriteToy just be a computed that returns the enum derived from FavoriteToyString.
I've heard this might be possible in the current version of entity framework. Is that true? Can you please show me how to do this?
You may use DTO class and automapper to solve your issue :)
Generally, yes a lookup table reference is a better option since your data can comply with referential integrity. That is, No cat records with toys that your Enum hopefully doesn't contain. (Though your Enum would need to be kept in sync with the Toys table.) You can configure EF to store enumerations as a string using a bit of a trick with the mapping:
public class Cat
{
[Key]
public int CatId { get; set; }
public string Name { get; set; }
[Column("FavoriteToy")]
public string FavoriteToyMapped { get; set; }
[NotMapped]
public Toy FavoriteToy
{
get { return (Toy)Enum.Parse(typeof(Toy), FavoriteToyMapped); }
set { FavoriteToyMapped = value.ToString(); }
}
}
The caveat of this approach is that where you might use Linq to Entity to filter on your cat's favorite toy, you need to reference the FavoriteToyMapped value in the query expression because EF/DB won't know what FavoriteToy is.
I.e.
Cats with a favorite toy of "Yarn"
var catsThatLoveYarn = context.Cats.Where(c => c.FavoriteToyMapped == Toys.Yarn.ToString()).ToList();
// not
var catsThatLoveYarn = context.Cats.Where(c => c.FavoriteToy == Toys.Yarn).ToList();
// Will error because EF doesn't map that property.
Once you are working with instances of entities, that the set of entities has been pulled back from the database, you can further access/refine queries with FavoriteToy. Just be cautious and prepared for the unknown field if you use it too early and EF goes and tries to compose a query.
var threeYearOldCats = context.Cats.Where(c => c.Age == 3).ToList();
var threeYearOldCatsThatLoveYarn = threeYearOldCats.Where(c => c.FavoriteToy == Toys.Yarn).ToList();
This is Ok because the .ToList() in the first query executed the EF-to-SQL, so threeYearOldCats is now a local List<Cat> of cat entities, not an IQueryable<Cat>.
I'm in a position where I need to serialize some complex documents into MongoDb, but I can't change the class definition as I don't have control over the source.
However, we need to ensure that callers can still use Linq, so we need to map the class correclty into MongoDb.
Current there are few issues we're faced with:
The _id_ representation is on a nested class.
There are properties with private setters that need to be serialized/ deserialzied.
The shape of the class looks a little like this:
public class AggregateType : AggregateBase
{
public int IntProperty { get; private set; }
public ComplexObject ComplexObjectProperty { get; private set; }
}
With AggregateBase looking like this:
abstract public class AggregateBase
{
public AggregateDetails Details { get; set; }
}
And finally:
public class AggregateDetails
{
public Guid Id { get; set; }
...other properties
}
On the base class AggregateBase, there is a property called Details which contains the Id of the aggregate, which is a Guid. This Id field needs to be mapped to the ObjectId or _id field within a MongoDb document.
I need to be able to serialize the document, forcing the use of the Details.Id as the _id, and have the private setters serialized too.
I've done this with CosmoDb using a custom JsonContractResolver without issue. But the move to MongoDb has proved a little more complex.
It's worth noting that there are many AggregateType classes, all with a different shape. I'd like to find a generic way of serializing them, without having to write lots of specific mappers if possible - much like we do with CosmoDb.
On top of that, we would need this solution to work with the Linq query provider for MongoDb too.
Ive thought a little about this , the only way I can see this working is if you create matching types that will serve as your POCO for inserting into mongodb. Im going to assume you are using the C# Driver for Mongo.
public class AggregateTypeDocument : AggregateBaseDocument
{
public int IntProperty { get; private set; }
public ComplexObject ComplexObjectProperty { get; private set; }
}
abstract public class AggregateBaseDocument
{
public AggregateDetailsDocument Details { get; private set; }
}
public class AggregateDetailsDocument
{
[BsonId]
public Guid Id { get; private set; }
...other properties
}
You will end up replicating the structure but just be appending Document at the end for this example. By no means do you have to conform to this
Now you can mold your types to be more mongo friendly using various attributes.
The next step would be to either in your repository ( or wherever ) to map the types with class definitions you don't have access to to your new mongo friendly ones.
I would suggest AutoMapper for this or plain old instantiation. Now you should be able to safely operate on the collection. See below example for automapper.
var normalAggregateType = new AggregateType();
var client = new MongoClient("yourconnectionstring");
var db = client.GetDatabase("mydatabase");
var collection = db.GetCollection<AggregateTypeDocument>("myaggregatetypes");
var mongoAggregateType = Mapper.Map<AggregateTypeDocument>(normalAggregateType);
collection.InsertOne(mongoAggregateType);
We have been using EF CF for a while in our solution. Big fans! Up to this point, we've been using a hack to support enums (creating an extra field on the model; ignore the enum durring mapping; and map the extra field to the column in the db that we would have used). Traditionally we have been storing our enums as strings(varchars) in the DB (makes it nice and readable). Now with enum support in EF 5 (Beta 2) it looks like it only supports mapping enums to int columns in the DB....Can we get EF 5 to store our enums as their string representation.
Where "Type" is an enum of type DocumentType
public enum DocumentType
{
POInvoice,
NonPOInvoice,
Any
}
I tried to map it using:
public class WorkflowMap : EntityTypeConfiguration<Model.Workflow.Workflow>
{
public WorkflowMap()
{
ToTable("Workflow", "Workflow");
...
Property(wf => wf.Type).HasColumnType("varchar");
}
}
I thought was going to be the magic bullet but..
That just throws:
Schema specified is not valid. Errors: (571,12) : error 2019: Member
Mapping specified is not valid. The type
'Dodson.Data.DataAccess.EFRepositories.DocumentType[Nullable=False,DefaultValue=]'
of member 'Type' in type
'Dodson.Data.DataAccess.EFRepositories.Workflow' is not compatible
with
'SqlServer.varchar[Nullable=False,DefaultValue=,MaxLength=8000,Unicode=False,FixedLength=False]'
of member 'Type' in type 'CodeFirstDatabaseSchema.Workflow'.
Your thoughts?
This is currently not possible. Enum in EF has same limitations as enums in CLR - they are just named set of integer values. Check this article for confirmation:
The EF enum type definitions live in conceptual layer. Similarly to
CLR enums the EF enums have underlying type which is one of Edm.SByte,
Edm.Byte, Edm.Int16, Edm.Int32 or Edm.Int64 with Edm.Int32 being the
default underlying type if none has been specified.
I posted article and related suggestion about this problem. If you want to see this feature in the future please vote for the suggestion.
I hit this problem a few weeks ago. The best I could come up with is a bit hacky.
I have a Gender enum on the class Person, and I use data annotations to map the string to the database and ignore the enum.
public class Person
{
public int PersonID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[Column("Gender")]
public string GenderString
{
get { return Gender.ToString(); }
private set { Gender = value.ParseEnum<Gender>(); }
}
[NotMapped]
public Gender Gender { get; set; }
}
And the extension method to get the correct enum from the string.
public static class StringExtensions
{
public static T ParseEnum<T>(this string value)
{
return (T)Enum.Parse(typeof(T), value, true);
}
}
See this post for full details - http://nodogmablog.bryanhogan.net/2014/11/saving-enums-as-strings-with-entity-framework/
I'd like to know if it is possible to map some database columns to a custom data type (a custom class) instead of the basic data types like string, int, and so on. I'll try to explain it better with a concrete example:
Lets say I have a table where one column contains (text) data in a special format (e.g a number followed by a separator character and then some arbitrary string). E.g. the table looks like this:
Table "MyData":
ID |Title(NVARCHAR) |CustomData (NVARCHAR)
---+----------------+-----------------------
1 |Item1 |1:some text
2 |Item2 |333:another text
(Assume I am not allowed to change the database) In my domain model I'd like to have this table represented by two classes, e.g. something like this:
public class MyData
{
public int ID { get; set; }
public string Title { get; set; }
public CustomData { get; set; }
}
public class CustomData
{
public int ID { get; set; }
public string Text { get; set; }
public string SerializeToString()
{
// returns the string as it is stored in the DB
return string.Format("{0}:{1}", ID, Title);
}
public string DeserializeFromString(string value)
{
// sets properties from the string, e.g. "1:some text"
// ...
}
}
Does entity framework (V4) provide a way to create and use such "custom data types"?
No. Not like that, anyway.
However, you could work around this by:
Write a DB function to do the mapping and then use a defining query in SSDL.
Using one type for EF mapping and another type like you show above, and then projecting.
Add extension properties to your EF type to do this translation. You can't use these in L2E, but it may be convenient in other code.