MongoDB - Class Map not applying - c#

I have a class called Entity, it has an Id property which is of type Guid. Now by default MongoDb serializes this as binary which is not what I want.
I cannot for the life of me get the Id property to serialize as a string.
I have tried both a ConventionPack and a ClassMap.
Entity Definition:
public class Entity : Dictionary<string, object>
{
public Guid Id
{
get { return this.ContainsKey(nameof(Id)) ? Guid.Parse(this[nameof(Id)].ToString()) : Guid.Empty; }
set { this[nameof(Id)] = value; }
}
public string EntityName
{
get { return this.ContainsKey(nameof(EntityName)) ? this[nameof(EntityName)].ToString() : ""; }
set { this[nameof(EntityName)] = value; }
}
}
Class Map Definition:
public class EntityClassMap: BsonClassMap<Entity>
{
public EntityClassMap()
{
AutoMap();
GetMemberMap(x => x.Id).SetSerializer(new GuidSerializer(BsonType.String));
}
}
Class Map registration:
BsonClassMap.RegisterClassMap(new EntityClassMap());
Convention
public class GuidAsStringRepresentationConvention : ConventionBase, IMemberMapConvention
{
public void Apply (BsonMemberMap memberMap)
{
if (memberMap.MemberType == typeof(Guid))
{
memberMap.SetSerializer(new GuidSerializer(BsonType.String));
}
}
}
Convention Pack definition and registration:
var pack = new ConventionPack() {new GuidAsStringRepresentationConvention()};
ConventionRegistry.Register("GuidAsString", pack, it => true);
At a complete loss as to what I am doing wrong.

OK so apparently none of the class map or convention code was wrong the issue was that the Entity object inherited from Dictionary<string, object>
When I stopped inheriting from Dictionary<string, object> it all started working.
What I was trying to achieve was essentially allow both the statically defined properties and any extra properties to be saved.
It turns out, Mongo will automatically map so called "Extra Elements" to a property that implements BsonDocument or IDictionary<string, object> if it uses the convention of being called ExtraElements or is configured in a mapping with MapExtraElementsMember(c => c.Fields);
It also tidies up my entity class which can now use auto properties:
public class Entity
{
public Guid Id { get; set; }
public string EntityName { get; set; }
public Dictionary<string, object> Fields { get; set; }
}
Hope this helps someone as it has taken me about a day and a half to figure this out!

Related

.NET EF Core - The specified field could not be found for property

I'm trying make a migration in .NET EF Core but still getting error:
The specified field '_tags' could not be found for property Document<FinancialReport>.Tags
I need to save string array (tags) as one string separated by comma to the database. I found exactly what I need (https://kimsereyblog.blogspot.com/2017/12/save-array-of-string-entityframework.html), but when I try to make migration I got the error.
My main class in which I need to do that looks this:
public class DocumentBase<T>
{
private string? _tags { get; set; }
public int Id { get; set; }
[NotMapped]
public string[] Tags
{
get { return _tags?.Split(',') ?? Array.Empty<string>(); }
set { _tags = value?.ToArray().Any() ?? false ? string.Join(",", value.ToArray()) : null; }
}
public T? Data { get; set; }
}
And then I setting _tags property as backing field in overridden method OnModelCreating() in dataContext:
public class DataContext : DbContext
{
public DbSet<Document<FinancialReport>> FinancialReports { get; set; }
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Document<FinancialReport>>()
.Property(p => p.Tags)
.HasField("_tags");
}
}
I tried use BackingField attribute for Tags property, make _tags a field (remove getter & setter) instead of property, but it also didn't help. Any ideas what am I missing?
When using formatted properties like this, I will used a mapped property for the raw data value then a [NotMapped] formatted value:
[Column("Tags")]
public string? TagsValue { get; set; }
[NotMapped]
public string[] Tags
{
get { return TagsValue?.Split(',') ?? Array.Empty<string>(); }
set { TagsValue = value?.ToArray().Any() ?? false ? string.Join(",", value.ToArray()) : null; }
}
The do away with the extra OnModelCreating configuration.
The care needed with [NotMapped] fields is to ensure they don't end up in any Linq expressions that would go to SQL.

Is it possible to have extra (ignored) properties in C#?

