2

我使用EF 4.1和Repository以及DbContext .. POCO以及T4模板。 對於每個版本庫,我使用單獨的DbContext。如何從實體框架中的上下文中去除實體?

我需要更新的對象與具有相關屬性,此刻我收到此錯誤

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

我想我的問題是怎麼一回事,因爲eventObjcandidate從不同的存儲庫中創建。

所以我試圖解決這個代碼的問題,沒有成功。

我的問題是?

  • 我該如何擺脫這個錯誤?
  • 是否可以從其上下文中刪除候選人?

    public void UpdateAddingCandidate(Event eventObj, Candidate candidate){ 
        Event updatedEvent = new Event(); 
        Candidate updatedCandidate = new Candidate(); 
        updatedEvent = eventObj; 
        updatedCandidate = candidate; 
        updatedEvent.Candidate = updatedCandidate; 
        db.Entry(updatedEvent).State = EntityState.Modified;  
    } 
    

編輯

public void UpdateAddingCandidate(Event eventObj, Candidate candidate) 
    { 
     /* 
     db.Events.AsNoTracking(); 
     db.Candidates.AsNoTracking(); 
     */ 
     db.Entry(eventObj).State = EntityState.Detached; 
     db.Entry(candidate).State = EntityState.Detached; 

     Event updatedEvent = new Event(); 
     Candidate updatedCandidate = new Candidate(); 
     updatedEvent = eventObj; 
     updatedCandidate = candidate; 
     updatedEvent.Candidate = updatedCandidate; 
     db.Entry(updatedEvent).State = EntityState.Detached; 
     db.Entry(updatedEvent).State = EntityState.Modified; 

    } 
+1

爲什麼要創建Event和Candidate的新實例?我認爲你不應該。 – Eduard

回答

1

當你試圖更新實體連接到不同的ObjectContext比您正在使用更新它的上下文這個錯誤確實拋出。只有一個ObjectContext的可跟蹤實體

你可以通過調用分離對象從它的背景:

,其中上下文是實體目前被附加到上下文。

using (var context = new UnicornsContext()) 
{ 
    var unicorn = new Unicorn { Name = "Franky", PrincessId = 1}; 
    context.Entry(unicorn).State = EntityState.Detached; 
    context.SaveChanges(); 
} 

http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx

而且在您的實現:

public void UpdateAddingCandidate(Event eventObj, Candidate candidate) 
{ 
    db.Entry(candidate).State = EntityState.Detached; 
    db.SaveChanges(); 

    eventObj.Candidate = candidate; 
    db.Entry(eventObj).State = EntityState.Modified; 
    db.SaveChanges(); 
} 
+0

我正在使用DbContext,在我的上下文中我看不到方法分離......我在這裏做錯了什麼? – GibboK

+0

我爲我的ObjectContext使用DataModelContainer。 U應該能夠使用dbContext.Entry(entity).State = EntityState.Detached;作爲解決方法 – middelpat

+0

謝謝,請看看我的編輯,即使以這種方式它不工作...任何想法?謝謝! – GibboK

0

爲什麼你創建的EventCandidate新的實例?你可以改變你已經擁有它們的狀態。如果您在存儲庫中共享ObjectContext的相同實例,則可以嘗試使用ObjectStateManager.GetObjectStateEntry(string key)ObjectStateManager從上下文中解析這些實體。

13

而不是隻使用存儲庫模式也使用工作單元模式。這樣你對每個使用的實體都有1分。

數據/合同/ IRepository.cs

namespace Data.Contracts 
{ 
    public interface IRepository<T> where T : class 
    { 
     IQueryable<T> GetAll(); 
     T GetById(int id); 
     void Add(T entity); 
     void Update(T entity); 
     void Delete(T entity); 
     void Delete(int id); 
} 

數據/合同/ IUnitOfWork.cs

namespace Data.Contracts 
{ 
    /// <summary> 
    /// Interface for the "Unit of Work" 
    /// </summary> 
    public interface IUnitOfWork 
    { 
     // Save pending changes to the data store. 
     void Commit(); 

     // Repositories 
     IRepository<Event> Events { get; } 
     IRepository<Candidate> Candidates { get; } 
    } 
} 

數據/ EFRepository.cs

using System; 
using System.Data; 
using System.Data.Entity; 
using System.Data.Entity.Infrastructure; 
using System.Linq; 
using Data.Contracts; 

namespace Data 
{ 
    /// <summary> 
    /// The EF-dependent, generic repository for data access 
    /// </summary> 
    /// <typeparam name="T">Type of entity for this Repository.</typeparam> 
    public class EFRepository<T> : IRepository<T> where T : class 
    { 
     public EFRepository(DbContext dbContext) 
     { 
      if (dbContext == null) 
       throw new ArgumentNullException("dbContext"); 
      DbContext = dbContext; 
      DbSet = DbContext.Set<T>(); 
     } 

