我有一个POCO,我能从DbContext获取代理吗?

9
我有一个模型是从POST请求中获取的。由于我的视图定义了它的POCO类型,所以从提交的数据创建的对象也是一个POCO。作为POCO,它没有覆盖各种虚拟属性。因此,这些虚拟属性返回null。这反过来意味着我必须根据外键进行单独的查询,以浏览其属性(如果我想做更复杂的事情而不仅仅是保存它)。
给定我的模型的POCO,我能否获得具有所有覆盖功能的代理?
(我曾认为这就是db.Entry().Entity的作用,但它仍然向我返回POCO对象,而不是代理。我在断点暂停期间通过鼠标悬停检查对象的运行时类型。)

3个回答

7

以下是可以满足您需求的代码。我已经使用了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;
    }
}

更新 根据@Colin在评论中的描述,无需使用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;
    }
}

1
非常感谢!这正是我需要的,以优雅的方式获取适当的代理,并为各种模型类型设置正确的值。 - Theodoros Chatzigiannakis
1
@TheodorosChatzigiannakis 我已经更新了代码,使用了Entry<T>的通用版本。 - qujck
是的,我注意到了。我已经根据我手头的通用处理程序库(针对 EF 的各种样板程序)自定义了您的初始代码。您的想法确实帮助了我前进。感谢您高质量的回答! - Theodoros Chatzigiannakis
3
Context.Entry.CurrentValues对象具有SetValues方法,可以让您设置属性,而无需使用automapper。参见此解决方案 - https://dev59.com/P3LYa4cB1Zd3GeqPYoIC#16811976 - Colin
1
如果e不是代理对象,则更新后的版本会在行DbEntityEntry<T> entry = context.Entry(instance);上抛出InvalidOperationException,因为instance尚未添加到context中:“无法为类型为'T'的实体调用成员'CurrentValues',因为该实体不存在于上下文中。要将实体添加到上下文中,请调用DbSet<T>AddAttach方法。” - Korijn
我已经找了几个小时了。我忽略了.CurrentValue.SetValues(object)重载。非常感谢你... - Pluc

1

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这样的工具,正如另一个答案中建议的那样)是我们能够接近此功能的最佳方式。 - Theodoros Chatzigiannakis

0
如果您想通过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的限制而重新查询数据库有些浪费。这就是为什么我正在探索其他可能的检索代理的选项。 - Theodoros Chatzigiannakis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接