【问题标题】:I have a POCO, can I get a proxy from the DbContext?我有一个 POCO,我可以从 DbContext 获取代理吗?
【发布时间】:2013-06-18 12:18:26
【问题描述】:

我有一个从 POST 请求中获得的模型。由于我的视图定义了它的 POCO 类型,所以从提交的数据创建的对象也是一个 POCO。作为 POCO,它没有覆盖各种虚拟属性。因此,这些虚拟属性返回 null。反过来,这意味着我必须根据外键进行单独的查询以浏览其属性(如果我想做比保存它更复杂的事情)。

鉴于我的模型的 POCO,我能否获得具有所有被覆盖功能的代理?

(我曾假设这是 db.Entry().Entity 的用途,但它仍然返回 POCO 对象,而不是代理。我在断点暂停期间通过鼠标悬停检查对象的运行时类型。)

【问题讨论】:

标签: c# asp.net-mvc entity-framework


【解决方案1】:

与此代码类似的内容将满足您的需求。我使用automapper 将值从传入的实体复制到代理版本。

代码检查传入的实体是否为代理并进行相应处理。

public class Repository<T> where T : class
{
    private readonly Context context;
    private bool mapCreated = false;
    public Repository(Context context)
    {
        this.context = context;
    }

    protected virtual T InsertOrUpdate(T e, int id)
    {
        T instance = context.Set<T>().Create();
        if (e.GetType().Equals(instance.GetType()))
            instance = e;
        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<T>(instance).State = EntityState.Modified;

        return instance;
    }
}

UPDATE 版本,由 @Colin 在 cmets 中描述,不需要 automapper

public class Repository<T> where T : class
{
    private readonly Context context;
    public Repository(Context context)
    {
        this.context = context;
    }

    protected virtual T InsertOrUpdate(T e, int id)
    {
        T instance = context.Set<T>().Create();
        if (e.GetType().Equals(instance.GetType()))
        {
            instance = e;
        }
        else
        {
            DbEntityEntry<T> entry = context.Entry(instance);
            entry.CurrentValues.SetValues(e);
        }

        context.Entry<T>(instance).State =
            id == default(int)
                ? EntityState.Added
                : EntityState.Modified;

        return instance;
    }
}

【讨论】:

  • 非常感谢!这正是我为各种模型类型优雅地获取具有正确值的适当代理所需要的。
  • @TheodorosChatzigiannakis 我已更新代码以使用Entry&lt;T&gt; 的通用版本。
  • 是的,我注意到了。我已经根据我手中的内容定制了您的初始代码(这是一个通用处理程序库,用于 EF 的各种样板程序)。你的想法真的帮助我前进。感谢您的高质量回答!
  • Context.Entry.CurrentValues 对象有一个 SetValues 方法,可让您设置属性而无需使用自动映射器。请参阅此解决方案 - stackoverflow.com/a/16811976/150342
  • 如果e不是代理对象,更新后的版本会在DbEntityEntry&lt;T&gt; entry = context.Entry(instance);这一行抛出InvalidOperationException,因为instance还没有添加到context中:“会员'@987654332无法为类型为 'T' 的实体调用 @',因为该实体在上下文中不存在。要将实体添加到上下文中,请调用 DbSet&lt;T&gt; 的 Add 或 Attach 方法。”跨度>
【解决方案2】:

db.Entry().Entity 将始终返回一个 POCO,并且不会返回处理虚拟导航属性实现的代理对象:

var o = db.Entry(myPoco).Entity;   // always returns a POCO

当针对数据库上下文调用 Find() 或 Where() 时,您通常会得到一个代理对象而不是 POCO。 然而,在对象首次添加到数据库的上下文中,这些方法将(出乎意料?)返回 POCO 而不是代理对象。您实际上必须离开上下文并打开一个新上下文才能获取代理:

        // create a new POCO object, and connect to it to another object already in the DB
        MyPoco myPoco = new MyPoco();
        myPoco.MyOtherPocoId = myPoco2.MyOtherPocoId;   // make reference to existing object

        using (var db = new MyContext())
        {
            // Add myPoco to database.
            db.MyPocos.Add(myPoco);
            db.SaveChanges();

            // One would think you get a proxy object here, but you don't: just a POCO
            var test10 = db.MyPocos.Find(myPoco.Id);                        // returns a MyPoco                        
            var test11 = db.MyPocos.Where(x => x.Id == myPoco.Id).First();  // returns a MyPoco
            var test12 = db.Entry(myPoco).Entity;                           // returns a MyPoco

            // ...so, you can't access the referenced properties through virtual navigation properties:
            MyOtherPoco otherPoco1 = myPoco.Poco2;  // returns NULL
        }

        // leave the context and build a new one

        using (var db = new MyContext())
        {
            // Now, the same Find() and Where() methods return a proxy object
            var test20 = db.MyPocos.Find(myPoco.Id);    // returns a proxy object
            var test21 = db.MyPocos.Where(x => x.Id == myPoco.Id).First();  // returns a proxy object

            // ...which means the virtual properties can be accessed as expected:
            MyOtherPoco otherPoco = myPoco.Poco2;   // works as expected

            // Note that db.Entry().Entity still returns a POCO:
            var test22 = db.Entry(myPoco).Entity;   // returns a MyPoco
        }

可能有一些魔法咒语可以让添加对象的上下文给你一个代理对象,但我还没有遇到过。

【讨论】:

  • 谢谢!这类似于我已经拥有的。很遗憾,EF 没有任何东西可以自动给出相关代理。但我想请求一个空代理并使用 Automapper 等工具手动(或可能)映射它,正如另一个答案中所建议的那样)是我们最接近此功能的方法。
【解决方案3】:

如果您想通过 MVC 控制器执行此操作,您可以使用类似这样的操作:

    [HttpPost]
    public ActionResult Update(int? id, FormCollection form)
    {
        // assumes repository will handle
        // retrieving the entity and
        // including and navigational properties
        var entity = repository.Get(id);
        if (entity == null)
        {
            throw new InvalidOperationException(string.Format("Not found: {0}", id));
        }
        if (TryUpdateModel(entity))
        {
            try
            {
                //
                // do other stuff, additional validation, etc
                repository.Update(entity);
            }
            catch (Exception ex)
            {
                //
                // exception cleansing/handling
                // additional model errors
                return View(entity);
            }
            return View("Success", entity);
        }            

        return View(entity);
    }

【讨论】:

  • 感谢您的回答!这是一种有效的方法,确实与我已经使用的方法相似。但是,我觉得为了绕过 EF 的限制而重新查询数据库有点浪费。这就是为什么我在探索检索代理的其他可能选项。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-19
  • 1970-01-01
  • 2021-06-18
  • 1970-01-01
  • 1970-01-01
  • 2018-03-06
  • 2015-04-01
相关资源
最近更新 更多