Get _id of an inserted document in MongoDB when using BsonClassMap? - c#

Before anyone marks this question as a duplicate of Get _id of an inserted document in MongoDB? please read the whole question.
I am developing a ASP.NET Core API app with MongoDB driver.
The problem I am facing is that after inserting a document in the database the Id property of the Post class is not assigned the id generated by MongoDB.
I found few questions where they solved it using the annotations/attributes in the class but, I am developing a pattern where domain classes don't have any persistence knowledge.
I have created the entity class without annotations:
public class Post
{
public string Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
}
And defined mapping using BsonClassMap:
BsonClassMap.RegisterClassMap<Domain.Entities.Post>(x =>
{
x.AutoMap();
x.MapIdField(x => x.Id)
.SetSerializer(new StringSerializer(BsonType.ObjectId))
.SetIgnoreIfDefault(true);
});
Here's is the code:
public async Task Create(Domain.Entities.Post entity, CancellationToken cancellationToken)
{
async Task Create()
{
await Post.InsertOneAsync(entity);
}
await Create();
var e = entity;
}
When I inspect the entity the Id property is null.
So, how do I get id when using the BsonClassMap?

Currently, your code knows how to read Ids when they come as ObjectId from the database but still needs help when it comes to generating ids on the client side,
The missing part is SetIdGenerator method invocation, try:
BsonClassMap.RegisterClassMap<Post>(x =>
{
x.AutoMap();
x.MapIdField(x => x.Id)
.SetIdGenerator(StringObjectIdGenerator.Instance)
.SetSerializer(new StringSerializer(BsonType.ObjectId))
.SetIgnoreIfDefault(true);
});
Now ObjectIds will be generated and turned into string on the client side so you should be able to see them when you hover over e variable.
Details here.

Related

EF Core Update Foreign Key in Disconnected Scenario

I have a model class in my Blazor Server project as follows
public class TestModel
{
public Client Client { get; set; }
... other value types follow etc.
}
My Client class is defined as below:
public class Client
{
public string ClientID { get; set; }
public string ClientName { get; set; }
public ICollection<SetupModel> SetupModels { get; set; }
}
Client loads from a view and will never be updated by my application (users change its value using a dropdown). I am defining this as a one to many relation in OnModelCreating as each Client can have many TestModels.
modelBuilder.Entity<Client>().HasMany(x => x.SetupModels).WithOne(x=> x.Client);
The ClientID shadow property of TestModel need to be updated whenever the user changes the Client in a dropdown. I am using local storage to handle drafts so it is not possible to use traditional entity tracking, as I often need to parse json back into the relevant classes. My first attempt was to use Entry.CurrentValues.SetValues:
using var context = Factory.CreateDbContext();
var existing = await context.SetupForm.SingleOrDefaultAsync(x => x.ID == model.ID);
context.Entry(existing).CurrentValues.SetValues(model);
await context.SaveChangesAsync();
SetValues doesn't work for reference properties, however, so I am stuck unable to update the foreign key on my TestModel in this disconnected scenario. I have spent several hours on this today and think I am overlooking something simple. How should I handle this scenario?

.NET Entity Framework - duplicate entities