I have a repository for a DocumentDb database. My documents all have a set of common properties so all documents implement the IDocumentEntity interface.
public interface IDocumentEntity {
[JsonProperty("id")]
Guid Id { get; set; }
[JsonProperty("documentClassification")]
DocumentClassification DocumentClassification { get; set; }
}
public class KnownDocument : IDocumentEntity {
[JsonProperty("id")]
Guid Id { get; set; }
[JsonProperty("documentClassification")]
DocumentClassification DocumentClassification { get; set; }
[JsonProperty("knownProperty")]
string KnownProperty { get; set; }
}
public class BaseDocumentRepository<T> where T : IDocumentEntity {
public Set(T entity) {
// ... stuff
}
}
This works fine with a KnownDocument where I know all of the properties. But, of course, what's great about a Document Db is that I don't need to know all of the properties (and in many cases I won't).
So my client submits something like this-
{unknownProperty1: 1, unknownProperty2: 2}
And I want to upsert this using my document repository.
public OtherDocumentService() {
_otherDocumentService = new OtherDocumentRepository();
}
public UpsertDocument(dynamic entity) {
entity.id = new Guid();
entity.documentClassification = DocumentClassification.Other;
_otherDocumentRepository.Set(entity);
}
But I get an InvalidCastException from dynamic to IDocumentEntity. I assume it's because of the extra properties that exist on the dynamic object but not on the IDocumentEntity interface?
What I'm trying to do is leave my document entities open to be dynamic, but rely on a few properties being there to maintain them.
Entity parameter passed to the UpsertDocument should explicitly implement IDocumentEntity in order do make the code works, it is not enough just have a Id property.
Some options:
1) Proxy may be applied:
public class ProxyDocumentEntity : IDocumentEntity
{
public dynamic Content { get; private set; }
public ProxyDocumentEntity(dynamic #content)
{
Content = #content;
}
public Guid Id
{
get { return Content.Id; }
set { Content.Id = value; }
}
}
... using
public void UpsertDocument(dynamic entity)
{
entity.Id = new Guid();
repo.Set(new ProxyDocumentEntity(entity));
}
The stored document will have nested Object property, which may be not acceptable
2)There is a lib https://github.com/ekonbenefits/impromptu-interface which creates a proxy dynamically
and does not make extra property like solution above.
Drawback will be in performance.
Technically it could be 2 methods:
public void UpsertDocument(IDocumentEntity entity){...}
public void UpsertDocument(dynamic entity){...}
so the first (fast) will work for the objects which implement IDocumentEntity and second(slow) for the rest of the objects.
But this is a speculation a bit , as I dunno the details of the whole code base of the project you have.
If you have some flexibility as to how to name those dynamic properties, you could stuff them into a Dictionary property on your object:
public Dictionary<string, dynamic> extra { get; set; }

How to make Entity Framework code first serialize a property list

I have a class to save to database using codefirst ef, as follows
public class Book
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int BookId { get; set; }
public string Name { get; set; }
public Dictionary<string, object> Properties { get; set; }
}
it works fine, except it does not generate any column for the Dictionary. I want it to make a varchar(max) and I want it to convert my dictionary into JSON and write the JSON string into the column.
How is the easiest way to do that? I suppose I can add a dummy string like this...
public string PropertiesJson;
public Dictionary<string, object> Properties
{
get { return JsonConvert.DeserializeObject<Dictionary<string, object>>(PropertiesJson); }
set { PropertiesJson = JsonConvert.SerializeObject(value); }
}
And the string will get written to the database. However this has several problems. The string might get stale if someone adds to the dictionary, and the logic isn't properly lazy, etc.
And I want to use the paradigm for the columns in several tables, so I'd like to have a simple plug in type. For example,
public PropertyDictionary Properties { get; set; }
in which PropertyDictionary is a type with which EF will automatically do the right thing. is that the best way? How can I do that?
First, your code :
public string PropertiesJson;
public Dictionary<string, object> Properties
{
get { return JsonConvert.DeserializeObject<Dictionary<string, object>>(PropertiesJson); }
set { PropertiesJson = JsonConvert.SerializeObject(value); }
}
... cannot work in my opinion because you don't set the value each time you add or edit a key/value in your dictionary.
Instead, what I would try is to have a string property so it will be persisted. First problem solved.
Then, I would manually bind the get on the Dictionary like this:
protected string _propertiesJson;
public string PropertiesJson
{
get {
return JsonConvert.SerializeObject(Properties);
}
set {
_propertiesJson = value;
Properties = JsonConvert.DeserializeObject<Dictionary<string, object>>(value);
}
}
protected Dictionary<string, object> _properties;
public Dictionary<string, object> Properties
{
get
{
if (_properties != null)
{
return _properties;
}
else if (string.IsNullOrEmpty(_propertiesJson))
{
return JsonConvert.DeserializeObject<Dictionary<string, object>>(_propertiesJson);
}
else
{
return null;
}
}
set
{
_properties = value;
}
}
Note that this code was not tested and is a start of something I would try.
The string should only be used by Entity Framework to persist your dictionary. You should not try to edit both. The general idea here is that you use the dictionary and let Entity Framework do the job.
It's a start.
Maybe this answer will be not fine for you but I would create the DataAccess class which will be the same as database schema. So I would introduce it like that:
public class Book
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int BookId { get; set; }
public string Name { get; set; }
public string Properties { get; set; }
}
And for my domain classes I would use the Dictionary collection.
Before I would save data I would use for example the AutoMapper to serialize the collection.
This solution for me would be the clearest. Data access will be close to what you have in DB and the domain model will be easy to use in domain logic.

