I have a problem while upserting to mongo db using the official C# driver.
public abstract class AggregateRoot
{
/// <summary>
/// All mongoDb documents must have an id, we specify it here
/// </summary>
protected AggregateRoot()
{
Id = ObjectId.GenerateNewId();
}
[BsonId]
public ObjectId Id { get; set; }
}
My entities already have the id-s but I had to create the mongo specific Id for it to work, as all the documents in a collection should have one. Now then I receive a new entity in my system a new Mongo Id is generated and I get the mongo cannot change _id of a document old exception. Is there some work-around?
Let me describe the design a bit. All the entities which would be
stored as documents were inheriting from AggregateRoot which had the
id generation in it. Every sub-document had its id generated
automatically and I had no problem with this. The id in AggregateRoot
was introduced to correct the problem when retrieving data from
MongoCollection to List and the generation was introduced so the id-s
are different. Now we can move that id generation to save methods
because the new entity for update had a new id generation. But it
would mean that every dev on the team must not forget generating id-s
in repository which is risky. It would be nicer just to ignore the id
than mapping from mongo if it is possible and not to have
AggregateRoot class at all
I've encountered similar problem.
I wanted to upsert documents using official C# driver. I had a class like this:
public class MyClass
{
public ObjectId Id { get; set; }
public int Field1 { get; set; }
public string Field2 { get; set; }
}
In console I would write: db.collection.update({Field1: 3},{Field1: 3, Field2: "value"}) and it would work. In C# I wrote:
collection.Update(Query.EQ("Field1", 3),
Update.Replace(new MyClass { Field1 = 3, Field2 = "value" }),
UpdateFlags.Upsert);
and it didn't work! Because driver includes empty id in update statement and when I upsert second document with different value of Field1 exception E11000 duplicate key error index is thrown (in this case Mongo tries to insert a document with _id that already exists in db).
When I generated _id by myself (like topic starter) I've encountered the same exception (mongo cannot change _id of a document) on upserting objects with existing value of Field1.
Solution is to mark Id property by attribute [BsonIgnoreIfDefault] (and not initialize it). In this case driver omits _id field in update statement and MongoDb generates Id if it necessary.
Looks like you might be explicitly setting the Id value for both inserts and updates. That's fine for inserts, all new objects need an _id value, however for updates you're not allowed to change the value of _id on an existing document after it's created.
Try not setting the Id value at all. If you don't specify a value before inserting, the driver uses built-in IdGenerator classes to generate a new _id value, so if it's an ObjectId type it'll use the ObjectIdGenerator. Then both your inserts and updates work fine.
Related
I've got a question. I have an existing mongodb collection I need to talk to containing an _id objectId and a UUID saved as string id field.
example
I need to fetch the documents based on a list of uuids -> which are basically the uuids in the id field of the document.
I've made a gateway to do this. (don't mind the TryAsync thingy that's basically a monad wrapped around a task)
public TryAsync<IEnumerable<T>> ByIds(List<Guid> ids)
{
var filter = Builders<T>.Filter.In("id", ids.Select(id => id.ToString()));
return TryAsync<IEnumerable<T>>.Apply(() => new List<T>()).SetAsync(() => Collection.Find(filter).ToListAsync());
}
The T is basically:
public interface IDomainObject
{
string id { get; set; }
}
When I run the code I get no result back.
So I decided to do some more testing, I've added the _id as well and tried to fetch it based on a fix _id.
Then I get this message =>
MongoDB.Bson.BsonSerializationException: 'The property 'Id' of type 'RG.Product.Core.Process.Product' cannot use element name '_id' because it is already being used by property '_id''
Seems like you can't have an _id and id on the same object?
I'm not interested in the _id object id, I just want to fetch the document without the _id field based on the id. Hopefully somebody can point me in the right direction?
Have a look at the attributes you can use in MongoDB.
public class DBInfo
{
[BsonId] // With this attribute you can define the _id field name
public ObjectId recordId;
[BsonIgnore]
public DBInfo child; // Ignore this variable
[BsonElement("sp")] // Rename this variable
public string searchPolicy;
[BsonExtraElements] // I always add this attribute on object with Id, this avoid deserialization exception in case of schema change
// All element the deserialization can't match will be added here
public BsonDocument catchAll;
}
https://github.com/iso8859/learn-mongodb-by-example/blob/main/dotnet/01%20-%20Begin/04%20-%20Attributes.cs
I have a mongo collection for customers. There is some 280 records in total.
The first 110 records have no BSON object ID, they simply have a customerId field that is of the type int.
After those first 110 the other records have both the customerId field as well as an _id object ID field.
Code that I found in the solution has a premade model for this, that has the Object id defined in it as well.
If I do something like this:
List<Customer> customers = _customerCollection.Find(_ => true).ToList();
I get an error because there is no BSON object id in the first 110 records. If I change the model and comment out this object id I will get the error when it reaches the ones that do have one.
Is there a way that I can deal with this and make this work? I am in the process of migrating the data and remapping it to other models.
I did not make the mongoDB or the existing code and have never worked with mongoDB, so my knowledge of it is limited.
Update - example code
Here is code of the class
public class Customer
{
//[BsonId]
//[BsonIgnoreIfDefault]
//[JsonIgnore]
//public ObjectId Id;
//public int CustomerId { get; set; }
public string CustomerName { get; set; }
public string CustomerDomain { get; set; }
...omitted other fields...
}
And here is an example document
{
"_id" : ObjectId("5c05a78ab66f775aefecafd9"),
"CustomerId" : 1000006,
"Warehouse" : "505",
"CustomerName" : "Some name",
"Email" : ""
}
I solved this by copying the collection twice. In one copy I removed all BSON object id having documents, in the other collection the other series of documents.
I ran my code twice, changing the model to accommodate the documents I was migrating. (eg once with object ID, once with int ID)
I've made up a generic repository to make CRUD operations in a MVC project.
When i try to delete a row from a table that has an identity on SQLServer, the code generated by the Ormlite Delete method and inspected with the profiler doesn't not affect any rows.
This is the Crud operation for the deletion (pretty simple):
public void Destroy<T>(T entity)
{
using (var db = dbFactory.Open())
{
db.Delete<T>(entity);
}
}
The Type T in my test is represented by the following class:
[Alias("FT_TEST_DEVELOPMENT")]
public class TestTable
{
[AutoIncrement]
[PrimaryKey]
public int ID { get; set; }
public string DESCR { get; set; }
public DateTime? TIMESTAMP { get; set; }
public DateTime DATE { get; set; }
public decimal PRICE { get; set; }
public int? QTY { get; set; }
}
And the inspected code corresponds to the following:
exec sp_executesql N'DELETE FROM "FT_TEST_DEVELOPMENT" WHERE "ID"=#ID AND "DESCR"=#DESCR AND "TIMESTAMP"=#TIMESTAMP AND "DATE"=#DATE AND "PRICE"=#PRICE AND "QTY"=#QTY ',
N'#ID int,#DESCR nvarchar(6),#TIMESTAMP datetime,#DATE datetime,#PRICE decimal(2,0),#QTY int',
#ID=4,#DESCR=N'SECOND',#TIMESTAMP=NULL,#DATE='2015-06-01 00:00:00',#PRICE=15,#QTY=NULL
When I execute this perfectly sensed statement the server tells me that no row
Disclaimer: as some names where in my native language, I translated them so there may be little grammar error, if it's so, let me note and I'll edit.
UPDATE
The matching row actually EXISTS in the database
SELECT * FROM FT_TEST_DEVELOPMENT WHERE ID= 4
ID DESCR TIMESTAMP DATE PRICE QTY
4 SECOND NULL 2015-06-01 15 NULL
I mean that actually the OrmLite generated code appears to be bugged.
And yes, the ID column is the table's key.
SECOND UPDATE
I think I've found the cause:
actually in the WHERE clause the NULL fields are assigned in the way
#TIMESTAMP=NULL
but actually the SQL server will not match this statement, because it expects to receive
WHERE [...] AND "TIMESTAMP" IS NULL [...]
The way db.Delete() API works has been updated so that NULL fields are moved out of the parameterized queries and appended to the SQL filter so this should now work from v4.0.37+ that's now available on MyGet.
You can also delete rows in OrmLite by PrimaryKey with:
Db.DeleteById<TestTable>(entity.Id);
For generic methods you can use the T.GetId() extension method to get the value of the Id field, i.e:
Db.DeleteById<TestTable>(entity.GetId());
Or to delete using every non null property in the DELETE WHERE criteria, you can use:
Db.DeleteNonDefaults(entity);
If you execute the same statement in SSMS and nothing gets deleted, it's because no row matches the criteria.
OrmLite expects the primary key of an entity to be named Id (case-sensitive). Your property is named ID and the [PrimaryKey] attribute wasn't specified. In this case OrmLite has to use all available fields in the WHERE clause to find the rows to delete.
AutoIncrement doesn't mean the field is a key, just that its value is auto-generated by the server and comes from an identity column. The same applies with SQL Server - an identity column isn't a primary key, you need to define the primary key separately.
You need to either rename ID to Id or add the [PrimaryKey] attribute to it.
I use Oracle EF 5.0 provider. Oracle 11G database.
Here is my database schema:
Table has an Id as primary key. For each table in database there are trigger, that fires on insert new record and EF get primary key after insert from sequence. Sequences are created for each table.
In my edmx file each ID column has an StoreGeneratedPattern="Identity" attribute set.
My code is:
using (var context = new Entities())
{
var title = new TITLE
{
TITLE_NUMBER = 4000001,
IS_DRAFT = "N",
REGISTRY_DATE = DateTime.Now
};
var titleName = new TITLE_NAME
{
NAME = "title name"
};
title.TITLE_NAME.Add(titleName);
context.Set<TITLE>().Add(title);
context.SaveChanges();
}
When context.SaveChanges() are executed, there is exception thrown:
The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges.
But changes are saved in database correctly. And my Title object has correct Id for Title and for Title_Name. What can I do? Or what I do wrong?
Opps. I remove inheritance from edmx and error goes away!....
But all project is build in inheritance!!!
Ops. MS SQL with same scheme gives this exception too.
From here: https://entityframework.codeplex.com/workitem/2515
EF Team Triage: This is because you have identity configured with a TPC mapping. Because the two types are mapped to completely separate tables, you will get duplicate IDs generated since the database doesn't know to generate unique values between the two tables. One way around this is to modify the tables to have one generate odd numbers and the other generate even numbers.
Workaround for this.
We create Interface with shared properties for all entities and remove inheritance from edmx file.
I.e.
public interface IHistoricalEntity
{
int ID { get; set; }
int ENTITY_ID { get; set; }
DateTime CREATE_DATE { get; set; }
DateTime DELETE_DATE { get; set; }
int CREATOR { get; set; }
}
in partial file TITLE.cs
public partial class TITLE : IHistoricalEntity
{
}
and thus we can use generic version for all entities in our project.
I have the following entity with custom ID field:
public class User : IEntity {
public string Id { get; set; }
}
if (!BsonClassMap.IsClassMapRegistered(typeof(User))) {
BsonClassMap.RegisterClassMap<User>(map => map.AutoMap());
}
It works pretty good, when I add new record to collection, ID value is assigned.
However, when I'm trying to index any other field within this collection:
UserCollection.EnsureIndex(IndexKeys<User>.Ascending(p => p.Email),IndexOptions.SetUnique(true));
Id mapping becames broken, ID field value is null after insert and in collection I see default '_id' field generated (insead of my 'ID').
Any ideas what's the problem? (I'm using driver 1.8.3.9, mongo win32 2.4.5, NET 4.5). Thanks.
Found solution! UserCollection.EnsureIndex was called before the class mapping, causing the problem.