     protected DbContext DbContext { get; set; } 

     protected DbSet<T> DbSet { get; set; } 

     public virtual IQueryable<T> GetAll() 
     { 
      return DbSet; 
     } 

     public virtual T GetById(int id) 
     { 
      //return DbSet.FirstOrDefault(PredicateBuilder.GetByIdPredicate<T>(id)); 
      return DbSet.Find(id); 
     } 

     public virtual void Add(T entity) 
     { 
      DbEntityEntry dbEntityEntry = DbContext.Entry(entity); 
      if (dbEntityEntry.State != EntityState.Detached) 
      { 
       dbEntityEntry.State = EntityState.Added; 
      } 
      else 
      { 
       DbSet.Add(entity); 
      } 
     } 

     public virtual void Update(T entity) 
     { 
      DbEntityEntry dbEntityEntry = DbContext.Entry(entity); 
      if (dbEntityEntry.State == EntityState.Detached) 
      { 
       DbSet.Attach(entity); 
      } 
      dbEntityEntry.State = EntityState.Modified; 
     } 

     public virtual void Delete(T entity) 
     { 
      DbEntityEntry dbEntityEntry = DbContext.Entry(entity); 
      if (dbEntityEntry.State != EntityState.Deleted) 
      { 
       dbEntityEntry.State = EntityState.Deleted; 
      } 
      else 
      { 
       DbSet.Attach(entity); 
       DbSet.Remove(entity); 
      } 
     } 

     public virtual void Delete(int id) 
     { 
      var entity = GetById(id); 
      if (entity == null) return; // not found; assume already deleted. 
      Delete(entity); 
     } 
    } 
} 

Data/UnitOfWork。CS

using System; 
using Data.Contracts; 
using Data.Helpers; 
using Models; 

namespace Data 
{ 
    /// <summary> 
    /// The "Unit of Work" 
    ///  1) decouples the repos from the controllers 
    ///  2) decouples the DbContext and EF from the controllers 
    ///  3) manages the UoW 
    /// </summary> 
    /// <remarks> 
    /// This class implements the "Unit of Work" pattern in which 
    /// the "UoW" serves as a facade for querying and saving to the database. 
    /// Querying is delegated to "repositories". 
    /// Each repository serves as a container dedicated to a particular 
    /// root entity type such as a <see cref="Url"/>. 
    /// A repository typically exposes "Get" methods for querying and 
    /// will offer add, update, and delete methods if those features are supported. 
    /// The repositories rely on their parent UoW to provide the interface to the 
    /// data layer (which is the EF DbContext in this example). 
    /// </remarks> 
    public class UnitOfWork : IUnitOfWork, IDisposable 
    { 
     public UnitOfWork(IRepositoryProvider repositoryProvider) 
     { 
      CreateDbContext(); 

      repositoryProvider.DbContext = DbContext; 
      RepositoryProvider = repositoryProvider;  
     } 

     // Repositories 
     public IRepository<Event> Events { get { return GetStandardRepo<Event>(); } } 
     public IRepository<Candidate> Candidates { get { return GetStandardRepo<Candidate>(); } } 

     /// <summary> 
     /// Save pending changes to the database 
     /// </summary> 
     public void Commit() 
     { 
      //System.Diagnostics.Debug.WriteLine("Committed"); 
      DbContext.SaveChanges(); 
     } 

     protected void CreateDbContext() 
     { 
      DbContext = new UnicornsContext(); 
     } 

     protected IRepositoryProvider RepositoryProvider { get; set; } 

     private IRepository<T> GetStandardRepo<T>() where T : class 
     { 
      return RepositoryProvider.GetRepositoryForEntityType<T>(); 
     } 
     private T GetRepo<T>() where T : class 
     { 
      return RepositoryProvider.GetRepository<T>(); 
     } 

     private UnicornsContext DbContext { get; set; } 

     #region IDisposable 

     public void Dispose() 
     { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
     } 

     protected virtual void Dispose(bool disposing) 
     { 
      if (disposing) 
      { 
       if (DbContext != null) 
       { 
        DbContext.Dispose(); 
       } 
      } 
     } 

