I have two entities, User and Store. User has many Stores (1:M) relation. I've inserted some stores list into the store table by following code.
public void saveStoresToDatabase(Context context, ArrayList<Store> storeList) {
DevOpenHelper helper = new DaoMaster.DevOpenHelper(context, "notes-db", null);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
DaoSession daoSession = daoMaster.newSession();
StoreDao storeDao = daoSession.getStoreDao();
ArrayList <Store> list = SharedData.getInstance().getUser().getStoreList();
for(int i = 0; i < storeList.size(); i++) {
storeList.get(i).setUserIdForStore(SharedData.getInstance().getUser().getId());
}
storeDao.insertOrReplaceInTx(storeList);
list.addAll(storeList);
user.resetStoreList();
}
I am getting "entity is detached from DAO context" exception whenever I try call user.getStoreList(). The exception occurs at following code sniped as the daoSession is null.
public ArrayList<Store> getDMStoreListFromDatabase(Context context) {
return SharedData.getInstance().getUser().getStoreList();
}
where SharedData is my singleton, having a user object:
private SharedData() {
user = new User();
}
and I get the sharedData instance as follow:
public static synchronized SharedData getInstance() {
if (sharedObject == null) {
sharedObject = new SharedData();
}
return sharedObject;
}
Objects representing database entries (like User) are only attached to a Database-session if they have been fetched from the database or inserted to the database before.
It looks like you don't load your user-object using greendao, but instead just create it with new.
You also seem not to store this user-object using the dao. Thus the user-object is not attached to the session.
On top of that you are also just setting the userid in each store. If you haven't inserted the user-object somewhere else this may also cause an error since the foreignkey-constraint may be broken (depending on how greendao handles this internally).
Try to add the user-object to the stores with setUser() instead of setUserIdForStore().
If this doesn't work try to store or load the user-object first using a UserDao.
Related
I'm using Xamarin.Forms with EF and SqLite. I've installed the "Microsoft.EntityFrameworkCore.Sqlite" Nuget package in my project. The code issue is in the shared code project, .NetStandard 2.0.
I have created a simple class, let's say CAT class to hold my DB table objects
I can use the "ensurecreated" command and that works fine
I can create a CAT, set properties and SaveChanges() to the DB; this works fine, I can see the data in the DB
I cannot get the data back out; I get an "object not set to a reference..." error.
Ignore my couple of outer curly braces; new to posting code and only way to get it all together in one block. I have handled the platform-specific (Android & iOS) code for obtaining the dbPath to the SqLite .db3 file (not shown here).
Cannot figure what I'm missing that no data will come back out of DB. Any help much appreciated!
{
public class DatabaseContext : DbContext
{
string _dbPath;
public DbSet<Cat> Cats { get; set; }
public DatabaseContext(string dbPath)
{
_dbPath = dbPath;
Database.EnsureCreatedAsync();
}
public async Task<IEnumerable<Cat>> GetCats()
{
var allCats = await Cats.ToListAsync().ConfigureAwait(false);
return allCats;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite($"Filename={_dbPath}");
}
}
List<Cat> itemSource;
// Create Database & Tables
using (var db = new DatabaseContext(App.dbPath))
{
// Ensure database is created
db.Database.EnsureCreated();
// Insert Data
db.Add(new Cat() { IdCat = "111", Fname = "Felix1" });
db.SaveChanges();
// Retreive Data
//method 1
// RESULT: no data are in "itemsource", info reads "exception count = 1"
itemSource = db.Cats.ToList();
// method 2
// RESULTS: crashes with error "System.NullReferenceException: Object reference not set to an instance of an object."
Task<IEnumerable<Cat>> p = db.GetCats();
itemSource = db.Cats.ToList();
}
}
We are using the Entity Framework 4.3.1 with POCO classes (no proxies).
Our User class stores an email address which should be not be used by any other user.
The following test is used to check the validation of this:
[TestMethod]
public void UserEmailNotUniqueTest()
{
// arrange
// - create user one and store in db
User userOne = TIF.GetUser(model, true);
// - create user two and store in db
User userTwo = TIF.GetUserTwo(model, true);
// act
// - change user one email to user two email
userOne.EmailAddress = userTwo.EmailAddress;
// - save
model.SaveChanges();
The test initialize creates an empty database and hooks it up to the “model” DbContext decendant. The TIF class creates the test instances of users and stores them in database. This works, both users are present in the database.
This validation requires the state of the database to be unchanged, so we have an overridden SaveChanges method so we can pass in serializable transaction:
public virtual int SaveChanges(bool commitWhenDone)
{
try
{
saving = true;
int result;
TransactionScope scope;
using (scope = new TransactionScope( modelTransaction.Get() ))
{
//… open connection etc.
ChangeTracker.DetectChanges();
IEnumerable<DbEntityValidationResult> validationResults =
GetValidationErrors();
//… handle the errors
result = base.SaveChanges();
The test fails as not any change is detected. No validation code is executed. Inspecting model.Entity(userOne).State returns “Unchanged”, and yet the CurrentValues contains the proper value i.e. the userOne has the email address of userTwo.
What are we missing?
I am using EF 4.1 in a MVC 3 project and I am having problems with inserting an object. I am using session as well to hold onto some objects. In concrete :
I have a simple class with a parent child relationship:
public class Event
{
public User Promotor {get;set;}
}
The promotor is based on the CurrentUser. In my application I store the CurrentUser in http session. Now when I add an event like this the user (and all related objects) gets inserted one more time with a new primary key.
//1st request inserts/loads the user
User user;
using (var context = new MyDbContext())
{
user = new User();
context.Users.Add(user);
context.SaveChanges();
}
//2nd request saves the event
var before = db.Users.Count();
var #event = new Event
{
Promotor = user, //user was kept in Session
};
db.Entry(#event).State = EntityState.Added;
db.SaveChanges();
When i check the state of the user it is 'added' as well although the primary key is not 0 and EF should know it is already persistent. How can fix this without adding a lot of to my persistency code. Do I have to reattach my currentuser to the new dbcontext on every request? This will lead to db code 'leaking' into my application. I want to keep the DB stuff in a data layer. I am using a repository like this :
public void Save(T entity)
{
dbContext.Entry(entity).State = IsPersistent(entity) ?
EntityState.Modified : EntityState.Added;
dbContext.SaveChanges();
}
As you've already mentioned yourself, reattaching the user to your current context should solve the problem. The only other way I know of, would be to retrieve the object once again in your context based on the primary key value.
My database has a 'LastModifiedUser' column on every table in which I intend to collect the logged in user from an application who makes a change. I am not talking about the database user so essentially this is just a string on each entity. I would like to find a way to default this for each entity so that other developers don't have to remember to assign it any time they instantiate the entity.
So something like this would occur:
using (EntityContext ctx = new EntityContext())
{
MyEntity foo = new MyEntity();
// Trying to avoid having the following line every time
// a new entity is created/added.
foo.LastModifiedUser = Lookupuser();
ctx.Foos.Addobject(foo);
ctx.SaveChanges();
}
There is a perfect way to accomplish this in EF 4.0 by leveraging ObjectStateManager
First, you need to create a partial class for your ObjectContext and subscribe to
ObjectContext.SavingChanges Event. The best place to subscribe to this event is inside the OnContextCreated Method. This method is called by the context object’s constructor and the constructor overloads which is a partial method with no implementation:
partial void OnContextCreated() {
this.SavingChanges += Context_SavingChanges;
}
Now the actual code that will do the job:
void Context_SavingChanges(object sender, EventArgs e) {
IEnumerable<ObjectStateEntry> objectStateEntries =
from ose
in this.ObjectStateManager.GetObjectStateEntries(EntityState.Added
| EntityState.Modified)
where ose.Entity != null
select ose;
foreach (ObjectStateEntry entry in objectStateEntries) {
ReadOnlyCollection<FieldMetadata> fieldsMetaData = entry.CurrentValues
.DataRecordInfo.FieldMetadata;
FieldMetadata modifiedField = fieldsMetaData
.Where(f => f.FieldType.Name == "LastModifiedUser").FirstOrDefault();
if (modifiedField.FieldType != null) {
string fieldTypeName = modifiedField.FieldType.TypeUsage.EdmType.Name;
if (fieldTypeName == PrimitiveTypeKind.String.ToString()) {
entry.CurrentValues.SetString(modifiedField.Ordinal, Lookupuser());
}
}
}
}
Code Explanation:
This code locates any Added or Modified entries that have a LastModifiedUser property and then updates that property with the value coming from your custom Lookupuser() method.
In the foreach block, the query basically drills into the CurrentValues of each entry. Then, using the Where method, it looks at the names of each FieldMetaData item for that entry, picking up only those whose Name is LastModifiedUser. Next, the if statement verifies that the LastModifiedUser property is a String field; then it updates the field's value.
Another way to hook up this method (instead of subscribing to SavingChanges event) is by overriding the ObjectContext.SaveChanges Method.
By the way, the above code belongs to Julie Lerman from her Programming Entity Framework book.
EDIT for Self Tracking POCO Implementation:
If you have self tracking POCOs then what I would do is that I first change the T4 template to call the OnContextCreated() method. If you look at your ObjectContext.tt file, there is an Initialize() method that is called by all constructors, therefore a good candidate to call our OnContextCreated() method, so all we need to do is to change ObjectContext.tt file like this:
private void Initialize()
{
// Creating proxies requires the use of the ProxyDataContractResolver and
// may allow lazy loading which can expand the loaded graph during serialization.
ContextOptions.ProxyCreationEnabled = false;
ObjectMaterialized += new ObjectMaterializedEventHandler(HandleObjectMaterialized);
// We call our custom method here:
OnContextCreated();
}
And this will cause our OnContextCreated() to be called upon creation of the Context.
Now if you put your POCOs behind the service boundary, then it means that the ModifiedUserName must come with the rest of data from your WCF service consumer. You can either expose this
LastModifiedUser property to them to update or if it stores in another property and you wish to update LastModifiedUser from that property, then you can modify the 2nd code as follows:
foreach (ObjectStateEntry entry in objectStateEntries) {
ReadOnlyCollection fieldsMetaData = entry.CurrentValues
.DataRecordInfo.FieldMetadata;
FieldMetadata sourceField = fieldsMetaData
.Where(f => f.FieldType.Name == "YourPropertyName").FirstOrDefault();
FieldMetadata modifiedField = fieldsMetaData
.Where(f => f.FieldType.Name == "LastModifiedUser").FirstOrDefault();
if (modifiedField.FieldType != null) {
string fieldTypeName = modifiedField.FieldType.TypeUsage.EdmType.Name;
if (fieldTypeName == PrimitiveTypeKind.String.ToString()) {
entry.CurrentValues.SetString(modifiedField.Ordinal,
entry.CurrentValues[sourceField.Ordinal].ToString());
}
}
}
Hope this helps.
There is a nuget package for this now : https://www.nuget.org/packages/TrackerEnabledDbContext
Github: https://github.com/bilal-fazlani/tracker-enabled-dbcontext
If I run the following code it throws the following error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker
public void Save(Category category)
{
using(var db = new NorthwindContext())
{
if(category.CategoryID == 0)
{
db.AddToCategorySet(category);
}
else
{
//category.RemoveTracker();
db.Attach(category);
}
db.SaveChanges();
}
}
The reason is of course that the category is sent from interface which we got from GetById method which already attached the EntityChangeTracker to the category object. I also tried to set the entity tracker to null but it did not update the category object.
protected void Btn_Update_Category_Click(object sender, EventArgs e)
{
_categoryRepository = new CategoryRepository();
int categoryId = Int32.Parse(txtCategoryId.Text);
var category = _categoryRepository.GetById(categoryId);
category.CategoryName = txtUpdateCategoryName.Text;
_categoryRepository.Save(category);
}
I'm still learning Entity Framework myself, but maybe I can help a little. When working with the Entity Framework, you need to be aware of how you're handling different contexts. It looks like you're trying to localize your context as much as possible by saying:
public void Save(Category category)
{
using (var db = new NorthwindContext())
{
...
}
}
... within your data access method. Did you do the same thing in your GetById method? If so, did you remember to detach the object you got back so that it could be attached later in a different context?
public Category GetById(int categoryId)
{
using (var db = new NorthwindContext())
{
Category category = (from c in db.Category where Category.ID == categoryId select c).First();
db.Detach(category);
}
}
That way when you call Attach it isn't trying to step on an already-attached context. Does that help?
As you pointed out in your comment, this poses a problem when you're trying to modify an item and then tell your database layer to save it, because once an item is detached from its context, it no longer keeps track of the changes that were made to it. There are a few ways I can think of to get around this problem, none of them perfect.
If your architecture supports it, you could expand the scope of your context enough that your Save method could use the same context that your GetById method uses. This helps to avoid the whole attach/detach problem entirely, but it might push your data layer a little closer to your business logic than you would like.
You can load a new instance of the item out of the new context based on its ID, set all of its properties based on the category that is passed in, and then save it. This costs two database round-trips for what should really only need one, and it isn't very maintainable.
You can dig into the context itself to mark the Category's properties as changed.
For example:
public void Save(Category category)
{
using (var db = new NorthwindContext())
{
db.Attach(category);
var stateEntry = db.ObjectStateManager.GetObjectStateEntry(category);
foreach (var propertyName in stateEntry.CurrentValues.DataRecordInfo.FieldMetadata.Select(fm => fm.FieldType.Name)) {
stateEntry.SetModifiedProperty(propertyName);
}
db.SaveChanges();
}
}
This looks a little uglier, but should be more performant and maintainable overall. Plus, if you want, you could make it generic enough to throw into an extension method somewhere so you don't have to see or repeat the ugly code, but you still get the functionality out of it.