Usefull Context
I currently have two entities that look as below.
MovieSerie
public class MovieSerie
{
[Key]
public Guid MovieSerieId { get; set; }
[Required]
[MaxLength(128)]
public string Title { get; set; }
[Required]
[MaxLength(256)]
public string Description { get; set; }
public virtual ICollection<Movie> Movies { get; set; }
}
Movie
public class Movie
{
[Key]
public Guid MovieId { get; set; }
[Required]
[MaxLength(128)]
public string Title { get; set; }
public virtual MovieSerie MovieSerie { get; set; }
}
I have removed some properties that were unused so far so the example is a bit more readable.
These entities have a one-to-many relationship because a MovieSerie contains multiple movies but a movie can only belong to one MovieSerie.
The problem
When I am trying to make a new movie from Postman by providing an EXISTING MovieSerie, I am getting an exception. The exception looks as below.
Duplicate entry '\xA9\xCE\x0E\x1E\x9A\xAE\xA2G\x91<\xE6\xE3-\x88C\xE9' for key 'movieseries.PRIMARY'
So I figured out that it is trying to make a new MovieSerie when I am providing a MovieSerie object. The raw JSON from the request that I am trying to send from Postman looks like below.
{
"MovieId" : "6aa8c134-689c-45e2-bf60-cd0eb5473cc2",
"Title" : "TestMovie",
"MovieSerie" : {
"movieSerieId": "1e0ecea9-ae9a-47a2-913c-e6e32d8843e9",
"title": "Harry Potter",
"description": "This contains the Harry Potter serie"
}
}
The POST method to save the movie is shown below.
[HttpPost]
public async Task<ActionResult<Movie>> PostMovie(Movie movie)
{
if (movie == null)
{
return BadRequest("No movie object provided");
}
else if (movie.MovieSerie != null)
{
if (!_validator.MovieSerieExists(movie.MovieSerie.MovieSerieId))
{
return BadRequest("The movie serie does not exists in the database");
}
}
_context.Movies.Add(movie);
await _context.SaveChangesAsync();
return CreatedAtAction("GetMovie", new { id = movie.MovieId }, movie);
}
Could someone give me any insight into what I am doing wrong? Why is it trying to make a new entity while it already exists? What should I change to get the wished behavior?
I tried to provide all information required, however, let me know if I missed something.
EDIT ADDED DBCONTEXT
modelBuilder.Entity<MovieSerie>(entity =>
{
entity.HasKey(movieSerie => movieSerie.MovieSerieId);
entity.Property(movieSerie => movieSerie.Title).IsRequired();
entity.Property(movieSerie => movieSerie.Description).IsRequired();
entity.HasMany(ms => ms.Movies)
.WithOne(m => m.MovieSerie);
});
modelBuilder.Entity<Movie>(entity =>
{
entity.HasKey(movie => movie.MovieId);
entity.Property(movie => movie.Title).IsRequired();
entity.HasOne(m => m.MovieSerie)
.WithMany(s => s.Movies);
});
This is what happens when passing entities between server and client in ASP.Net. When your DbContext is lifetime scoped to a request, the entities are loaded by a DbContext and passed to the view, but then what you pass back on the Post call is a JSON object that is deserialized into an entity class definition. On this request, neither the Movie or it's associated related entities are tracked by the DbContext.
When you tell the Post's DbContext to Add the movie, any child entities on that movie will be treated as new entities as well, resulting in duplicate records.
How to avoid this:
Option 1: Use ViewModels to avoid confusing data coming from views with entities. (Data state) This is always my recommended option. This avoids confusion about what objects you are dealing with, and also means you can reduce the amount of data being sent over the wire. As entities get larger, sending entities back and forth means larger payloads for fields your view doesn't need. ViewModels can be populated to serve just the fields that the view will interact with. Automapper can help largely with turning entity graphs into ViewModels with it's ProjectTo method.
So if we had a view for creating a Movie (Movie/Create) and that view listed a the movie series to choose from, it might search/fetch series:
[Serializable]
public class MovieSeriesSummaryViewModel
{
public Guid MovieSeriesId { get; set; }
public string Name { get; set; }
}
Then when the controller goes to search/retrieve those series to choose from:
var series = _context.MovieSeries
// .Where(x => [search criteria...])
.ProjectTo<MovieSeriesSummaryViewModel>(config)
.ToList();
or
var series = _context.MovieSeries
// .Where(x => [search criteria...])
.Select( x = > new MovieSeriesSummaryViewModel
{
MovieSeriesId = x.MovieSeriesId,
Name = x.Name
}).ToList();
a PostMovie action accepts a PostMovieViewModel:
[Serializable]
public class PostMovieViewModel
{
public string MovieName { get; set; }
public Guid? MovieSeriesId { get; set; }
// ...
}
The create movie view model only needs to pass the series ID (if applicable) and the required fields to create a new movie. From there we associate the series from the DbContext when creating our new Movie:
[HttpPost]
public async Task<ActionResult<PostMovieViewModel>> PostMovie(PostMovieViewModel movieVM)
{
var movieSeries = movieVM.MovieSeriesId.HasValue
? _context.MovieSeries.Single(x => x.MovieSeriesId == movieVM.MovieSeriesId.Value)
: null;
var movie = new Movie
{
Name = movieVM.Name,
MovieSeries = movieSeries
};
_context.Movies.Add(movie);
await _context.SaveChangesAsync();
}
The key point here is that we fetch the existing series from the Context to associate to the new movie. Fetching entities by ID is quite fast and serves as a meaningful validation that the data we passed in is complete.
Option 2: Re-associate all references. The underlying problem with passing deserialize objects and treating them as entities is that the DbContext isn't tracking them. There are 2 ways you can fix this, either tell the DbContext to track them, or replace the references with tracked objects.
2a - Replacing references
[HttpPost]
public async Task<ActionResult<Movie>> PostMovie(Movie movie)
{
if (movie.MovieSeries != null)
{
var existingMovieSeries = _context.MovieSeries
.Single(x => MovieSeriesId == movie.MovieSeries.MovieSeriesId);
movie.MovieSeries = existingMovieSeries; // Replace the reference.
}
_context.Movies.Add(movie);
await _context.SaveChanges();
}
This still potentially means going to the DB for all references, and forgetting to will result in silent duplication issues.
2b - Track related entities. This one I saved for last as it can seem simple, but can trip you up...
[HttpPost]
public async Task<ActionResult<Movie>> PostMovie(Movie movie)
{
if (movie.MovieSeries != null)
_context.Attach(movie.MovieSeries);
_context.Movies.Add(movie);
await _context.SaveChanges();
}
That looks simple, and would work most of the time, but if the DbContext is already tracking that movie series for any reason, the Attach method will fail. This is an error that could appear intermittently at runtime depending on the particular actions/data combinations. (I.e. updating 2 movies /w same series or conditionally calling a method that loads that series) The proper check would be:
[HttpPost]
public async Task<ActionResult<Movie>> PostMovie(Movie movie)
{
if (movie.MovieSeries != null)
{
var existingMovieSeries = _context.MovieSeries.Local
.SingleOrDefault(x => x.MovieSeriesId == movie.MovieSeries.MovieSeriesId);
if (existingMovieSeries == null)
_context.Attach(movie.MovieSeries);
else
movie.MovieSeries = existingMovieSeries;
}
_context.Movies.Add(movie);
await _context.SaveChanges();
}
Checking MovieSeries.Local checks to see if the DbContext is tracking the series. (without hitting the DB) If not, we can attach it. If it is, we need to replace the reference. This can be a lot of boiler plate code to put in for every reference on a new object. When attaching entities coming from a view, it is also important not to ever set the entity state for that entity to Modified without first verifying the data is valid. (Which would require loading the entity first anyway) Doing so could allow users to alter data in ways you don't intend as setting an entity to Modified will update all fields on that entity. (Where loading an entity and then copying across values means only those values you change will be updated)
Your problem is that you are passing the whole movie serie object. This is not something you should do. The idea of relational databases is to, as the name suggest, relate tables. This relationships are done using keys (foreign keys).
In your particular case, you need to define a foreign key column in your Movie table, to relate it to MovieSeries, as follows:
public class Movie
{
[Key]
public Guid MovieId { get; set; }
public int MovieSerieId {get; set; }
[Required]
[MaxLength(128)]
public string Title { get; set; }
[ForeignKey("MovieSerieID")]
public virtual MovieSerie MovieSerie { get; set; }
}
As you can see, im specifying that the MovieSerieID attribute is a foreign key. The virtual MovieSerie attribute is used by EF to get all the details of your foreign key.
Now, you can create your movie passing only the MovieSerieid, as follows:
{
"MovieId" : "6aa8c134-689c-45e2-bf60-cd0eb5473cc2",
"Title" : "TestMovie",
"MovieSerieId": "1e0ecea9-ae9a-47a2-913c-e6e32d8843e9"
}