     #endregion 
    } 
} 

數據/助手/ IRepositoryProvider.cs

using System; 
using System.Data.Entity; 
using Data.Contracts; 

namespace Data.Helpers 
{ 
    /// <summary> 
    /// Interface for a class that can provide repositories by type. 
    /// The class may create the repositories dynamically if it is unable 
    /// to find one in its cache of repositories. 
    /// </summary> 
    /// <remarks> 
    /// Repositories created by this provider tend to require a <see cref="DbContext"/> 
    /// to retrieve data. 
    /// </remarks> 
    public interface IRepositoryProvider 
    { 
     /// <summary> 
     /// Get and set the <see cref="DbContext"/> with which to initialize a repository 
     /// if one must be created. 
     /// </summary> 
     DbContext DbContext { get; set; } 

     /// <summary> 
     /// Get an <see cref="IRepository{T}"/> for entity type, T. 
     /// </summary> 
     /// <typeparam name="T"> 
     /// Root entity type of the <see cref="IRepository{T}"/>. 
     /// </typeparam> 
     IRepository<T> GetRepositoryForEntityType<T>() where T : class; 

     /// <summary> 
     /// Get a repository of type T. 
     /// </summary> 
     /// <typeparam name="T"> 
     /// Type of the repository, typically a custom repository interface. 
     /// </typeparam> 
     /// <param name="factory"> 
     /// An optional repository creation function that takes a <see cref="DbContext"/> 
     /// and returns a repository of T. Used if the repository must be created. 
     /// </param> 
     /// <remarks> 
     /// Looks for the requested repository in its cache, returning if found. 
     /// If not found, tries to make one with the factory, fallingback to 
     /// a default factory if the factory parameter is null. 
     /// </remarks> 
     T GetRepository<T>(Func<DbContext, object> factory = null) where T : class; 


     /// <summary> 
     /// Set the repository to return from this provider. 
     /// </summary> 
     /// <remarks> 
     /// Set a repository if you don't want this provider to create one. 
     /// Useful in testing and when developing without a backend 
     /// implementation of the object returned by a repository of type T. 
     /// </remarks> 
     void SetRepository<T>(T repository); 
    } 
} 

數據/助手/ RepositoryProvider.cs

using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using Data.Contracts; 

namespace Data.Helpers 
{ 
    /// <summary> 
    /// Provides an <see cref="IRepository{T}"/> for a client request. 
    /// </summary> 
    /// <remarks> 
    /// Caches repositories of a given type so that repositories are only created once per provider. 
    /// There should be a a new provider per client request. 
    /// </remarks> 
    public class RepositoryProvider : IRepositoryProvider 
    { 
     public RepositoryProvider(RepositoryFactories repositoryFactories) 
     { 
      _repositoryFactories = repositoryFactories; 
      Repositories = new Dictionary<Type, object>(); 
     } 

     /// <summary> 
     /// Get and set the <see cref="DbContext"/> with which to initialize a repository 
     /// if one must be created. 
     /// </summary> 
     public DbContext DbContext { get; set; } 

     /// <summary> 
     /// Get or create-and-cache the default <see cref="IRepository{T}"/> for an entity of type T. 
     /// </summary> 
     /// <typeparam name="T"> 
     /// Root entity type of the <see cref="IRepository{T}"/>. 
     /// </typeparam> 
     /// <remarks> 
     /// If can't find repository in cache, use a factory to create one. 
     /// </remarks> 
     public IRepository<T> GetRepositoryForEntityType<T>() where T : class 
     { 
      return GetRepository<IRepository<T>>(
       _repositoryFactories.GetRepositoryFactoryForEntityType<T>()); 
     } 

     /// <summary> 
     /// Get or create-and-cache a repository of type T. 
     /// </summary> 
     /// <typeparam name="T"> 
     /// Type of the repository, typically a custom repository interface. 
     /// </typeparam> 
     /// <param name="factory"> 
     /// An optional repository creation function that takes a DbContext argument 
     /// and returns a repository of T. Used if the repository must be created and 
     /// caller wants to specify the specific factory to use rather than one 
     /// of the injected <see cref="RepositoryFactories"/>. 
     /// </param> 
     /// <remarks> 
     /// Looks for the requested repository in its cache, returning if found. 
     /// If not found, tries to make one using <see cref="MakeRepository{T}"/>. 
     /// </remarks> 
     public virtual T GetRepository<T>(Func<DbContext, object> factory = null) where T : class 
     { 
      // Look for T dictionary cache under typeof(T). 
      object repoObj; 
      Repositories.TryGetValue(typeof(T), out repoObj); 
      if (repoObj != null) 
      { 
       return (T)repoObj; 
      } 

      // Not found or null; make one, add to dictionary cache, and return it. 
      return MakeRepository<T>(factory, DbContext); 
     } 