How to make a derived class use a property of a base class?

The general idea starts from creating a control, which would work with any database object.
The database is accessed via LINQ to SQL, and database objects are auto-generated from the existing database.
To make the control work with any of these objects, I use a base interface, which all the auto-generated objects inherit from. So the control actually works with that interface.
This interface at first should provide:
an ability to get the identifier (primary key) of an object
an expression which would return this identifier, if compiled and called via an instance of an object
So this looks like:
public interface IEntity<TEntity, TEntityId>
where TEntity : IEntity<TEntity, TEntityId>
where TEntityId : IEquatable<TEntityId>
{
Expression<Func<TEntity, TEntityId>> GetIdExpression();
TEntityId EntityId { get; }
}
Then a database object's class definition would look like:
//let it be the auto-generated part:
public partial class Entity1
{
public Guid UId { get; set; }
}
//and the manual part:
public partial class Entity1 : IEntity<Entity1, Guid>
{
public Expression<Func<Entity1, Guid>> GetIdExpression()
{
return (Expression<Func<Entity1, Guid>>)(se => se.UId);
}
public Guid EntityId
{
get { return this.UId; }
}
}
And we can test it:
var e1 = new Entity1();
e1.UId = Guid.NewGuid();
Console.WriteLine(e1.UId);
Console.WriteLine(e1.GetIdExpression().Compile()(e1));
The output of these two lines is equal. It's OK.
The next idea was, that the lion share of all database objects have int identifier with the name Id. And it would be much better to avoid editing every of them, i.e. not to write one and the same code implementing IEntity<TEntity, TEntityId>.
As far as I need to specify the implementation of this interface, I need a class.
For now I ended up with
public class IntEntity<TEntity> : IEntity<TEntity, int>
where TEntity : IntEntity<TEntity>
{
public int Id { get; set; }
public Expression<Func<TEntity, int>> GetIdExpression()
{
return (Expression<Func<TEntity, int>>)(e => e.Id);
}
public int EntityId
{
get { return this.Id; }
}
}
Then the database object class would derive from it:
//let it be the auto-generated part:
public partial class Entity2
{
public int Id { get; set; }
}
//and the manual part:
public partial class Entity2 : IntEntity<Entity2>
{
}
Already now it's obvious, that Id in Entity2 does hide the Id in IntEntity<TEntity>. This is also proved by the same test:
var re = new Entity2();
re.Id = 5;
Console.WriteLine(re.Id); //5
Console.WriteLine(re.GetIdExpression().Compile()(re)); //0
So this solution is wrong...
I also tryed to make IntEntity<TEntity> abstract, but obviously failed, as in Entity2 Id is defined in the auto-generated part, and I could not make it override the abstract Id of the base class.
Is there anything I can do to make all database objects with int Id use the Id from the base IntEntity<TEntity> class. Or, probably, vice versa...
The main point here is that the expression returned by GetIdExpression should have the form e => e.Id, as this expresion would later be used in LINQ to SQL queries, so LINQ should be able to translate it to a correct SQL query.
I'm not sure that's exactly what you want, but you can try to do so
public abstract class IntEntity<TEntity> : IEntity<TEntity, int>
where TEntity : IntEntity<TEntity>
{
public abstract Expression<Func<TEntity, int>> GetIdExpression();
public abstract int EntityId
{
get;
}
}
public partial class Entity2
{
public int Id { get; set; }
}
//and the manual part:
public partial class Entity2 : IntEntity<Entity2>
{
public override int EntityId
{
get { return this.Id; }
}
public override Expression<Func<Entity2, int>> GetIdExpression()
{
return (Expression<Func<TEntity, int>>)(e => e.Id);
}
}
in fact without override virtual you can't get access to field in derive class from method in base class
in sample above in fact equals your first sample
UPDATE
as workaround you can add method SetId that set id in derive and base class like this
public class IntEntity<TEntity> : IEntity<TEntity, int>
where TEntity : IntEntity<TEntity>
{
public int Id { get; set; }
public Expression<Func<TEntity, int>> GetIdExpression()
{
return (Expression<Func<TEntity, int>>)(e => e.Id);
}
public int EntityId
{
get { return this.Id; }
}
}
//let it be the auto-generated part:
public partial class Entity2
{
public int Id { get; set; }
}
//and the manual part:
public partial class Entity2 : IntEntity<Entity2>
{
public void SetId(int id)
{
Id = id;
base.Id = id;
}
}
var re = new Entity2();
re.SetId(5);
Console.WriteLine(re.Id); //5
Console.WriteLine(re.GetIdExpression().Compile()(re)); //5