Wrong column name weird issue in Entity Framework

I have been facing this problem some time, and to be honest I am myself confused with it so please excuse me if i don't succeed explaining it as I should.
I am trying to insert some data into a Table called CommunicationAttachment which is related as One to Many relationship with Communication; every communication could have many attachments.
The thing is that I get:
UpdateException: Invalid Column Name: "Communication_CommunicationId
when I try to insert list of attachments.
And please note that I am using the repository pattern but I even tried the normal way and the issue wasn't fixed.
I tried tracing the transaction that happens on the database and I figured out that it sends Communication_CommunicationId with the Insert statement, yet there is no such column. I am pretty sure I didn't send such a column.
Here is my code (this is happening when adding new Communication); first of all I call CasefileAttachments to make copies from them, and Communications are related to CaseFiles:
public List<CorrespondenceAttachment> GetCaseFileAttachments(List<Guid> CorrespondenceAttachmentIds)
{
List<CorrespondenceAttachment> originalAttachments = new List<CorrespondenceAttachment>();
foreach (var item in CorrespondenceAttachmentIds)
{
var attachment = QueryData.Query<CorrespondenceAttachment>().Where(att => att.CorrespondenceAttachmentID == item).FirstOrDefault();
originalAttachments.Add(attachment);
}
return originalAttachments;
}
Then I copy the CaseFileAttachments and create new objects of CommunicationAttachments :
public List<CommunicationAttachment> CopyCaseFileAttachmentsToCommunication(List<CorrespondenceAttachment> originalAttachments,Guid communicationId)
{
var communicationAttachments = new List<CommunicationAttachment>();
if (originalAttachments.Any())
{
foreach (var attachmentRef in originalAttachments)
{
var CommunicationAttachmentId = Guid.NewGuid();
communicationAttachments.Add(new CommunicationAttachment()
{
CommunicationAttachmentId = CommunicationAttachmentId,
DmsFileId = CommunicationAttachmentId,
CommunicationId = communicationId,
AttachmentTitle = attachmentRef.AttachmentTitle,
MimeType = attachmentRef.MimeType,
NewVersionID = null,
UploadDate = DateTime.Now,
Size = attachmentRef.Size,
Version = "0001",
AttachmentsGroupId = attachmentRef.AttachmentsGroupId,
DocumentId = attachmentRef.DocumentId,
RelativePath = attachmentRef.RelativePath,
Extension = attachmentRef.Extension,
AttachmentSubject = attachmentRef?.AttachmentSubject,
ExternalContactID = attachmentRef?.ExternalContactID,
AttachmentNumber = string.IsNullOrEmpty(attachmentRef?.AttachmentNumber) ? null : attachmentRef.AttachmentNumber,
TemplatedmsId = attachmentRef.TemplatedmsId,
State = eSense.Framework.Data.ObjectState.Added,
});
}
}
return communicationAttachments;
}
and the methods above are called something like this way:
public void AddNewCommunication(CommunicationDto communicationDto)
{
var communication = communicationDto
if (communicationDto.CommunicationAttachmentIdList.Any())
{
caseFileAttachments = GetCaseFileAttachments(communicationDto.CommunicationAttachmentIdList);
if (caseFileAttachments.Any())
{
commAttachments = CopyCaseFileAttachmentsToCommunication(caseFileAttachments, communication.CommunicationId);
}
}
communication.Attachments = commAttachments;
Save(communication)
}
So what could be the problem that I get a wrong column name?
Here is the relation between Communication and CommunicationAttachment
Note I added only the Important fields so don't bother if the declaring does not match the entity
Communication Entity:
public class Communication : BaseEntity
{
public Communication()
{
Attachments = new HashSet<CommunicationAttachment>();
}
[Key]
public Guid CommunicationId { get; set; }
public string Subject { get; set; }
public string CommunicationNumber { get; set; }
public virtual ICollection<CommunicationAttachment> Attachments { get; set; }
public DateTime DateCreated { get; set; }
public Guid? PreviousCommunicationId { get; set; }
[ForeignKey("PreviousCommunicationId")]
public virtual Communication PreviousCommunication { get; set; }
}
CommunicationAttachment Entity:
public class CommunicationAttachment : AttachmentBaseWithDelegation<Guid>
{
public override Guid PrimaryId
{
get
{
return this.CommunicationAttachmentId;
}
}
public CommunicationAttachment()
{
}
[Key]
public Guid CommunicationAttachmentId { get; set; }
private string _attachmentNumber;
public string AttachmentNumber { get; set; }
[ForeignKey("NewVersionID")]
public virtual CommunicationAttachment CaseFileAttachmentNewerVersion { get; set; }
public Guid CommunicationId { get; set; }
[ForeignKey("CommunicationId")]
public virtual Communication Communication { get; set; }
}
Sorry if you found it hard to understand my question I myself is confused!
Thanks in advance.
This is typically a case where a relationship between entities is not set up correctly. It would appear that EF should be resolving this relationship by convention if Communication's PK is "CommunicationId".
I notice that you've commented out a line to set the CommunicationId on the new entity:
//CommunicationId = communicationId,
What fields are in the CommunicationAttachment? is there a CommunicationId? Is there a Communication navigation property? What configuration settings are you are using?
For example, with fluent configuration I would have something like:
(CommunicationEntityConfiguration)
If CommunicationAttachment has a navigation property back to Communication and a FK field called CommunicationId...
HasMany(x => x.CommunicationAttachments)
.WithRequired(x => x.Communication)
.HasForeignKey(x => x.CommunicationId);
If the attachment entity has a navigation property without a mapped FK in the entity...
HasMany(x => x.CommunicationAttachments)
.WithRequired(x => x.Communication)
.Map(x => x.MapKey("CommunicationId"));
If the attachment entity does not have a navigation property, but has a FK in the entity...
HasMany(x => x.CommunicationAttachments)
.WithRequired()
.HasForeignKey(x => x.CommunicationId);
Or lastly if the attachment entity does not have a navigation property nor a mapped FK...
If the attachment entity does not have a navigation property, but has a FK in the entity...
HasMany(x => x.CommunicationAttachments)
.WithRequired()
.Map(x => x.MapKey("CommunicationId"));
I am a big fan of explicit mapping over convention as it is very clear as to what maps to what, and how, in order to resolve potential mapping conflicts. If the rest of the similar relations seem to be working and just this one is playing up, I'd be looking for possible typos in the field names. With a mapped collection like above, setting a Communcation.CommunicationAttachments.Add(attachment) should be setting the FK / related entity on the attachment without having to explicitly set the FK or related entity manually.
One additional note:
From your example I see you are setting Primary Keys manually client-side using Guid.NewGuid(). It is generally better to allow the database to manage PK generation and let EF manage FK assignment to ensure that related entities get the FKs to newly inserted rows automatically. Rather than SQL's NewId() or using Guid.NewGuid(), it is advisable to use sequential UUIDs. In SQL Server this is NewSequentialId(). For client-side setting, you can reproduce the sequential UUID pattern either with a system DLL call to get the ID, or a simple re-hash of the Guid bytes. see: Is there a .NET equalent to SQL Servers newsequentialid()
The GUIDs still carry the same uniqueness, the bytes are simply arranged to be more sequential and practical for database indexing to reduce page fragmentation. The downside is that IDs are more predictable. Depending on your database engine you might want to customize the algorithm based on whether the database is optimized for indexing on the lower-order or high-order bytes.
When using GUIDs for database, sequential or otherwise, you should ensure you have a scheduled index maintenance job on the database. With sequential IDs this job will run faster and keep the index tables more compact.