     /// <summary> 
     /// Get the dictionary of repository objects, keyed by repository type. 
     /// </summary> 
     /// <remarks> 
     /// Caller must know how to cast the repository object to a useful type. 
     /// <p>This is an extension point. You can register fully made repositories here 
     /// and they will be used instead of the ones this provider would otherwise create.</p> 
     /// </remarks> 
     protected Dictionary<Type, object> Repositories { get; private set; } 

     /// <summary>Make a repository of type T.</summary> 
     /// <typeparam name="T">Type of repository to make.</typeparam> 
     /// <param name="dbContext"> 
     /// The <see cref="DbContext"/> with which to initialize the repository. 
     /// </param>   
     /// <param name="factory"> 
     /// Factory with <see cref="DbContext"/> argument. Used to make the repository. 
     /// If null, gets factory from <see cref="_repositoryFactories"/>. 
     /// </param> 
     /// <returns></returns> 
     protected virtual T MakeRepository<T>(Func<DbContext, object> factory, DbContext dbContext) 
     { 
      var f = factory ?? _repositoryFactories.GetRepositoryFactory<T>(); 
      if (f == null) 
      { 
       throw new NotImplementedException("No factory for repository type, " + typeof(T).FullName); 
      } 
      var repo = (T)f(dbContext); 
      Repositories[typeof(T)] = repo; 
      return repo; 
     } 

     /// <summary> 
     /// Set the repository for type T that this provider should return. 
     /// </summary> 
     /// <remarks> 
     /// Plug in a custom repository if you don't want this provider to create one. 
     /// Useful in testing and when developing without a backend 
     /// implementation of the object returned by a repository of type T. 
     /// </remarks> 
     public void SetRepository<T>(T repository) 
     { 
      Repositories[typeof(T)] = repository; 
     } 

     /// <summary> 
     /// The <see cref="RepositoryFactories"/> with which to create a new repository. 
     /// </summary> 
     /// <remarks> 
     /// Should be initialized by constructor injection 
     /// </remarks> 
     private RepositoryFactories _repositoryFactories; 
    } 
} 

數據/助手/ RepositoryFactories.cs

using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using Data.Contracts; 

namespace Data.Helpers 
{ 
    /// <summary> 
    /// A maker of Repositories. 
    /// </summary> 
    /// <remarks> 
    /// An instance of this class contains repository factory functions for different types. 
    /// Each factory function takes an EF <see cref="DbContext"/> and returns 
    /// a repository bound to that DbContext. 
    /// <para> 
    /// Designed to be a "Singleton", configured at web application start with 
    /// all of the factory functions needed to create any type of repository. 
    /// Should be thread-safe to use because it is configured at app start, 
    /// before any request for a factory, and should be immutable thereafter. 
    /// </para> 
    /// </remarks> 
    public class RepositoryFactories 
    { 
     /// <summary> 
     /// Return the runtime repository factory functions, 
     /// each one is a factory for a repository of a particular type. 
     /// </summary> 
     /// <remarks> 
     /// MODIFY THIS METHOD TO ADD CUSTOM FACTORY FUNCTIONS 
     /// </remarks> 
     private IDictionary<Type, Func<DbContext, object>> GetFactories() 
     { 
      return new Dictionary<Type, Func<DbContext, object>> 
       { 
        //If you have an custom implementation of an IRepository<T> 
        //{typeof(IArticleRepository), dbContext => new ArticleRepository(dbContext)} 
       }; 
     } 

     /// <summary> 
     /// Constructor that initializes with runtime repository factories 
     /// </summary> 
     public RepositoryFactories() 
     { 
      _repositoryFactories = GetFactories(); 
     } 

     /// <summary> 
     /// Constructor that initializes with an arbitrary collection of factories 
     /// </summary> 
     /// <param name="factories"> 
     /// The repository factory functions for this instance. 
     /// </param> 
     /// <remarks> 
     /// This ctor is primarily useful for testing this class 
     /// </remarks> 
     public RepositoryFactories(IDictionary<Type, Func<DbContext, object>> factories) 
     { 
      _repositoryFactories = factories; 
     } 

     /// <summary> 
     /// Get the repository factory function for the type. 
     /// </summary> 
     /// <typeparam name="T">Type serving as the repository factory lookup key.</typeparam> 
     /// <returns>The repository function if found, else null.</returns> 
     /// <remarks> 
     /// The type parameter, T, is typically the repository type 
     /// but could be any type (e.g., an entity type) 
     /// </remarks> 
     public Func<DbContext, object> GetRepositoryFactory<T>() 
     { 

      Func<DbContext, object> factory; 
      _repositoryFactories.TryGetValue(typeof(T), out factory); 
      return factory; 
     } 

