RavenDb changes to Property (nullable) on Entity that is a class doesn't save - ravendb

I have a class like this:
public class Content {
public string Id {get; set;} = "content/"
public ContentJob? Job {get; set;}
}
public class ContentJob {
public string JobId {get; set;} = string.Empty;
}
I can create the content and if the job is there it will persist. with Store/SaveChanges.
But what I can't do is update (or more accurately set) ContentJob on Content and have it detect that there was a change. (HasChange(instance) == false) and SaveChanges doesn't update the database.
Why? And how do I make it detect the change?
(incidentally I tried using C# 9 records in hopes that because it automatically does deep object equality that this would solve it and no, it doesn't)

I created an unit-test based on your question, and it works as expected.
[Fact]
public void ShouldWork()
{
using (var store = GetDocumentStore())
{
string id = string.Empty;
using (var session = store.OpenSession())
{
var c = new Content();
session.Store(c);
session.SaveChanges();
id = session.Advanced.GetDocumentId(c);
var entity = session.Load<Content>(id);
entity.Job = new ContentJob()
{
JobId = "123"
};
Assert.True(session.Advanced.HasChanged(entity));
session.SaveChanges();
}
Assert.False(string.IsNullOrEmpty(id));
using (var session = store.OpenSession())
{
var entity = session.Load<Content>(id);
Assert.NotNull(entity.Job);
Assert.Equal("123", entity.Job.JobId);
}
}
}
public class Content
{
public string Id { get; set; } = "content/";
public ContentJob? Job { get; set; }
}
public class ContentJob
{
public string JobId { get; set; } = string.Empty;
}

Related

How to override the ef core to write data automatically or log to file?

I am using asp.net core + ef core. I can add delete get or update data to the database.
But can it be true that when I do these operations it can auto log to file or database the userinfo and datetime?
If I am updating data the data will auto update the LastModifiedDateTime and ModifyUserId property.
or log it to file?
for example:
If I update the database
await _context.SaveChangesAsync(cancellationToken);
I can do this:
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
foreach (var entry in ChangeTracker.Entries<AuditableEntity>())
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreatedBy = _currentUserService.UserId;
entry.Entity.Created = _dateTime.Now;
break;
case EntityState.Modified:
entry.Entity.LastModifiedBy = _currentUserService.UserId;
entry.Entity.LastModified = _dateTime.Now;
break;
}
}
return base.SaveChangesAsync(cancellationToken);
}
no need I specify the property but override it to log it automatically?
how to set the property of each entity?
Then I can know when and who do the update?
Create new base model class. Every models must have at least one or more columns that are same such as ID, CreatedBy, etc. Then link base model with all your models.
BaseModel.cs
public class BaseModel
{
public int ID { get; set; }
public DateTime? Created { get; set; }
public string CreatedBy { get; set; }
public DateTime? LastModified { get; set; }
public string LastModifiedBy { get; set; }
}
User.cs
public class User : BaseModel
{
public string Name { get; set; }
public int Age {get; set; }
}
YourDbContext.cs
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseModel && (x.State == EntityState.Added || x.State == EntityState.Modified));
DateTime dt = DateTime.Now;
foreach (var entity in entities)
{
if (entity.State == EntityState.Added)
{
((BaseModel)entity.Entity).CreatedBy = _currentUserService.UserId;
((BaseModel)entity.Entity).Created = dt;
}
else if (entity.State == EntityState.Modified)
{
((BaseModel)entity.Entity).ModifiedBy = _currentUserService.UserId;
((BaseModel)entity.Entity).LastModified = dt;
}
}
return base.SaveChangesAsync(cancellationToken);
}

MVC An entity object cannot be referenced by multiple instances of IEntityChangeTracker