Unable to map List<> navigational properties using OData, EF Core and AutoMapper

I'm currently writing an ASP .NET Core API utilizing OData for querying, and Entity Framework to talk to the database.
I want to separate the domain objects from the DTOs sent to the user, so have also started to use AutoMapper to translate entity framework query results to DTOs I have created.
At this point (while I'm testing), my DTOs and domain objects are the same - just public getter/setter properties. Examples of the DTOs are as follows:
public class NoteDTO
{
public int Id { get; set; }
public string Body { get; set; }
public string Conclusion { get; set; }
public string Title { get; set; }
public ManagerDTO Manager { get; set; }
}
public class ManagerDTO
{
public int Id { get; set; }
public virtual List<ProductDto> Products { get; set; }
}
public class ProductDto
{
public int Id { get; set; }
}
I also have a test method in my NotesController for fetching notes (again, using OData) which is as follows:
[HttpGet]
[EnableQuery]
public IQueryable<NoteDTO> GetMeeting()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Note, NoteDTO>();
cfg.CreateMap<Product, ProductDto>();
cfg.CreateMap<Manager, ManagerDTO>()
.ForMember(md => md.Products, conf => conf.MapFrom(m => m.Products));
});
return _context.Notes.ProjectTo<NoteDTO>(config);
}
I then try and hit my API with the following query:
https://localhost:5001/api/Notes?$select=Id,Body,Conclusion&$top=5&$expand=Manager($select=Id)
However, this fails, and in amongst the stack trace, I'm given the following error message:
System.ArgumentException: Expression of type 'System.Collections.Generic.IEnumerable`1[System.Tuple`3[TestEntityFramework.DataObjects.ProductDto,Microsoft.EntityFrameworkCore.Query.Internal.MaterializedAnonymousObject,Microsoft.EntityFrameworkCore.Query.Internal.MaterializedAnonymousObject]]' cannot be used for parameter of type 'System.Collections.Generic.IEnumerable`1[TestEntityFramework.DataObjects.ProductDto]' of method 'System.Collections.Generic.IEnumerable`1[TestEntityFramework.DataObjects.ProductDto] _ToEnumerable[ProductDto](System.Collections.Generic.IEnumerable`1[TestEntityFramework.DataObjects.ProductDto])'
If I remove the List from the ManagerDTO object and the relevant Product mapping config, the query above works successfully.
I saw this comment on a GitHub issue for what sounds like the same problem, but trying to implement the suggestion hasn't helped (assuming I've understood them correctly): https://github.com/AutoMapper/AutoMapper/issues/2853#issuecomment-482317381
Has anyone else run into this problem? I'm still getting used to AutoMapper so may have missed something obvious, but from searching around this seems to be a fairly uncommon issue and so pointers as to what's going on here have been hard to come by.
I'm open to any other suggestions as to what the best way of translating an OData query to entity framework, then back to a DTO is as well - if what I'm doing here isn't optimal!
Are you using the Automapper Collection Extensions? If not, this should solve your problem: https://github.com/AutoMapper/AutoMapper.Collection