     /// <summary> 
     /// Get the factory for <see cref="IRepository{T}"/> where T is an entity type. 
     /// </summary> 
     /// <typeparam name="T">The root type of the repository, typically an entity type.</typeparam> 
     /// <returns> 
     /// A factory that creates the <see cref="IRepository{T}"/>, given an EF <see cref="DbContext"/>. 
     /// </returns> 
     /// <remarks> 
     /// Looks first for a custom factory in <see cref="_repositoryFactories"/>. 
     /// If not, falls back to the <see cref="DefaultEntityRepositoryFactory{T}"/>. 
     /// You can substitute an alternative factory for the default one by adding 
     /// a repository factory for type "T" to <see cref="_repositoryFactories"/>. 
     /// </remarks> 
     public Func<DbContext, object> GetRepositoryFactoryForEntityType<T>() where T : class 
     { 
      return GetRepositoryFactory<T>() ?? DefaultEntityRepositoryFactory<T>(); 
     } 

     /// <summary> 
     /// Default factory for a <see cref="IRepository{T}"/> where T is an entity. 
     /// </summary> 
     /// <typeparam name="T">Type of the repository's root entity</typeparam> 
     protected virtual Func<DbContext, object> DefaultEntityRepositoryFactory<T>() where T : class 
     { 
      return dbContext => new EFRepository<T>(dbContext); 
     } 

     /// <summary> 
     /// Get the dictionary of repository factory functions. 
     /// </summary> 
     /// <remarks> 
     /// A dictionary key is a System.Type, typically a repository type. 
     /// A value is a repository factory function 
     /// that takes a <see cref="DbContext"/> argument and returns 
     /// a repository object. Caller must know how to cast it. 
     /// </remarks> 
     private readonly IDictionary<Type, Func<DbContext, object>> _repositoryFactories; 
    } 
} 

現在,如果你有這樣的使用你的UnitOfWork你不能簡單地調用它,因爲所有的相關性,如果你已經使用IoC容器,你知道,你可以簡單地解決這些依賴關係之前。在我來說,我使用Ninject這樣的代碼來設置它會是:

var kernel = new StandardKernel(); // Ninject IoC 

kernel.Bind<RepositoryFactories>().To<RepositoryFactories>().InSingletonScope(); 
kernel.Bind<IRepositoryProvider>().To<RepositoryProvider>(); 
kernel.Bind<IUnitOfWork>().To<UnitOfWork>(); 

然後爲您的網頁,你可以添加一個基本頁面類:

Web.BasePage.cs

namespace Web 
{ 
    public abstract class BasePage : System.Web.UI.Page 
    { 
     // NOT NECESSARY TO DISPOSE THE UOW IN OUR CONTROLLERS 
     // Recall that we let IoC inject the Uow into our controllers 
     // We can depend upon on IoC to dispose the UoW for us 
     protected IUnitOfWork Uow { get; set; } 
    } 
} 

還是在MVC的情況下,你可以添加一個基本的控制器:

網站/控制器/ BaseController.cs

using System.Web.Mvc; 

namespace Site.Controllers 
{ 
    public abstract class BaseController : Controller 
    { 
     // NOT NECESSARY TO DISPOSE THE UOW IN OUR CONTROLLERS 
     // Recall that we let IoC inject the Uow into our controllers 
     // We can depend upon on IoC to dispose the UoW for us 
     protected IUnitOfWork Uow { get; set; } 
    } 
} 

然後在你的代碼中,當你想訪問你的實體時,這很容易。您可以簡單地調用每個頁面中可用的UnitOfWork,並訪問其中的不同存儲庫。因爲它們都共享相同的DbContext,所以你的問題不復存在。


我做了一個非常簡單的工作單元例子,支持Ninject控制反轉。

在我的例子還有ASP.NET Web表單和ASP.NET MVC4例如

例子是一個單頁網站,每次加載網站將添加事件的時間到示例數據庫和類別到數據庫,都使用隨機名稱。 (我需要DB的東西,所以我只做了2個簡單的POCOS)。之後,頁面將在兩個列表中顯示所有內容。只需查看Mvc4/Controllers/HomeController.csWebForms/Default.aspx.cs即可。

Example on skydrive

+0

什麼是' EFRepository'?你能發表一個完整的工作代碼? – Mrchief

+0

@Mrchief true true,我忘了EFRepository,它是一個IRepository 的實現,虐待添加在一秒 – SynerCoder

+0

@Mrchief你想要一個webforms或mvc的例子嗎? – SynerCoder