I've got a model that represents a joint table (with payload) in my database:
public class UserHasCar
{
// Foreign keys
[Key, Column(Order = 0)]
public string ApplicationUserId { get; set; }
[Key, Column(Order = 1)]
public int CarId { get; set; }
// Navigation properties
[Required]
public virtual ApplicationUser ApplicationUser { get; set; }
[Required]
public virtual Car Car{ get; set; }
// Additional fields
public int YearsRidden { get; set; }
}
public class Car
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<UserHasCar> UserHasCars { get; set; }
}
public class ApplicationUser : IdentityUser
{
public int BirthYear{ get; set; }
public virtual ICollection<UserHasCar> UserHasCars { get; set; }
}
I have a form that includes multiple select boxes, and upon submitting I want to clear out all records related to that user who submitted the form in the UserHasCar table and replace them with the new updated information. I'm getting a An entity object cannot be referenced by multiple instances of IEntityChangeTracker. because I am doing something wrong, but I don't see where I am using more than one context. This code happens in my controller:
public ApplicationUser GetCurrentUser()
{
return UserManager.FindById(User.Identity.GetUserId());
}
public string GetUserId()
{
string id = User.Identity.GetUserId();
var user = UserManager.FindById(id);
return user.Id;
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ManageCars(FormCollection form)
{
string id = GetUserId();
// Remove cars records with my id from database
var queryCars = (from m in db.UserHasCars where m.ApplicationUserId == id select m).ToList();
foreach (var record in queryCars )
{
// Could the problem be here?
db.UserHasCars.Remove(record)
}
// Add user-submitted cars to the database
string carval = form["Cars[0]"];
Car car = (from m in db.Cars where m.Name == carval select m).First();
int carid = car.ID;
// I get the abovementioned title error here
db.UserHasCars.Add(
new UserHasCar()
{
ApplicationUser = GetCurrentUser(),
ApplicationUserId = id,
Car = car,
CarId = carid,
YearsRidden = 0
}
);
db.SaveChanges();
}
I've seen many SO posts, but can't seem the problem as why my code doesn't want to save the new database entries.
EDIT
The solution was to remove the call to get the user and replace it with a query. Why? I was making database conflict errors by having both types of calls (database and DataManager calls in the same controller action). I ended up using a modified GetUser() function instead of GetCurrentUser()
Code:
public ApplicationUser GetUser()
{
// As opposed to:
// UserManager.FindById(User.Identity.GetUserId())
// We make a database call to grab our user instead
// So we don't get database context conflicts by using UserManager
string id = GetUserId();
return db.Users.Where(m => m.Id == id).First();
}
public string GetUserId()
{
return User.Identity.GetUserId();
}
// snip
// in ManageCars(FormCollection form)
ApplicationUser user = GetUser();
// snip
var newRow = db.UserHasCars.Create();
newRow.ApplicationUser = user;
// snip
db.UserHasCars.Add(newRow);
Try removing this line:
ApplicationUser = GetCurrentUser(),
from your object instantiation when adding.
Entity populates this object automatically once you set the foreign key ApplicationUserId. If UserManager.FindById(User.Identity.GetUserId()) uses a different db context that's where your exception is coming from.
Also to save yourself further trouble down the line, you should always call db.SaveChanges() in between the two operations. If you're worried about the atomicity of the db operation, just wrap the whole thing in a Transaction.
And when adding new rows to a table, I usually prefer to use something like:
var newRow = db.SomeTable.Create();
newRow.SomeColumn1 = "something";
newRow.SomeColumn2 = 5;
db.SomeTable.Add(newRow);
db.SaveChanges();
In order to delete entries from UserHasCars you need to change their EntityState to Deleted.
Example:
foreach (var record in queryCars )
{
db.ObjectStateManager.ChangeObjectState(record, EntityState.Deleted);
}
Hope this will fix your issue.

Entity Framework 6 - child property data not loading

The ManagingAgent child property on the Complex entity is not being loaded with data.... possibly the result of too much mulled wine.
I have logged the SQL on the database calls and the SQL is returning the correct data.
LazyLoading is disabled.
public ApplicationDbContext()
: base("DefaultConnection")
{
this.Configuration.LazyLoadingEnabled = false;
}
Aggregate Root
public class Complex
{
public Complex()
{
Forums = new List<Forum>();
ManagingAgent = new ManagingAgent();
}
[Key]
public int ComplexId { get; set; }
[Required]
public string Name { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3 { get; set; }
public int? PostCodeId { get; set; }
public PostCode PostCode { get; set; }
public int? LocationId { get; set; }
public Location Location { get; set; }
public int? CountyId { get; set; }
public County County { get; set; }
public int? ManagingAgentId { get; set; }
public ManagingAgent ManagingAgent { get; set; }
public int? CountOfUnits { get; set; }
public List<Forum> Forums { get; set; }
}
Attempt 1. using Include...
public List<Complex> GetComplexesByUserId(Guid userId)
{
using (var db = new ApplicationDbContext())
{
db.Database.Log = Logger;
var complexIds = db.UserApartments.Where(r => r.UserId == userId)
.Select(c => c.ComplexId).ToList();
return db.Complexes.Where(c => complexIds.Contains(c.ComplexId))
.Include(m => m.ManagingAgent).ToList();
}
}
Attempt 2 - explicitly loading ..same result (SQL returns data correctly but ManagingAgent isn't populated)
public List<Complex> GetComplexesByUserId(Guid userId)
{
using (var db = new ApplicationDbContext())
{
db.Database.Log = Logger;
var complexIds = db.UserApartments.Where(r => r.UserId == userId)
.Select(c => c.ComplexId).ToList();
var list = new List<Complex>();
foreach (var id in complexIds)
{
var complex = db.Complexes.Find(id);
db.Entry(complex).Reference(m => m.ManagingAgent).Load();
list.Add(complex);
}
return list;
}
}
So, to force the load I am doing this.... not good..
foreach (var id in complexIds)
{
var complex = db.Complexes.Find(id);
var managingAgent = db.ManagingAgents.Find(complex.ManagingAgentId);
complex.ManagingAgent = managingAgent;
list.Add(complex);
}
Remove this line...
ManagingAgent = new ManagingAgent();
...from the constructor of the Complex entity. Then it will work. (Generally don't instantiate reference navigation properties in an entity default constructor. EF calls this constructor via reflection when it materializes the entity and "gets confused" if the navigation property already has a reference. I can't explain the "gets confused" better since I don't know the exact mechanism of object materialization with related entities, but the effect is that the loaded child column values are ignored because there is already an instantiated child entity, but just with the useless default values from the ManagingAgent constructor.)

RavenDB issue storing object that inherits List<T>

I have a class that inherits from List, this class has some additional properties on it. When I store the parent document in RavenDB, the list items get stored but the additional properties do not.
The failing test below probably explains my issue better:
[TestFixture]
public class RDBIssueTests
{
private DocumentStore _documentStore;
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
_documentStore = new EmbeddableDocumentStore
{
RunInMemory = true,
UseEmbeddedHttpServer = true,
DataDirectory = "Data",
};
_documentStore.Initialize();
}
[Test]
public void StoreSimpleDataTest()
{
string id = "people/1";
string laptopName = "MacPro";
string personName = "Joe Bloggs";
string attrOne = "Screen";
string attrTwo = "Keyboard";
var person = new Person()
{
Id = id,
Name = personName,
Laptop = new Possession<string>()
};
person.Laptop.Name = laptopName;
person.Laptop.Add(attrOne);
person.Laptop.Add(attrTwo);
using (var session = _documentStore.OpenSession())
{
session.Store(person);
session.SaveChanges();
}
using (var session = _documentStore.OpenSession())
{
var loadedPerson = session.Load<Person>(id);
Assert.AreEqual(personName, loadedPerson.Name);
Assert.AreEqual(2, loadedPerson.Laptop.Count); // 2 items in the list
Assert.IsTrue(loadedPerson.Laptop.Contains(attrOne));
Assert.IsTrue(loadedPerson.Laptop.Contains(attrTwo));
Assert.AreEqual(laptopName, loadedPerson.Laptop.Name); // fails here - Person.Laptop.Name is not persisted in RBD
}
}
}
public class Person
{
public string Id { get; set; }
public string Name { get; set; }
public Possession<string> Laptop { get; set; }
}
public class Possession<TValueType> : PossessionAttribute<TValueType>
{
public string Name { get; set; } // RDB doesn't persist this value
}
public class PossessionAttribute<TValueType> : List<TValueType>
{
}
As you can see from the test, the string property 'Name' on the Possession class does not get saved.
Is there something I need to do to get this property to persist?
Many thanks!
JSON has no way of representing an object that is both a list and has properties.
That is why you cannot do that. You can have an object that contains a list property, which is a more natural way of going about it.

Objects not getting saved properly in Database

[Test]
public void Artist_gets_stored_properly()
{
using (ISession session = NHHelper.GetSession())
{
Artist artist = new Artist() { Name = "Somathilaka Jayamaha" };
artist.Songs = new List<Song>()
{
new Song(){Artist = artist, Name = "Manamaala girawu"},
new Song(){Artist = artist, Name = "Sende andura"},
new Song(){Artist = artist, Name = "Sunilwan nuwan"}
};
foreach (var s in artist.Songs)
{
session.Save(s);
}
session.Save(artist);
}
using (ISession session = NHHelper.GetSession())
{
Artist artist = session.Query<Artist>().Single(x => x.Name == "Somathilaka Jayamaha");
Assert.AreEqual(3, artist.Songs.Count);
Assert.AreEqual("Sende andura", artist.Songs[1].Name);
}
}
public class Artist
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Song> Songs { get; set; }
}
public class Song
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string WebPath { get; set; }
public virtual string FilePath { get; set; }
public virtual bool Downloaded { get; set; }
public virtual Artist Artist { get; set; }
void Download(IDownloader downloader)
{
}
}
I have the above test case and it fails at this line : Assert.AreEqual(3, artist.Songs.Count);. The songs do not get saved it seems. I use automapping and have Cascade.All() for the collection fields in mapping overrides and have lazyloading off. I can't understand why this test fails. As you can see, I manually saved the 3 songs as well though as I understand it, I don't need to do that when I have Cascade.All() for Artist.Songs field. Can someone tell me what I am doing wrong? Thanks.
MS SQLServer 2005, .NET 3.5, FluentNHibernate 1.2.0.712
You are not using a transaction, and you are never flushing your session.
The only reason you are getting the inserts is because you are using an identity generator, which inserts when you call Save (this is a limitation, not a feature).
The correct way to to this:
using (ISession session = NHHelper.GetSession())
using (var transaction = session.BeginTransaction())
{
Artist artist = new Artist { Name = "Somathilaka Jayamaha" };
artist.Songs = new List<Song>()
{
new Song{Artist = artist, Name = "Manamaala girawu"},
new Song{Artist = artist, Name = "Sende andura"},
new Song{Artist = artist, Name = "Sunilwan nuwan"}
};
session.Save(artist);
transaction.Commit();
}