Fluent NHibernate PropertyNotFoundException for Auto Property

I'm trying to get Fluent NHibernate to map a collection for me. My class definitions are as follows:
public abstract class Team
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
public class ClientTeam : Team
{
public virtual IEnumerable<Client> Clients { get; set; }
}
public class Client
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual string Identifiers { get; set; }
}
My mappings:
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
Table("Team");
Id(x => x.Id).GeneratedBy.Assigned();
Map(t => t.TeamName);
}
}
public class ClientTeamMap : SubclassMap<ClientTeam>
{
public ClientTeamMap()
{
HasMany(t => t.Clients);
}
}
public class ClientMap : ClassMap<Client>
{
public ClientMap()
{
Table("Client");
Id(c => c.Id);
Map(c => c.Name);
Map(c => c.Identifiers);
}
}
I've built a unit test that instantiates a team and then attempts to persist it (the test base has dependency configuration, etc. in it):
public class TeamMapTester : DataTestBase
{
[Test]
public void Should_persist_and_reload_team()
{
var team = new ClientTeamDetail
{
Id = Guid.NewGuid(),
TeamName = "Team Rocket",
Clients = new[]
{
new ClientDetail {ClientName = "Client1", ClientIdentifiers = "1,2,3"}
}
};
using (ISession session = GetSession())
{
session.SaveOrUpdate(team);
session.Flush();
}
AssertObjectWasPersisted(team);
}
}
When I run the test, I get this error:
SetUp : FluentNHibernate.Cfg.FluentConfigurationException : An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
Database was not configured through Database method.
----> NHibernate.MappingException: Could not compile the mapping document: (XmlDocument)
----> NHibernate.PropertyNotFoundException : Could not find field '_clients' in class 'ClientTeam'`
I've looked through the NHibernate documentation and done some google searching, but I can't find anything that appears to address this issue. The documentation for Fluent NHibernate's Referencing methods explicitly uses auto properties, so I'm sure that's not the issue.
Why might NHibernate think that _clients is the field it should map in this case?
And the reason turns out to be: Conventions.
The Fluent mappings were set up to try to enforce read-only collection properties, by requiring a backing field. The ICollectionConvention in question:
public class CollectionAccessConvention : ICollectionConvention
{
public void Apply(ICollectionInstance instance)
{
instance.Fetch.Join();
instance.Not.LazyLoad();
instance.Access.CamelCaseField(CamelCasePrefix.Underscore);
}
}
which requires that collection backing fields be camelCased and start with an underscore.

Categories