Entity Framework Attaching a Persisted Object to the New Object - entity-framework

I am trying to perform a very simple task which is "Add the user with role in the database". The roles are already populated in the database and I am simply adding the role to the User roles collection but it keeps throwing the following exception:
The EntityKey property can only be set when the current value of the property is null.
Here is the code in User.cs:
public void AddRole(Role role)
{
if (!Exists(role))
{
role.User = this;
Roles.Add(role);
}
}
And here is the test that fails:
[Test]
public void should_save_user_with_role_successfully()
{
var _role = _roleRepository.GetByName("Student");
_user.AddRole(_role);
_userRepository.Save(_user);
Assert.IsTrue(_user.UserId > 0);
}
The Repository Code:
public bool Save(User user)
{
bool isSaved = false;
using (var db = new EStudyDevDatabaseEntities())
{
db.AddToUsers(user);
isSaved = db.SaveChanges() > 0;
}
return isSaved;
}
Here is the AddRole Method:
public bool Exists(Role role)
{
var assignedRole = (from r in Roles
where r.RoleName.Equals(role.RoleName)
select r).SingleOrDefault();
if (assignedRole != null) return true;
return false;
}
public void AddRole(Role role)
{
if (!Exists(role))
{
role.User = this;
Roles.Add(role);
}
}
And here is the whole exception:
------ Test started: Assembly: EStudy.Repositories.TestSuite.dll ------
TestCase 'EStudy.Repositories.TestSuite.Repositories.when_saving_new_user.should_save_user_with_role_successfully'
failed: System.InvalidOperationException : The EntityKey property can only be set when the current value of the property is null.
at System.Data.Objects.EntityEntry.GetAndValidateChangeMemberInfo(String entityMemberName, Object complexObject, String complexObjectMemberName, StateManagerTypeMetadata& typeMetadata, String& changingMemberName, Object& changingObject)
at System.Data.Objects.EntityEntry.EntityMemberChanging(String entityMemberName, Object complexObject, String complexObjectMemberName)
at System.Data.Objects.EntityEntry.EntityMemberChanging(String entityMemberName)
at System.Data.Objects.ObjectStateEntry.System.Data.Objects.DataClasses.IEntityChangeTracker.EntityMemberChanging(String entityMemberName)
at System.Data.Objects.DataClasses.EntityObject.set_EntityKey(EntityKey value)
at System.Data.Objects.Internal.LightweightEntityWrapper`1.set_EntityKey(EntityKey value)
at System.Data.Objects.ObjectStateManager.AddEntry(IEntityWrapper wrappedObject, EntityKey passedKey, EntitySet entitySet, String argumentName, Boolean isAdded)
at System.Data.Objects.ObjectContext.AddSingleObject(EntitySet entitySet, IEntityWrapper wrappedEntity, String argumentName)
at System.Data.Objects.DataClasses.RelatedEnd.AddEntityToObjectStateManager(IEntityWrapper wrappedEntity, Boolean doAttach)
at System.Data.Objects.DataClasses.RelatedEnd.AddGraphToObjectStateManager(IEntityWrapper wrappedEntity, Boolean relationshipAlreadyExists, Boolean addRelationshipAsUnchanged, Boolean doAttach)
at System.Data.Objects.DataClasses.RelatedEnd.IncludeEntity(IEntityWrapper wrappedEntity, Boolean addRelationshipAsUnchanged, Boolean doAttach)
at System.Data.Objects.DataClasses.EntityCollection`1.Include(Boolean addRelationshipAsUnchanged, Boolean doAttach)
at System.Data.Objects.DataClasses.RelationshipManager.AddRelatedEntitiesToObjectStateManager(Boolean doAttach)
at System.Data.Objects.ObjectContext.AddObject(String entitySetName, Object entity)
C:\Projects\EStudy\EStudySolution\EStudy.BusinessObjects\Entities\EStudyModel.Designer.cs(97,0): at EStudy.BusinessObjects.Entities.EStudyDevDatabaseEntities.AddToUsers(User user)
C:\Projects\EStudy\EStudySolution\EStudy.BusinessObjects\Repositories\UserRepository.cs(17,0): at EStudy.BusinessObjects.Repositories.UserRepository.Save(User user)
C:\Projects\EStudy\EStudySolution\EStudy.Repositories.TestSuite\Repositories\Test_UserRepository.cs(47,0): at EStudy.Repositories.TestSuite.Repositories.when_saving_new_user.should_save_user_with_role_successfully()
0 passed, 1 failed, 0 skipped, took 6.07 seconds (NUnit 2.5).
UPDATE:
Here is my UserRepository and RoleRepository and they both uses separate contexts:
public bool Save(User user)
{
bool isSaved = false;
using (var db = new EStudyDevDatabaseEntities())
{
db.AddToUsers(user);
isSaved = db.SaveChanges() > 0;
}
return isSaved;
}
public Role GetByName(string roleName)
{
using (var db = new EStudyDevDatabaseEntities())
{
return db.Roles.SingleOrDefault(x => x.RoleName.ToLower().Equals(roleName.ToLower()));
}
}
As, you can see the user and the role are using different context which you have already pointed out. The problem with using single datacontext is that I cannot layer the application properly.