Entity Framework Core returns null always except when using OrderBy?

I've been doing some testing with a graphql.net server, asp.net core, ef core and a northwind mssql database. Altho I ran into issues while doing getting the data from the database, what happens is that EF Core isn't returning the data that it should.
A Example of a model class:
public partial class Categories
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
public byte[] Picture { get; set; }
public ICollection<Products> Products { get; set; }
}
Pretty straight forward, btw I've been using the db first approach, and this is actually generated with the scaffold tool from ef core.
What I want to do is that when I query a category without arguments in graphql that it returns me the first item in the table(if I want to query any other item I would do with a other method, and that is working).
In my graphql resolver when I query a field, in this case a Category(I don't want a list of categories at this point), I would usually do it really easy with the following code:
public async Task<Categories> GetOne(int CategoryID)
{
Categories category =
await _dbContext.Categories.FirstOrDefaultAsync(
c => c.CategoryId.Equals(CategoryID));
return category;
}
The upper code returns me a null, but what is really weird is that the following code also returns null:
Categories categories = await _dbContext.Categories
.Where(c => c.CategoryId.Equals(CategoryID))
.FirstOrDefaultAsync();
The only scenario where it works, but not as expected because it actually returns me the 2nd ID out of the table, and whatever I append on the end be that FirstAsync, SingleAsync, or even LastOrDefaultAsync the result is always the second row in the table, using code snippet:
Categories categories = await _dbContext.Categories
.OrderBy(c => c.CategoryId.Equals(CategoryID))
.FirstOrDefaultAsync();`
I am fairly new to the Entity framework, so every help and suggestion is welcome.
So the behaviour of EF Core was ok, but the graphql resolver wasn't setup properly. After revisiting my graphql resolver, the actual methods did what they had to do.
I am very thankful for the input that all provided, and can mark this as resolved.

Categories

Resources