I got an .net core application that is pretty straight forward it is using REST to add and download objects to and from mongo db. Adding items works really well. Getting a list that contains all items aswell, but when I try to access one using id then everytime I get null. What should i change to make this piece of code work. It means get a Tool object from database using it unique ID when there's one matching in database.
Here's a object in database
Here's my repository class
private IMongoCollection<Tool> Tools => _database.GetCollection<Tool>("Tools");
public async Task<Tool> GetAsync(Guid id) =>
await Tools.AsQueryable().FirstOrDefaultAsync(tool => tool.Id == id);
Argument looks like that when I check it out in debugger "{ee1aa9fa-5d17-464c-a8ba-f685203b911f}"
Edit
Tool Class Properties
public Guid Id { get; protected set; }
public string Model { get; protected set; }
public string Brand { get; protected set; }
public string Type { get; protected set; }
public uint Box { get; protected set; }
Fixed check comments
Project on github
The easiest way to do this in C# MongoDB Driver is to set a global GuidRepresentation setting which can be found on the BsonDefaults object. This is a global setting and will effect all serialization/deserialization of GUIDs in to Bson Binary Objects.
BsonDefaults.GuidRepresentation = GuidRepresentation.PythonLegacy;
var collection = new MongoClient().GetDatabase("test").GetCollection<ClassA>("test");
var item = collection.Find(x => x.MyId == new Guid("ee1aa9fa-5d17-464c-a8ba-f685203b911f"))
.FirstOrDefault();
The second option is to convert the GUID manually from a LUUID to a CSUUID, for this there is a helper class within the MongoDB driver of GuidConverter, with this it converts the GUID in to byte[] which is normally used for storage but we can use it for our query.
BsonDefaults.GuidRepresentation = GuidRepresentation.CSharpLegacy;
var collection = new MongoClient().GetDatabase("test").GetCollection<ClassA>("test");
var luuid = new Guid("0798f048-d8bb-7048-bb92-7518ea4272cb");
var bytes = GuidConverter.ToBytes(luuid, GuidRepresentation.PythonLegacy);
var csuuid = new Guid(bytes);
var item = collection.Find(x => x.MyId == csuuid)
.FirstOrDefault();
I also noticed that you're using Robo 3T (formerly Robomongo), within this application you can set how GUIDs are displayed by going to Options -> Legacy UUID Encodings
Related
I can add, but not erase any item with the collection - unable to delete.
Found a few partial solutions, but nothing to guide me to a working solution. I can easily add values to the collection; ny help is appreciated.
I have the following:
[HttpPut("updateSOJ4")]
public IActionResult UpdateSOJ4([FromBody] Routing_Tool_SOJ4 Routing_Tool_SOJ4)
{
Routing_Tool_SOJ4 request = new Routing_Tool_SOJ4();
request.Id = Routing_Tool_SOJ4.Id;
request.Routing_Tool_Services = Routing_Tool_SOJ4.Routing_Tool_Services;
request.Routing_ToolId = Routing_Tool_SOJ4.Routing_ToolId;
_repository.UpdateSOJ4(request);
return Ok(request);
}
Here is where I was trying the different solutions, but, I am still stuck:
public void UpdateSOJ4(object routing_Tool_SOJ4)
{
// var missingItem = _context.Routing_Tool_Service.Where(i => i.Routing_Tool_SOJ4Id == _context.Routing_Tool_SOJ4.Id).First(); -- DOES NOT WORK
_context.Update(routing_Tool_SOJ4).State = EntityState.Modified;
_context.SaveChanges();
}
Here is the database structure:
public class Routing_Tool_SOJ4
{
[Key]
[Required]
public int Id { get; set; }
public int Routing_ToolId { get; set; }
[ForeignKey("Routing_ToolId")]
public virtual Routing_Tool Routing_Tool { get; set; }
public virtual ICollection <Routing_Tool_Service> Routing_Tool_Services { get; set; }
}
Collection:
public class Routing_Tool_Service
{
[Key]
[Required]
public int Id { get; set; }
public string ServiceName { get; set; }
[Required]
[ForeignKey("Routing_Tool_SOJ4Id")]
public int Routing_Tool_SOJ4Id { get; set; }
}
What I am deduce from your question is you have a method that accepts an updated Routing Tool object which contains an updated collection of Tool Services. You want to update that tool and it's associated services so that any service within that tool that is new gets added, otherwise updated, and any existing tool in the DB that is no longer in the passed in collection should be deleted..
If this is the case, you need to compare the provided version of the data to the database version of the data. For this example I am not using your Repository instance because I have no idea how it is implemented. Generally this pattern should be avoided unless there is a really good reason to have it.
[HttpPut("updateSOJ4")]
public IActionResult UpdateSOJ4([FromBody] Routing_Tool_SOJ4 updatedRoutingTool)
{
using (var context = new AppDbContext())
{
// Get tool and services from DB.
var existingRoutingTool = context.Routing_Tool_SOJ4s
.Include(x => x.Routing_Tool_Services)
.Single(x => x.Id == updatedRoutingTool.Id);
// Copy values that can be updated from the updatedRoutingTool to existingRoutingTool.
// ...
var updatedServiceIds = updatedRoutingTool.Routing_Tool_Services
.Select(x => x.Id)
.ToList();
var existingServiceIds = existingRoutingTool.Routing_Tool_Services
.Select(x => x.Id)
.ToList();
var serviceIdsToRemove = existingServiceIds
.Except(updatedServiceIds)
.ToList();
foreach (var service in updatedRoutingTool.Routing_Tool_Services)
{
var existingService = existingRoutingTool.Routing_ToolServices
.SingleOrDefault(x => x.Id == service.Id);
if (existingService == null)
existingRoutingTool.Routing_Tool_Services.Add(service);
else
{
// Copy allowed values from service to existingService
}
}
if(serviceIdsToRemove.Any())
{
var servicesToRemove = existingRoutingTool.Routing_Tool_Services
.Where(x => serviceIdsToRemove.Contains(x.Id))
.ToList();
foreach(var serviceToRemove in servicesToRemove)
existingRoutingTool.Routing_Tool_Services.Remove(serviceToRemove);
}
context.SaveChanges();
}
return Ok(request);
}
Normally the DbContext or Unit of Work would be injected into your controller, or the logic would be handed off to a service. This example uses a using block with a DbContext just to outline the minimum viable process flow for the operation.
Essentially load the current data state, compare that with the provided state to determine what needs to be added, updated, or removed.
Generally speaking when it comes to RESTful web services my recommendation is to avoid large update operations like this and instead structure the application to perform more atomic operations such as adding and removing services for a given tool as a distinct operation, working with a persisted copy (i.e. cached instance) of the data if you want the whole related operation to be committed to data state or abandoned at a higher level. This can help keep message sizes small, and server code more compact & worrying about a single responsibility. The risk of performing these large operations is that the passed in data must represent a complete picture of the data state or you could end up deleting/clearing data you don't intend. For example if you later want to optimize your code so that only added and updated services are sent over the wire, not unchanged services (to reduce message size) the above code will not work as it would delete anything not sent.
I am using Entity Framework Core with npgsql postgresql for Entity Framework Core.
and i'm working with .net core 3
My question is, when i try to update a MyTableRelated element from the MyTableClass and saving the context to the database, no changes are detected.
For example, lets suppose we have the following classes:
public class MyTableClass
{
public int Id { get; set; }
[Column(TypeName = "jsonb")]
public virtual List<MyTableRelated> Data { get; set; }
}
public class MyTableRelated
{
public int Id { get; set; }
public string prop1 { get; set; }
public string prop2 { get; set; }
}
and some code like this (this is not actual code, its just to get the ideia):
var context = dbContext;
var newMyTableClass = new MyTableClass() {
Id = 1;
};
var newMyTableRelated = new MyTableRelated(){
Id=1;
prop1 = "";
prop2 = "";
}
newMyTableClass.Data.Add(newMyTableRelated);
context.SaveChanges();
This works, and the entry is saved on the database.
Now somewhere on the application, i want to access that entry and change values on Data:
var context = dbContext;
var updateMyTableClass = context.MyTableClass.FirstOrDefault(x => x.Id == 1);
var tableRelated = updateMyTableClass.Data.FirstOrDefault(y => y.Id == 1);
tableRelated.prop1 = "prop1";
tableRelated.prop2 = "prop2";
context.SaveChanges();
I would suppose this would change values on database, like it does for other types of properties. But nothing happens.
A solution i found, was using this:
var entry = context.Entry(updateMyTableClass);
if (entry.State == EntityState.Unchanged)
{
entry.State = EntityState.Modified;
}
This is more of a temporary solution for that case.
How can we then make the EF automatically detect changes on jsonb properties?
Someone pointed to me that i should look at coase grained lock.
https://www.martinfowler.com/eaaCatalog/coarseGrainedLock.html
How can something like that be implemented?
Automatic change detection would mean that EF Core would take a snapshot of the JSON document when it loads the property (duplicating the entire tree), and then do a complete structural comparison of the original and current tree whenever SaveChanges is called. As this can be very heavy perf-wise, it is not done by default.
However, if you wish to do so, you can create a value comparer to implement precisely this - see the EF docs on how to do that. I've opened an issue on the Npgsql provider repo in case someone wishes to contribute this.
For perf reasons, I'd recommend manually flagging properties when they change, similar to what you have done. Note that you're marking the entire entity instance as changed - so all properties will be saved. You can use the following to only mark the JSON property:
ctx.Entry(entry).Property(e => e.SomeJsonProperty).IsModified = true;
I have table with records in HTML and many filtering options by properties. User can choose none, one or more. I'm using mongoDb database and MongoDb driver in C#. How I can realize this filtering?
Filter
public class ConsultantFiltersModel
{
public string RecruiterName { get; set; }
public int? RecruiterCode { get; set; }
public bool? WithouRecruiter { get; set; }
public string FutureWorkerName { get; set; }
public string PhoneNumber { get; set; }
}
I pass this filter from client side, some properties are Nullable. I need to filter records by only non-null properties.
I've tried to realize it like this
public Consultant FilterRecords(ConsultantFiltersModel consultantFiltersModel)
{
return consultantsCollection
.AsQueryable()
.Where(recruiterNameFilter =>
string.IsNullOrEmpty(consultantFiltersModel.RecruiterName)
? true
: recruiterNameFilter.Recruiter.Value == consultantFiltersModel.RecruiterName)
.Where(recruiterCodeFilter =>
consultantFiltersModel.RecruiterCode.HasValue
? true
: recruiterCodeFilter.Recruiter.Key == consultantFiltersModel.RecruiterCode)
.Where(phoneNumberFilter =>
string.IsNullOrEmpty(consultantFiltersModel.PhoneNumber)
? true
: phoneNumberFilter.FutureWorker.Phone.Contains(consultantFiltersModel.PhoneNumber))
.Where(futureWorkerNameFilter =>
string.IsNullOrEmpty(consultantFiltersModel.FutureWorkerName)
? true
: futureWorkerNameFilter.FutureWorker.Name.Contains(consultantFiltersModel.FutureWorkerName))
.FirstOrDefault();
}
But I get this exception
An exception of type 'System.Reflection.TargetException' occurred in
System.Private.CoreLib.ni.dll but was not handled in user code
Additional information: Non-static method requires a target.
Maybe it's not supported by mongoDbDriver? Any advises?
You may have better luck using the native mongodb Find() either using Lambda directly, or using the Builders to create your filters. They might be easier to build dynamically than the query above
var builder = Builders<YourObject>.Filter;
var filter = builder.Eq(x => x.YourField, "SomeValue");
var andFilter = builder.And(filter, builder.Eq(x => .SomeOtherField, "SomeOtherValue"));
var results = consultantsCollection.Find(andFilter).FirstOrDefault();
The Builders have a number of extensions methods for your filtering, as shown above, i am using .Eq() and .And(), but explore it and you will find an ectensive list to help you
We are looking to switch from a relational database to elastic search and I am trying to get some basic code up and running with Nest. We have existing objects which use guids for ids that I would like to save into an elastic search index.
I don't want to add any specific attributes as the class is used in different applications and I don't want to add unnecessary dependencies to Nest.
Right now my code looks like this:
var node = new Uri("http://localhost:9200");
var settings = new ConnectionSettings(node)
settings.DefaultIndex = "test";
var client = new ElasticClient(settings);
var testItem = new TestType { Id = Guid.NewGuid(), Name = "Test", Value = "10" };
var response = client.Index(testItem);
With TestType as:
public class TestType
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Value { get; set; }
}
However I get an error like:
ServerError: 400Type: mapper_parsing_exception Reason: "failed to
parse [id]" CausedBy: "Type: number_format_exception Reason: "For
input string: "c9c0ed42-86cd-4a94-bc86-a6112f4c9188""
I think I need to specify a mapping that tells the server the Id is a string, but I can't find any examples or documentation on how I do this without using the attributes.
Assuming you're using Elasticsearch 2.x and NEST 2.x (e.g. latest of both at time of writing is Elasticsearch 2.3.5 and NEST 2.4.3), then NEST will automatically infer the id of a POCO by default from the Id property. In the case of a GUID id, this will be saved as a string in Elasticsearch.
Here's an example to get you going
void Main()
{
var node = new Uri("http://localhost:9200");
var settings = new ConnectionSettings(node)
// default index to use if one is not specified on the request
// or is not set up to be inferred from the POCO type
.DefaultIndex("tests");
var client = new ElasticClient(settings);
// create the index, and explicitly provide a mapping for TestType
client.CreateIndex("tests", c => c
.Mappings(m => m
.Map<TestType>(t => t
.AutoMap()
.Properties(p => p
// don't analyze ids when indexing,
// so they are indexed verbatim
.String(s => s
.Name(n => n.Id)
.NotAnalyzed()
)
)
)
)
);
var testItem = new TestType { Id = Guid.NewGuid(), Name = "Test", Value = "10" };
// now index our TestType instance
var response = client.Index(testItem);
}
public class TestType
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Value { get; set; }
}
Take a look at the Automapping documentation for more examples of how to explicitly map a POCO for controlling norms, analyzers, multi_fields, etc.
What I normally do is to have a separate class that is only specific to Elasticsearch. And use Automapper to map that into a DTO or ViewModel, or Model into the Elasticsearch Document.
That way, you won't have to expose an object that have a dependency in NEST and attributes that might be specific only to Elasticsearch.
Another good reason is that normally, documents in ES are flat, so you would normally flatten your objects before you index them to ES.
I am starting off with azure document db. I was trying to update an existing document. When I use the following query everything works:
dynamic Team2Doc = client.CreateDocumentQuery<Document>(documentCollection.DocumentsLink).Where(d => d.Id == "t002").AsEnumerable().FirstOrDefault();
Team2Doc.TeamName = "UPDATED_TEAM_2";
await client.ReplaceDocumentAsync(Team2Doc);
but if use the below code:
dynamic Team2Doc = client.CreateDocumentQuery<Document>(documentCollection.DocumentsLink).Where(d => d.TeamName== "team1").AsEnumerable().FirstOrDefault();
Team2Doc.TeamName = "UPDATED_TEAM_2";
await client.ReplaceDocumentAsync(Team2Doc);
I get this error:
"The best overloaded method match for
'Microsoft.Azure.Documents.Client.DocumentClient.ReplaceDocumentAsync(Microsoft.Azure.Documents.Document,
Microsoft.Azure.Documents.Client.RequestOptions)' has some invalid
arguments"
Is there anyway to retrieve a document by one of the properties and update the document?
The where clause is trying to query the property TeamName which does not exist in Document class.
Changing the type of the queryable to your data model should fix it.
For example, say you have the following data model:
public class EmployeeDocument : Document
{
// Other properties that you may have similarly defined ....
public class string TeamName
{
get
{
return this.GetValue<string>("TeamName");
}
set
{
this.SetValue("TeamName", value);
}
}
}
Then you can modify your query like this:
var team2Doc = client.CreateDocumentQuery<EmployeeDocument>(documentCollection.DocumentsLink).Where(d => d.TeamName== "team1").AsEnumerable().FirstOrDefault();
team2Doc.TeamName = "UPDATED_TEAM_2";
await client.ReplaceDocumentAsync(team2Doc);
Note that you have to use the EmployeeDocument, instead of the Document class, while creating the document queryable. That will let you query on EmployeeDocument properties.
SQL Version
Creating a document model for each of your existing data models may not be feasible if you have a large number of data models. In that case you may want to try out the SQL query syntax.
Refer to Aravind's answer in this post. The example he uses is for deleting documents, but it can be easily modified to update them too.
You can also create a model with Id:
public class Employee
{
[JsonPropery("id")]
public class string Id { get; set; }
public class string TeamName { get; set; }
}
And then replace the document using it's Id:
var employee= client
.CreateDocumentQuery<Employee>(documentCollection.DocumentsLink)
.Where(d => d.TeamName== "team1")
.AsEnumerable()
.FirstOrDefault();
employee.TeamName = "team2";
var documentUri = UriFactory.CreateDocumentUri(databaseName, collectionName, employee.Id);
await client.ReplaceDocumentAsync(employee);