Updated again based on updated question
I disagree that you "can't layer the application properly" when you share a context between repositories. It's a problem that you need to solve, but it's most certainly solvable. Also, I think you will find it considerably easier to solve than the number of problems which you create when you attempt to use multiple contexts.
At any rate, there are really only two possible solutions to your problem:
Manually keep track of which context a particular entity is attached to, and transfer it (with Attach and Detach), when necessary.
Share a Context between repository instances.
In our ASP.NET MVC applications, the logical unit of work is a single Request. Therefore, we instantiate an ObjectContext at the beginning of a request, Dispose it at the end of a request, and inject it into new repositories when we create them. Repository instances never outlast a single request.
Update based on updated question
Does the role repository and the user repository each have a (separate) context? Here's what's going on in the stack trace:
You add the User to the context.
The RelationshipManager goes through the User and ensures that any related entities are also in the context. This involves, among other things, setting their EntityKey property.
Presuming that the Role came from a different context (which appears to be the case, since otherwise the context should detect that the role is already in the context), you should see an error indicating that you cannot add an entity attached to one context into another context. For some reason, you're not seeing that here. But nevertheless, it's not a valid operation.
Instead, you get an error when the EntityKey of the role is assigned.
In my opinion, using a single ObjectContext at a time should be the general rule for working with the EntityFramework. You should use multiple contexts only when you're absolutely forced to, which, in my experience, is almost never. Working with multiple ObjectContexts concurrently is significantly harder than working with one at a time.
OK, I don't know the details of your mapping, but I would expect AddRole to be something more along the lines of:
public void AddRole(Role role)
{
this.Roles.Add(role);
}
... if User->Role is .. or:
public void AddRole(Role role)
{
this.Role = role;
}
if User -> Role is *..1.
If this doesn't help, please post the stack trace for the exception.

Related

Specify connection string for a query with DbContextScope project

I am currently using Mehdi El Gueddari's DbContextScope project, I think by the book, and it's awesome. But I came across a problem I'm unsure how to solve today. I have a query that I need to execute using a different database login/user because it requires additional permissions. I can create another connection string in my web.config, but I'm not sure how to specify that for this query, I want to use this new connection string. Here is my usage:
In my logic layer:
private static IDbContextScopeFactory _dbContextFactory = new DbContextScopeFactory();
public static Guid GetFacilityID(string altID)
{
...
using (_dbContextFactory.CreateReadOnly())
{
entity = entities.GetFacilityID(altID)
}
}
That calls into my data layer which would look something like this:
private AmbientDbContextLocator _dbcLocator = new AmbientDbContextLocator();
protected CRMEntities DBContext
{
get
{
var dbContext = _dbcLocator.Get<CRMEntities>();
if (dbContext == null)
throw new InvalidOperationException("No ambient DbContext....");
return dbContext;
}
}
public virtual Guid GetFaciltyID(string altID)
{
return DBContext.Set<Facility>().Where(f => f.altID = altID).Select(f => f.ID).FirstOrDefault();
}
Currently my connection string is set in the default way:
public partial class CRMEntities : DbContext
{
public CRMEntities()
: base("name=CRMEntities")
{}
}
Is it possible for this specific query to use a different connection string and how?
I ended up modifying the source code in a way that feels slightly hacky, but is getting the job done for now. I created a new IAmbientDbContextLocator with a Get<TDbContext> method override that accepts a connection string:
public TDbContext Get<TDbContext>(string nameOrConnectionString) where TDbContext : DbContext
{
var ambientDbContextScope = DbContextScope.GetAmbientScope();
return ambientDbContextScope == null ? null : ambientDbContextScope.DbContexts.Get<TDbContext>(nameOrConnectionString);
}
Then I updated the DbContextCollection to pass this parameter to the DbContext's existing constructor overload. Last, I updated the DbContextCollection maintain a Dictionary<KeyValuePair<Type, string>, DbContext> instead of a Dictionary<Type, DbContext> as its cached _initializedDbContexts where the added string is the nameOrConnectionString param. So in other words, I updated it to cache unique DbContext type/connection string pairs.
Then I can get at the DbContext with the connection I need like this:
var dbContext = new CustomAmbientDbContextLocator().Get<CRMEntities>("name=CRMEntitiesAdmin");
Of course you'd have to be careful your code doesn't end up going through two different contexts/connection strings when it should be going through the same one. In my case I have them separated into two different data access class implementations.

