At the moment i'm updating the entire document, but if the ID changes i get error message:
MongoDB.Driver.MongoWriteException: 'A write operation resulted in an error.
After applying the update, the (immutable) field '_id' was found to have been altered to _id: BinData(3, B3FD0EE0FF161845BE96BE40A7DDE84B)'
So i want it to ignore the ID field when updating a document.
Here's what i'm doing now:
public async Task<bool> UpdateMatch(Guid id, Match match)
{
ReplaceOneResult actionResult
= await _context.Match.ReplaceOneAsync(m => m.Id.Equals(id),
match,
new UpdateOptions { IsUpsert = true });
return actionResult.IsAcknowledged && actionResult.ModifiedCount > 0;
}
Thanks in advance for your help!
Add 'BsonIgnoreIfDefault' attribute to the InternalId property.
public class Document
{
[BsonId]
[BsonIgnoreIfDefault]
public ObjectId InternalId { get; set; }
// rest of document
}
The problem which is happening is that MongoDB will add the _id field to the replacement document if it is not specified in either the filter or replacement documents if ReplaceOneAsync is used. If _id is present in both, the values must be equal. However, if I understand your code properly you are trying to find a document by ID and replace it. There is a collection method called FindOneAndReplace() or FindOneAndReplaceAsync() that I would have used if I were you. You might want to check out the MongoDB documentation for this:
https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/
https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/
Hope this helps you!
Related
(Just using the {{ }} to tell which data is variable)
This gets the data from the database and puts it into a property. The Table_Column and PropertyName are usually not the same.
[Column(Name = "{{Table_Column}}")]
public string {{PropertyName}} { get; set; }
In this class the data (which is contained by properties in the location of {{PropertyName}})is connected to the docElement. docElement contains the name of the element and the name of the Table_Column.
var documentElements = GetAllElements();
foreach (DocumentElement docElement in documentElements)
{
ConnectData<Document>(docElement.ElementName, (doc) => doc.{{PropertyName}});
}
I want to connect data to my Elements using my function ConnectData, which requires me to give an ElementName and the data I want to connect to the Element. The documentElements, which I loop through with a foreach, all have an ElementName and ElementType. That ElementType is the same as {{Table_Column}} of the database. With that I want to find which property I need to call to get the right data.
Thanks in advance!
Questions? Please leave a comment.
I've a Parent Entity that has a property of list of Child Entities. Initially on Insert I don't have values for the child entities.
But when I try to Update(by calling push) the document with Child Entities it fails.
This works when I insert a dummy child entity value to the Initial Add .
This is because the embedded document refers to null .
public class ParentDocument : Entity
{
public string prop1 { get; set; }
public List<EmbeddedDocument> EmbeddedDocuments { get; set; }
}
public class EmbeddedDocument
{
public string prop2{ get; set; }
}
The parent is saved First
_collection.InsertOne(new ParentDocument(){prop1 ="value"});
and later when I Update the document
var builder = Builders<ParentDocument>.Update;
var updateDefintion = builder.Push(x => x.EmbeddedDocuments ,new EmbeddedDocument() { prop2= "value2" });
_collection.UpdateManyAsync(x=>x.Id==ParentDocumentId, updateDefinition)
error occurs "A write operation resulted in an error mongodb"
But this push works if I have already inserted Embedded Document(s) in the List on first insert.
I think that is because of that the EmbeddedDocuments property is Inserted as null the push doesn't work.
I also tried passing empty List to intial Insert,but not helped.
One Idea would be to check if the count of List of Embedded documents is zero and call
Builder.set(x=>x.EmbeddedDocuments ,new List<EmbeddedDocument>(){ item1 });
But this will cost a query , which I don't want to.
Is there any other solution?
Thanks in Advance
To Hazard a guess, it's because the "array" field in the database is null after the insert. You either need to make the initial value in the database an empty array, or you need to make it not-present. You can either:
use the [BsonIgnoreIfDefault] attribute on your list field to not store nulls,
Initialize your list field to an empty list to store an empty array
This can be reproduced in the shell very easily:
> db.so.insert({x:1, y: null})
> db.so.update({x:1}, {$push: { y: "funny" }})
This will error. However, if you remove y from the insertion or change it to an empty array, the update will succeed.
I have activated migrations on my Azure Mobile Services project. I filled the new seed function Inside the Configuration.cs class of the migrations. If the tables are empty, the seed function is going without any problems. When my AddorUpdate tries to update the first object I get the error in the inner exception : "Modifying a column with the 'Identity' pattern is not supported. Column: 'CreatedAt'. Table: 'CodeFirstDatabaseSchema.Category'."
Part of my code is as follows:
context.categories.AddOrUpdate(
new Category { Id="1", Code="GEN", Text="General"},
new Category { Id="2", Code="POL", Text="Politics"},
new Category { Id="3", Code="FAS", Text="Fashion"},
new Category { Id="4", Code="PEO", Text="People"},
new Category { Id="5", Code="TEC", Text="Technology"},
new Category { Id="6", Code="SPO", Text="Sport"},
new Category { Id="7", Code="LIV", Text="Living"}
);
Here's my generic implementation of Nikatlas' solution.
Short version of the answer: You can't modify CreatedAt with a null value, so you can use this function instead:
private void AddOrUpdatePreservingCreatedAt<T> (DbSet<T> set, T item) where T : EntityData
{
var existing = set.Where(i => i.Id == item.Id).FirstOrDefault();
if (existing != null)
{
item.CreatedAt = existing.CreatedAt;
}
set.AddOrUpdate(i => i.Id, item);
}
Call it like this
AddOrUpdatePreservingCreatedAt(context.YourItems, itemToBeUpdatedOrAdded);
It seems i have found a solution to this problem.
The reason this error occurs is because of the AddOrUpdate Method.
As stated in this post : http://thedatafarm.com/data-access/take-care-with-ef-4-3-addorupdate-method/
More importantly, if a match is found then the update will update all and null out any that weren’t in your AddOrUpdate.
What this means is that after the first seed, whenever your code runs it tries to update your entities correctly but it tries to pass the value null on CreatedAt field. If you look at EntityData class the CreateAt field has these attributes :
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] //Here they mark this as IDENTITY
[Index(IsClustered = true)] // Cluster index. i really dont know why ?
[TableColumn(TableColumnType.CreatedAt)]
public DateTimeOffset? CreatedAt { get; set; }
So the error occurs beacause you try to modify the CreatedAt column.
My solution was to Create a List, lookup to set CreatedAt to correct value and then addOrUpdate:
// Create List
List<Permission> permissions = new List<Permission>(new Permission[]{
new Permission { Id = "ID1" , Name = "Send SMS"},
new Permission { Id = "ID2", Name = "Send Email"} });
// Iterate through list to set CreatedAt to correct value
foreach (Permission p in permissions){
// Get the record from the db if it exists
var t = context.PermissionSet.Where(s => s.Id == p.Id).FirstOrDefault();
if (t != null){
p.CreatedAt = t.CreatedAt; //SET CreatedAt to correct Value if the record already exists
}
context.PermissionSet.AddOrUpdate(a => a.Id, p); // NOW I CAN UPDATE WITH NO PROBLEM
}
Hope this helps. :)
If you have a integer column named Id, then Entity Framework will assume that is the primary key and that it is database generated - so it is created as an IDENTITY column in the database.
You cannot specify the Id for IDENTITY columns, so you stop doing that by removing the Id = 1, Id = 2, etc
I am a bit thrown by the fact that the column you have a problem with is named "CreatedAt". It sounds like it should be a DateTime and might also be database generated, but it surely shouldn't be IDENTITY?
Anyways, the usage you probably want is the one where you specify the natural key of the entity, so that EF can identify any records that already exist. So, if CODE is the natural key then you should be writing the Seed like this:
context.categories.AddOrUpdate(
x => x.Code,//the natural key is Code
new Category { Code="GEN", Text="General"},
new Category { Code="POL", Text="Politics"},
new Category { Code="FAS", Text="Fashion"},
new Category { Code="PEO", Text="People"},
new Category { Code="TEC", Text="Technology"},
new Category { Code="SPO", Text="Sport"},
new Category { Code="LIV", Text="Living"}
);
Reference:
Take care with the AddOrUpdate method
This question, its answers and comments might help you a bit, but not much.
You can do Inserts on identity column using the solutions provided by the question. But you cannot update values of Identity Column. The only way to do updates on identity column is to mark it as not being identity. Probably by adding manual migrations.
This SO question and its answers may also be helpful.
Read here also on general MSSQL Server constraints on Updating Identity Column.
SAME PROBLEM AND SOLUTION
We had the same problem and it was a trigger in another table that had impact on the table that we had the problem on.
DETAILS OF OUR DEVELOPMENT
We developed a Xamarin App connected to an Azure Web Service. When we used the method PushAsync from iMobileServices, it gave us the error: Modifying a column with the 'Identity' pattern is not supported. Column: 'CreatedAt'.
It was strange for us as some tables did not have the problem with the web service
REASON
It seems that the trigger update got in conflict with the pushasync from the mobile device.
We disabled the trigger, switched the responsibility to the front end and it worked fine. At least for us.
We hope this soltion helps someone.
I can't figure this one out. I'm creating a new ElasticSearch index using the ElasticProperty attributes/decorators. Here's how I create the index:
client = ClientFactory(indexName);
if (!client.IndexExists(indexName).Exists)
{
var set = new IndexSettings() { NumberOfShards = 1 };
var an = new CustomAnalyzer() { Tokenizer = "standard" };
an.Filter = new List<string>();
an.Filter.Add("standard");
an.Filter.Add("lowercase");
an.Filter.Add("stop");
an.Filter.Add("asciifolding");
set.Analysis.Analyzers.Add("nospecialchars", an);
client.CreateIndex(c => c
.Index(indexName)
.InitializeUsing(set)
.AddMapping<ItemSearchable>(m => m.MapFromAttributes())
);
}
And all of my fields are being created properly from the attributes specified on the class, except these two. They are C# enumerations, which is maybe part of the problem. I'm trying to save them in the index as numeric fields...
[Required, ElasticProperty(Index = FieldIndexOption.NotAnalyzed, Store = true, NumericType = NumberType.Short, IncludeInAll = false)]
public Enums.PlatformType PlatformType { get; set; }
[ElasticProperty(Index = FieldIndexOption.NotAnalyzed, Store = true, NumericType = NumberType.Short, OmitNorms = true, IncludeInAll = false)]
public Enums.ItemType ItemType { get; set; }
When I set up the index and check via Kibana, I don't see PlatformType or ItemType at all in the list of fields in the empty index.
When I insert a record, I can see the values in the source (JSON) as numbers (as expected), but the "Fields" are not there.
So I'm thinking it's either because they're C# enum type, or because I'm trying to store it as a number. But I'm stumped on why Elasticsearch is skipping these fields. Many thanks for any ideas you may have.
UPDATE 1 ... The search still works (even without those fields being shown in the Fields section). I'm thinking it might just be a Kibana problem. In the Table view, it shows my two fields like this...
and hovering over those triangle exclamation marks says "No cache mapping for this field. Refresh your mapping from the Settings > Indices page". But of course I can't find such a page within Kibana.
So I might be fine behind the scenes and this is a non-issue. Does anyone else have any insight on what might fix this, make it clearer, or whether this is known behaviour and I should just move on? Thanks.
This is a tad complicated, and please, I know all the arguments against natural PK's, so we don't need to have that discussion.
using VS2012/MVC4/C#/CodeFirst
So, the PK is based on the date and a corresponding digit together. So, a few rows created today would be like this:
20131019 1
20131019 2
And one created tomorrow:
20131020 1
This has to be automatically generated using C# or as a trigger or whatever. The user wouldn't input this. I did come up with a solution, but I'm having problems with it, and I'm a little stuck, hence the question.
So, I have a model:
public class MainOne
{
//[Key]
//public int ID { get; set; }
[Key][Column(Order=1)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string DocketDate { get; set; }
[Key][Column(Order=2)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string DocketNumber { get; set; }
[StringLength(3, ErrorMessage = "Corp Code must be three letters")]
public string CorpCode { get; set; }
[StringLength(4, ErrorMessage = "Corp Code must be four letters")]
public string DocketStatus { get; set; }
}
After I finish the model, I create a new controller and views using VS2012 scaffolding.
Then, what I'm doing is debugging to create the database, then adding the following instead of trigger after Code First creates the DB [I don't know if this is correct procedure]:
CREATE TRIGGER AutoIncrement_Trigger ON [dbo].[MainOnes]
instead OF INSERT AS
BEGIN
DECLARE #number INT
SELECT #number=COUNT(*) FROM [dbo].[MainOnes] WHERE [DocketDate] = CONVERT(DATE, GETDATE())
INSERT INTO [dbo].[MainOnes] (DocketDate,DocketNumber,CorpCode,DocketStatus) SELECT (CONVERT(DATE, GETDATE
())),(#number+1),inserted.CorpCode,inserted.DocketStatus FROM inserted
END
And when I try to create a record, this is the error I'm getting:
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: The object state cannot be changed. This exception may result from one or more of the primary key properties being set to null. Non-Added objects cannot have null primary key values. See inner exception for details.
Now, what's interesting to me, is that after I stop debugging and I start again, everything is perfect. The trigger fired perfectly, so the composite PK is unique and perfect, and the data in other columns is intact.
My guess is that EF is confused by the fact that there is seemingly no value for the PK until AFTER an insert command is given. Also, appearing to back this theory, is that when I try to edit on of the rows, in debug, I get the following error:
The number of primary key values passed must match number of primary key values defined on the entity.
Same error occurs if I try to pull the 'Details' or 'Delete' function.
Any solution or ideas on how to pull this off? I'm pretty open to anything, even creating a hidden int PK. But it would seem redundant.
EDIT 21OCT13
[HttpPost]
public ActionResult Create(MainOne mainone)
{
if (ModelState.IsValid)
{
var countId = db.MainOnes.Count(d => d.DocketDate == mainone.DocketNumber); //assuming that the date field already has a value
mainone.DocketNumber = countId + 1; //Cannot implicitly convert type int to string
db.MainOnes.Add(mainone);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(mainone);
}
EDIT 21OCT2013 FINAL CODE SOLUTION
For anyone like me, who is constantly searching for clear and complete solutions.
if (ModelState.IsValid)
{
String udate = DateTime.UtcNow.ToString("yyyy-MM-dd");
mainone.DocketDate = udate;
var ddate = db.MainOnes.Count(d => d.DocketDate == mainone.DocketDate); //assuming that the date field already has a value
mainone.DocketNumber = ddate + 1;
db.MainOnes.Add(mainone);
db.SaveChanges();
return RedirectToAction("Index");
}
hy don't you manage it via code instead of using a trigger?
var countId = context.MainOne.Count(d => d.DocketDate == newItem.DocketNumber); //assuming that the date field already has a value
newItem.DocketNumber = CountId + 1;
Context.MainOne.Add(newItem);
context.SaveChanges();
And this should solve your problem.