【问题标题】:How to ensure proxies are created when using the repository pattern with entity framework?将存储库模式与实体框架一起使用时,如何确保创建代理?
【发布时间】:2013-05-24 13:27:53
【问题描述】:

我的 SurveyController 类中有这个方法:

public ActionResult AddProperties(int id, int[] propertyids, int page = 1)
{
    var survey = _uow.SurveyRepository.Find(id);
    if (propertyids == null)
        return GetPropertiesTable(survey, page);

    var repo = _uow.PropertySurveyRepository;

    propertyids.Select(propertyid => new PropertySurvey
                {
                    //Setting the Property rather than the PropertyID 
                    //prevents the error occurring later
                    //Property = _uow.PropertyRepository.Find(propertyid),
                    PropertyID = propertyid,
                    SurveyID = id
                })
                .ForEach(x => repo.InsertOrUpdate(x));
    _uow.Save();

    return GetPropertiesTable(survey, page);
}

GetPropertiesTable 重新显示属性,但 PropertySurvey.Property 被标记为虚拟,我使用 new 运算符创建了实体,因此从未创建支持延迟加载的代理,当我访问它时它为空。当我们可以直接访问 DbContext 时,我们可以对 explicitly create the proxy 使用 Create 方法。但是我在这里有一个工作单元和存储库模式。我想我可以通过 repository.Create 方法公开 context.Create 方法,然后我需要记住在添加 entity 时使用它而不是 new 运算符。但是将问题封装在我的 InsertOrUpdate 方法中不是更好吗?是否有某种方法可以检测到被添加的实体不是代理时应该是代理并替换代理?这是我的基础存储库类中的 InsertOrUpdate 方法:

    protected virtual void InsertOrUpdate(T e, int id)
    {
        if (id == default(int))
        {
            // New entity
            context.Set<T>().Add(e);
        }
        else
        {
            // Existing entity
            context.Entry(e).State = EntityState.Modified;
        }
    }

【问题讨论】:

    标签: entity-framework


    【解决方案1】:

    根据 qujck 提供的答案。以下是无需使用自动映射器的方法:

    已编辑以始终检查代理 - 不仅仅是在插入期间 - 如 cmets 中所建议的那样

    再次编辑以使用不同的方式检查代理是否已传入该方法。更改技术的原因是我在引入从另一个继承的实体时遇到了问题。在这种情况下,继承的实体即使是代理也可能无法通过entity.e.GetType().Equals(instance.GetType() 检查。我从this answer得到了新技术

    public virtual T InsertOrUpdate(T e)
    {
        DbSet<T> dbSet = Context.Set<T>();
    
        DbEntityEntry<T> entry;
        if (e.GetType().BaseType != null 
            && e.GetType().Namespace == "System.Data.Entity.DynamicProxies")
        {
            //The entity being added is already a proxy type that supports lazy 
            //loading - just get the context entry
            entry = Context.Entry(e);
        }
        else
        {
            //The entity being added has been created using the "new" operator. 
            //Generate a proxy type to support lazy loading  and attach it
            T instance = dbSet.Create();
            instance.ID = e.ID;
            entry = Context.Entry(instance);
            dbSet.Attach(instance);
    
            //and set it's values to those of the entity
            entry.CurrentValues.SetValues(e);
            e = instance;
        }
    
        entry.State = e.ID == default(int) ?
                                EntityState.Added :
                                EntityState.Modified;
    
        return e;
    }
    
    public abstract class ModelBase
    {
        public int ID { get; set; }
    }
    

    【讨论】:

    • 您的代码天真地假设任何具有 id 值的实体都是代理对象
    • 所有 id 不为零的实体都在更新。因此,它们必须首先从 DbContext 中检索到。那么他们怎么会不是代理呢?
    • 在我正在处理的代码中,更新来自 Web 层调用 WebAPI 服务 - 反序列化的实例是新的实体。
    • 我可能错了,但 CurrentValues.SetValues(e) 只设置第一级属性。如果您在模型中添加导航对象,则不会创建它们。 (例如:M:M 属性)
    【解决方案2】:

    我同意你的观点,这应该在一个地方处理,而最好的地方就是你的存储库。您可以将T 的类型与上下文创建的实例进行比较,如果类型不匹配,可以使用Automapper 之类的东西快速传输所有值。

    private bool mapCreated = false;
    
    protected virtual void InsertOrUpdate(T e, int id)
    {
        T instance = context.Set<T>().Create();
        if (e.GetType().Equals(instance.GetType()))
            instance = e;
        else
        {
            //this bit should really be managed somewhere else
            if (!mapCreated)
            {
                Mapper.CreateMap(e.GetType(), instance.GetType());
                mapCreated = true;
            }
            instance = Mapper.Map(e, instance);
        }
    
        if (id == default(int))
            context.Set<T>().Add(instance);
        else
            context.Entry(instance).State = EntityState.Modified;
    }
    

    【讨论】:

    • 太棒了!我要改变的两件事。 1.更新时不需要检查实例。 2. 我们可以像这样使用 DBEntityEntry,而不是使用 automapper: DbEntityEntry entry = context.Entry(instance); entry.State = EntityState.Added; entry.CurrentValues.SetValues(e);
    • 将 T 的类型与上下文创建的实例进行比较对我有用,直到我引入了一个从另一个实体继承的实体。然后测试也失败了,所以我改变了用于测试代理的技术 - 请参阅我的答案中的编辑
    猜你喜欢
    • 1970-01-01
    • 2014-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多