EF Core Update The instance of entity type 'Ads' cannot be tracked

I try to implement a XUnit Test for Asp.net Core DBContext, But I got below error.
Message: System.InvalidOperationException : The instance of entity type 'Ads' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.
Here is my current code:
public class AdsServiceTest
{
private readonly DbContextOptions<SensingSiteDbContext> _options;
private readonly SensingSiteDbContext _context;
private readonly AdsService _AdsService;
public AdsServiceTest()
{
//initialize db options
_options = new DbContextOptionsBuilder<SensingSiteDbContext>()
.UseInMemoryDatabase()
.Options;
//get service
_context = new SensingSiteDbContext(_options);
//initialize dbcontext
List<Ads> listAds = new List<Ads>() {
new Ads(){ Id=1,AdsName="Ads1", Deleted=false},
new Ads(){ Id=2,AdsName="Ads1", Deleted=false},
new Ads(){ Id=3,AdsName="Ads1", Deleted=false}
};
_context.Advertisements.AddRange(listAds);
//context.Advertisements
BaseLib.SSDbContext<Ads, AdsService> ssDbContent = new BaseLib.SSDbContext<Ads, AdsService>(_context);
_AdsService = ssDbContent.GetService((x, y) => new AdsService(x, y));
}
[Theory]
[InlineData(1)]
public void FindById(int id)
{
Ads adsResult = _AdsService.FindById(id);
Ads adsTarget = _context.Advertisements.Find(adsResult.Id);
Assert.True(adsTarget.Equals(adsResult));
}
//Failed by error System.InvalidOperationException : The instance of entity type 'Ads' cannot be tracked because another instance of this type with the same key is already being tracked
[Fact]
public void Update()
{
Ads adsResult = new Ads() { Id = 1, AdsName = "UpdateAds1" };
_AdsService.UpdateAds(adsResult);
Ads adsTarget = _context.Advertisements.Find(adsResult.Id);
Assert.True(adsTarget.Equals(adsResult));
}
}
There is no problem for Find, but failed on Update. AdsService is implemented to call SensingSiteDbContext. It seems I need to use scope lifetime for SensingSiteDbContext. But, I do not know how to implement it.
I have changed ObjectState for Update.
public virtual void Update(TEntity entity)
{
entity.ObjectState = ObjectState.Modified;
_dbSet.Update(entity);
_context.SyncObjectState(entity);
}
Any help would be appreciated.
You are new'ing up your own entity, when, you should just get that entity you've already added from the context:
Ads adsResult = new Ads() { Id = 1, AdsName = "UpdateAds1" };
_AdsService.UpdateAds(adsResult);
With this code, Entity Framework is saying, "Hey, I already have an entity with that key (check your constructor, you're putting an entity in with that same Id), but this object; I don't know what to do with it (because it came from outside with a key that already exists)".
You can change it to exactly what you're doing in the previous test:
Ads adsResult = _AdsService.FindById(id);
//do your changing here
_AdsService.UpdateAds(adsResult);

