【问题标题】:Can I actually attach single entity to multiple contexts我真的可以将单个实体附加到多个上下文吗
【发布时间】:2018-08-03 15:35:12
【问题描述】:

在我的自定义开发应用程序中,我收到一个错误“一个实体对象不能被多个 IEntityChangeTracker 实例引用”。据我了解,这意味着我不能使用附加到多个上下文的同一实体。为了重现错误,我使用以下代码创建了一个沙盒应用程序:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Linq.Expressions;

namespace EntityFrameworkRepositoryPatternTestApp
{
  public class Item
  {
    public Guid Id { get; set; }
    public string SomeString { get; set; }
    public int SomeInt { get; set; }
    public double SomeDouble { get; set; }
  }

  public class Context : DbContext
  {
    public DbSet<Item> Items { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
      modelBuilder.Configurations.Add(new ItemConfiguration());
      base.OnModelCreating(modelBuilder);
    }

    public Context() : base("Connection")
    {
    }
  }

  public class ItemConfiguration : EntityTypeConfiguration<Item>
  {
    public ItemConfiguration()
    {
      this.ToTable("Items").HasKey(i => i.Id);
      this.Property(i => i.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
      this.Property(i => i.SomeString).HasMaxLength(255);
      this.Property(i => i.SomeInt);
      this.Property(i => i.SomeDouble);
    }
  }

  public interface IRepository<T>
  {
    T Add(T item);
    IEnumerable<T> Get(Expression<Func<T, bool>> filter = null);
    T GetById(Guid id);
    void Remove(T item);
  }

  public class EntityFrameworkRepository<TEntity> : IRepository<TEntity> where TEntity : class
  {
    private readonly IDbSet<TEntity> dbSet;
    private Context Context { get; }

    public TEntity Add(TEntity item)
    {
      this.dbSet.Add(item);
      return item;
    }

    public IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null)
    {
      IQueryable<TEntity> query = this.dbSet;
      if (filter != null)
        query = query.Where(filter);
      return query.ToList();
    }

    public TEntity GetById(Guid id)
    {
      return this.dbSet.Find(id);
    }

    public void Remove(TEntity item)
    {
      this.dbSet.Remove(item);
    }

    public EntityFrameworkRepository(Context context)
    {
      this.Context = context;
      this.dbSet = this.Context.Set<TEntity>();
    }
  }

  public interface IDomainContext
  {
    int SaveChanges();
    int SaveChangesWithoutClientValidation();
  }

  public interface IDomainContext<TEntity> : IDomainContext where TEntity : class
  {
    IRepository<TEntity> Entities { get; }

    void Update(TEntity item);
    void Attach(TEntity item);
    void Detach(TEntity item);
  }

  public class DomainContext<TEntity> : IDomainContext<TEntity>, IDisposable where TEntity : class
  {
    private Context context;

    public int SaveChanges()
    {
      return this.context.SaveChanges();
    }

    public IRepository<TEntity> Entities => this.GetRepository<TEntity>();

    public void Update(TEntity item)
    {
      var entry = this.context.Entry(item);
      foreach (var propertyName in entry.OriginalValues.PropertyNames)
      {
        var original = entry.OriginalValues.GetValue<object>(propertyName);
        var current = entry.CurrentValues.GetValue<object>(propertyName);
        if ((original == null && current != null) || 
            ((original != null) && !original.Equals(current)))
          entry.Property(propertyName).IsModified = true;
      }
    }

    public void Attach(TEntity item) =>
      this.context.Set<TEntity>().Attach(item);

    public void Detach(TEntity entity) =>
      ((IObjectContextAdapter)this.context).ObjectContext.Detach(entity);

    public void Dispose()
    {
      this.context.Dispose();
    }

    private IRepository<T> GetRepository<T>() where T : class
    {
      var resultRepository = new EntityFrameworkRepository<T>(this.context);
      return resultRepository;
    }

    public DomainContext()
    {
      this.context = new Context();
    }
  }

  public class Program
  {
    static void Main(string[] args)
    {
      var domainContext = new DomainContext<Item>();
      {
        var item = new Item() { Id = Guid.NewGuid(), SomeDouble = 666, SomeInt = 777, SomeString = "some string"};
        domainContext.Entities.Add(item);
        domainContext.SaveChanges();
        using (var domainContext2 = new DomainContext<Item>())
        {
          if (item != null)
          {
            domainContext2.Attach(item);
            item.SomeDouble = 11.28;
            domainContext2.Update(item);
            domainContext2.SaveChanges();
          }
        }
      }
      domainContext.Dispose();
    }
  }
}

问题是我在这个沙盒应用程序中实际上并没有收到错误。所以现在我需要了解,Entity Framework 是否真的禁止将单个实体附加到多个上下文以及如何克服这个限制。我想到的解决方案是在将实体副本附加到另一个上下文时创建它。但现在我认为,真的可以在不创建副本的情况下将实体附加到另一个上下文。

【问题讨论】:

  • 我认为您缺少的关键是实体只能由一个上下文在给定时间跟踪。您可以将实体的状态设置为 EntityState.Detached 以停止对其进行跟踪,然后将其附加到另一个上下文
  • 好的。但是为什么我在测试应用程序中没有出错?它显然在两个上下文之间共享一个实体。

标签: .net entity-framework


【解决方案1】:

只有EF proxies 不能附加到多个上下文,因为它们引用了创建它们的上下文。

声明...

var item = new Item() ...

...创建一个简单的 POCO,它对上下文一无所知,即使它附加到上下文也不知道。

如果您将其替换为...

var item = context.Items.Create();

...item 将是一个代理对象,您会注意到尝试将其附加到另一个上下文将引发“无法被多个实例引用”异常。

另外,当上下文从数据库中读取一个对象时,它将是一个代理对象(前提是满足实体类成为代理的所有条件)。

【讨论】:

  • 这似乎是合理的,但是当我按照你的建议替换字符串时,我仍然没有收到错误...
  • 这与创建代理的条件有关。 Item 是一个没有 virtual 导航属性的简单类,因此 EF 不会创建代理。代理主要用于延迟加载导航属性。
  • 是的,当实体具有包含子集合的虚拟属性时,它确实会引发所需的异常。
猜你喜欢
  • 1970-01-01
  • 2010-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-06
  • 1970-01-01
  • 1970-01-01
  • 2011-06-12
相关资源
最近更新 更多