DaoException: Entity is detached from DAO context

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.

Are EF code-first models intended to fully describe a database's structure?

I'm a little confused as to the purpose of a data model in Entity Framework code-first. Because EF will auto-generate a database from scratch for you if it doesn't already exist using nothing more than the data model (including data annotations and Fluent API stuff in DbContext.OnModelCreating), I was assuming that the data model should fully describe your database's structure, and you wouldn't need to modify anything fundamental after that.
However, I came across this Codeplex issue in which one of the EF Triage Team members suggests that custom indexes be added in data migrations, but not as annotations to your data model fields, or Fluent API code.
But wouldn't that mean that anyone auto-generating the database from scratch would not get those custom indexes added to their DB? The assumption seems to be that once you start using data migrations, you're never going to create the database from scratch again. What if you're working in a team and a new team member comes along with a new SQL Server install? Are you expected to copy over a database from another team member? What if you want to start using a new DBMS, like Postgres? I thought one of the cool things about EF was that it was DBMS-independent, but if you're no longer able to create the database from scratch, you can no longer do things in a DBMS-independent way.
For the reasons I outlined above, wouldn't adding custom indexes in a data migration but not in the data model be a bad idea? For that matter, wouldn't adding any DB structure changes in a migration but not in the data model be a bad idea?
Are EF code-first models intended to fully describe a database's structure?
No, they don't fully describe the database structure or schema.Still there are methods to make the database fully described using EF. They are as below:
You can use the new CTP5’s ExecuteSqlCommand method on Database class which allows raw SQL commands to be executed against the database.
The best place to invoke SqlCommand method for this purpose is inside a Seed method that has been overridden in a custom Initializer class. For example:
protected override void Seed(EntityMappingContext context)
{
context.Database.ExecuteSqlCommand("CREATE INDEX IX_NAME ON ...");
}
You can even add Unique Constraints this way.
It is not a workaround, but will be enforced as the database will be generated.
OR
If you are badly in need of the attribute, then here it goes
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
public class IndexAttribute : Attribute
{
public IndexAttribute(string name, bool unique = false)
{
this.Name = name;
this.IsUnique = unique;
}
public string Name { get; private set; }
public bool IsUnique { get; private set; }
}
After this , you will have an initializer which you will call in your OnModelCreating method as below:
public class IndexInitializer<T> : IDatabaseInitializer<T> where T : DbContext
{
private const string CreateIndexQueryTemplate = "CREATE {unique} INDEX {indexName} ON {tableName} ({columnName});";
public void InitializeDatabase(T context)
{
const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance;
Dictionary<IndexAttribute, List<string>> indexes = new Dictionary<IndexAttribute, List<string>>();
string query = string.Empty;
foreach (var dataSetProperty in typeof(T).GetProperties(PublicInstance).Where(p => p.PropertyType.Name == typeof(DbSet<>).Name))
{
var entityType = dataSetProperty.PropertyType.GetGenericArguments().Single();
TableAttribute[] tableAttributes = (TableAttribute[])entityType.GetCustomAttributes(typeof(TableAttribute), false);
indexes.Clear();
string tableName = tableAttributes.Length != 0 ? tableAttributes[0].Name : dataSetProperty.Name;
foreach (PropertyInfo property in entityType.GetProperties(PublicInstance))
{
IndexAttribute[] indexAttributes = (IndexAttribute[])property.GetCustomAttributes(typeof(IndexAttribute), false);
NotMappedAttribute[] notMappedAttributes = (NotMappedAttribute[])property.GetCustomAttributes(typeof(NotMappedAttribute), false);
if (indexAttributes.Length > 0 && notMappedAttributes.Length == 0)
{
ColumnAttribute[] columnAttributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), false);
foreach (IndexAttribute indexAttribute in indexAttributes)
{
if (!indexes.ContainsKey(indexAttribute))
{
indexes.Add(indexAttribute, new List<string>());
}
if (property.PropertyType.IsValueType || property.PropertyType == typeof(string))
{
string columnName = columnAttributes.Length != 0 ? columnAttributes[0].Name : property.Name;
indexes[indexAttribute].Add(columnName);
}
else
{
indexes[indexAttribute].Add(property.PropertyType.Name + "_" + GetKeyName(property.PropertyType));
}
}
}
}
foreach (IndexAttribute indexAttribute in indexes.Keys)
{
query += CreateIndexQueryTemplate.Replace("{indexName}", indexAttribute.Name)
.Replace("{tableName}", tableName)
.Replace("{columnName}", string.Join(", ", indexes[indexAttribute].ToArray()))
.Replace("{unique}", indexAttribute.IsUnique ? "UNIQUE" : string.Empty);
}
}
if (context.Database.CreateIfNotExists())
{
context.Database.ExecuteSqlCommand(query);
}
}
private string GetKeyName(Type type)
{
PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo propertyInfo in propertyInfos)
{
if (propertyInfo.GetCustomAttribute(typeof(KeyAttribute), true) != null)
return propertyInfo.Name;
}
throw new Exception("No property was found with the attribute Key");
}
}
Then overload OnModelCreating in your DbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer(new IndexInitializer<MyContext>());
base.OnModelCreating(modelBuilder);
}
Apply the index attribute to your Entity type, with this solution you can have multiple fields in the same index just use the same name and unique.
OR
You can do the migrations later on.
Note:
I have taken a lot of this code from here.
The question seems to be if there is value in having migrations added mid-stream, or if those will cause problems for future database initializations on different machines.
The initial migration that is created also contains the entire data model as it exists, so by adding migrations (enable-migrations in the Package Manager Console) you are, in effect, creating the built-in mechanism for your database to be properly created down the road for other developers.
If you're doing this, I do recommend modifying the database initialization strategy to run all your existing migrations, lest EF should start up and get the next dev's database out of sync.
Something like this would work:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<YourNamespace.YourDataContext, Migrations.Configuration>());
So, no, this won't inherently introduce problems for future work/developers. Remember that migrations are just turned into valid SQL that executes against the database...you can even use script mode to output the TSQL required to make the DB modifications based on anything in the migrations you have created.
Cheers.

unable to query EntityFramework shared dbcontext reliably

I'm trying to share a simple DbContext with 4 DbSets among multiple repositories, each of my repositories inherit from this base class
public class CodeFirstRepository : IDisposable
{
private static MyContext _ctx = new MyContext();
protected MyContext Context
{
get { return _ctx; }
}
public void Dispose()
{
if (Context != null)
{
Context.Dispose();
}
}
}
Question: is this an appropriate way to share a connection between repositories?
I'm getting intermittent failures in my unit tests when accessing the various repositories. An exception is thrown from the repository method GetEntityByName
public IOfferResult GetEntityByName(string name)
{
return Context.Entities.Where(o => o.Name == name).FirstOrDefault()
}
Test method
Tests.Service.TestDelete
threw exception: System.ObjectDisposedException: The ObjectContext
instance has been disposed and can no longer be used for operations
that require a connection.
if the database already exists, the code executes as expected. it also works when i change the implementation of GetEntityByName(string name) to the following non-performant code
public IOfferResult GetEntityByName(string name)
{
foreach (OfferResult offer in Context.Offers)
{
if (offerName.ToLower() == offer.Name.ToLower())
{
return offer;
}
}
}
Question: what is going on here?
bear in mind that if the database exists when i run the tests i don't get the error at all.
tia,
jt
This problem is arising because you are treating the DbContext like a singleton by declaring it as a static field, but then you are treating it like it like a transient instance by disposing it as soon as any instance of CodeFirstRepository gets disposed. For example:
using (var r = new PersonRepository())
{
// do something
} // When you hit the end of this block, your static DbContext is disposed.
using (var r = new IOfferRepository())
{
r.GetEntityByName("test"); // this will fail because the context is still disposed.
}
You should not share contexts this way. If you really want all of your repositories to use a single instance of the DbContext, remove the call to Context.Dispose(). This would fix the problem you're getting right now, but it will likely introduce other problems in the future.
But I would strongly caution against using a single DbContext in a scenario where multiple threads could be trying to access it simultaneously. According to the DbContext specs:
Any instance members are not guaranteed to be thread safe.
You'd be better off just removing the static keyword from your field.