【问题标题】:Entity framework - global vs local context vs lazy loading实体框架 - 全局 vs 本地上下文 vs 延迟加载
【发布时间】:2014-01-28 14:47:07
【问题描述】:

最终答案:

我在 InRequestScope() 方法中使用了@WiktorZychla 答案和 Ninject 的组合。我重构了我的存储库以接受上下文注入,然后在我的 NinjectControllerFactory 中添加了以下行:

ninjectKernel.Bind<EFDbContext>().ToSelf().InRequestScope();

(注意:我替换了:

ninjectKernel.Bind<ISellingLocation>().To<EFSellingLocationRepository>().InReque‌​stScope().WithConstructorArgument("context",new EFDbContext());

我在其中一个 cmets 中提到的行,带有:

ninjectKernel.Bind<ISellingLocation>().To<EFSellingLocationRepository>();

因为它导致了错误)

我还使用 nuget 安装了 Ninject.MVC3,它使用以下行创建了文件:“NinjectWebCommon.cs”:

DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));

虽然有人说此行是可选的,但其他文章指出应该使用它以便 InRequestScope 在 MVC 站点中正常工作。

原问题:

我目前有几个 EF 存储库,每个都类似于以下内容:

public class EFCityRepository : ICityRepository
{
private EFDbContext context = new EFDbContext();

public bool Create(City cityToCreate)
{
...
}

public City Get(int cityID)
{
...
}
}

如您所见,现在我对所有操作都使用单个全局 EFDbContext,从我读到的内容来看这是不好的 - 所以我尝试将它(在 Create、Get 和其他方法中)更改为“使用”语句,类似于以下内容:

public City Get(int cityID)
{
using(EFDbContext context)
{
...some opeartions…
return entities;
}
}

现在我遇到了很多与实体延迟加载相关的问题,我必须使用以下内容:

context.Entry(getResult.FirstOrDefault()).Reference(x => x.Address).Load();
context.Entry(getResult.FirstOrDefault()).Reference(x => x.Agency).Load();
context.Entry(getResult.FirstOrDefault().Address).Reference(x => x.City).Load();

只是为了简单 Get 以我想要的方式工作,否则每次我尝试访问 Address 时,例如,我得到:“ObjectContext 实例已被释放,不能再用于需要连接的操作” .当然,当上下文是全局的时,它可以正常工作。

我需要一些建议:我应该使用本地上下文并使用热切加载而不是延迟加载吗?还是这里可以接受全局上下文?

还有一个人到底是怎么想使用延迟加载的呢?正如我所看到的 - 我必须为使用某个存储库的每个操作编写单独的逻辑 - 还是我错了?

编辑 1:

@Askolein:

好的,目前我的应用程序包含几个子项目:

Common
Domain - here I have my repositories
Helpers
Utils
WebUI

我用来触发错误的存储库如下所示:

public interface ISellingLocation
{
KeyValuePair<bool, Exception> Create(SellingLocation sellingLocationToAdd);
KeyValuePair<SellingLocation, Exception> Get(int sellingLocationID);
KeyValuePair<bool, Exception> Update(SellingLocation sellingLocationToUpdate);
KeyValuePair<bool, Exception> Delete(int sellingLocationID);

KeyValuePair<List<SellingLocation>, Exception> GetAll();
KeyValuePair<List<SellingLocation>, Exception> GetAll(int agencyID);
KeyValuePair<List<SellingLocation>, Exception> GetFiltered(string filter);
KeyValuePair<List<SellingLocation>, Exception> GetFiltered(Expression<Func<SellingLocation, bool>> filter);

KeyValuePair<bool, Exception> DisableSellingLocations(List<int> sellingLocationsIDs);
}

还有GetFiltered方法的实现,像这样:

public KeyValuePair<List<SellingLocation>, Exception> GetFiltered(Expression<Func<SellingLocation, bool>> filter)
{
Exception lastException = null;

using (var transaction = new TransactionScope())
{
using (EFDbContext context = new EFDbContext())
{
try
{
var getResult = context.SellingPoints.Where(filter).ToList();
//var getResult2 = getResult.ToList();

context.Entry(getResult.FirstOrDefault()).Reference(x => x.Address).Load();
context.Entry(getResult.FirstOrDefault()).Reference(x => x.Agency).Load();
context.Entry(getResult.FirstOrDefault().Address).Reference(x => x.City).Load();


transaction.Complete();

return new KeyValuePair<List<SellingLocation>, Exception>(getResult, lastException);
}
catch (Exception ex)
{
lastException = ex;

return new KeyValuePair<List<SellingLocation>, Exception>(new List<SellingLocation>(), ex);
}
}
}
}

我在我的控制器中这样调用这个方法:

var allSellingLocationsForCurrentUser = sellingLocationRepository.GetFiltered(x => x.IsEnabled);

if(allSellingLocationsForCurrentUser.Value == null)
{
AgencySalesSellingLocationsListViewModel agencySalesSellingLocationsListViewModel = new AgencySalesSellingLocationsListViewModel();

foreach (var item in allSellingLocationsForCurrentUser.Key)
{
agencySalesSellingLocationsListViewModel.aaData.Add(new AgencySalesSellingLocationsListViewModelRow()
{
ID = item.SellingLocationID,
DT_RowId = item.SellingLocationID.ToString(),
Name = item.Name,
City = item.Address.City.Name,
Street = item.Address.Street
});
}

return Json(agencySalesSellingLocationsListViewModel, JsonRequestBehavior.AllowGet);
}

我明白为什么我会收到错误,正如我之前所说 - 如果我明确告诉实体,加载:地址、代理和地址.城市 - 它会正常工作。

@WiktorZychla:

我当前的 DataContext 如下所示:

public class EFDbContext : DbContext
{
public EFDbContext():base("DefaultConnection")
{

}

public DbSet<User> Users { get; set; }
public DbSet<UserData> UserDatas { get; set; }
public DbSet<Address> Addresses { get; set; }
public DbSet<SkillCategory> SkillCategories {get;set;}
public DbSet<Skill> Skills {get;set;}
public DbSet<SkillAnswer> SkillAnswers { get; set; }
//public DbSet<UserSkills> UserSkills { get; set; }
public DbSet<User2Skill> User2Skill { get; set; }
public DbSet<Agency> Agencies { get; set; }
public DbSet<UniversalDictionary> UniversalDictionaries { get; set; }
public DbSet<UserAddressTimeTable> UserAddressTimeTables { get; set; }
public DbSet<City> Cities { get; set; }
public DbSet<SellingLocation> SellingPoints { get; set; }
}

如果我理解正确 - 我将不得不封装我的 EFCityRepository 并使用类似的东西:

using(SomeContext context = new SomeContext())
{
    EFCityRepository repository = new EFCityRepository(context);
    var result = repository.Get(id);
    ...do some work...
}

是不是有点过头了?现在我使用 Ninject,并使用存储库接口(IUserRepository、IUserRepository 等)注入我的控制器 - 所以我的控制器方法就像工作单元一样工作,如果我理解正确 - 我将不得不:在我的控制器方法中注入我的 DbContext ,或者在控制器方法和存储库之间创建另一层......我理解正确吗?

@cosset:

如上所述-我将控制器方法视为工作单元...如果我要实现您建议的某些东西-我应该把它放在哪里?在域或 WebUI 内部?它必须是存储库和控制器之间的另一层,对吗?

谢谢大家的建议。

最好的问候

【问题讨论】:

  • 您能否添加产生您所指错误的代码? (可能放在你写“...一些操作...”的地方)
  • 关于这个话题有很多可以说和已经说过的。通常,您希望上下文的生命周期与您的工作单元相同。
  • @Askolein:我回家后(1-2 小时)会添加更多代码。
  • @Aaron Palmer:问题是 - 我需要填充数据表,所以我想获取数据然后为我的表处理它,问题是由于延迟加载,我不能访问一些属性(即使我返回如下内容: context.City.Where(someCondition).ToList() - 因为我得到的是 City 的实体代理,而不是 City POCO 本身)。如何处理这样的事情?
  • 在给定的请求(你的工作单元)中,如果你总是要加载城市,那么你应该急切地加载它们。

标签: c# asp.net-mvc entity-framework lazy-loading code-first


【解决方案1】:

与其使上下文对存储库方法来说是本地的,不如反过来——让存储库独立于上下文:

public class EFCityRepository : ICityRepository
{ 
    public EFCityRepository( EFDbContext context )
    {
        this.context = context;
    }        

    private EFDbContext context;

    public bool Create(City cityToCreate)
    {
        ...
    }

    public City Get(int cityID)
    {
        ...
    }
}

这种方法为您提供了最佳的灵活性。您可以在存储库之间共享相同的上下文,每个存储库都可以有一个额外的上下文,无论如何。

对于基于 Web 的应用程序,通常的做法是“按请求”共享上下文,这意味着在单个请求中使用完全相同的上下文,并且在请求管道的末尾进行处理。

编辑: 正如 Maess 所建议的,您绝对应该考虑通过依赖注入引擎(如 Unity 或 Ninject)半自动管理上下文生命周期的可能性。 DI 引擎还可以通过自动解决构造函数依赖关系为您提供显着帮助。这是另一个故事,但可能是您的架构向前迈出的坚实一步。

【讨论】:

  • 你可能想谈谈 IOC 和 DI,但不确定 OP 是否熟悉它们。
  • @user2384366:“这不是有点矫枉过正吗?现在我使用 Ninject,并为我的控制器注入存储库接口 [...]”。太好了,只需使用“每个请求”的生命周期注册您的上下文,Ninject 就会愉快地将您的上下文注入到存储库中,并将存储库注入到控制器中。
  • @WiktorZychla 我已经进行了您建议的更改,并使用了以下 ninject 绑定:ninjectKernel.Bind().To().InRequestScope().WithConstructorArgument("context" ,新的 EFDbContext());但这是否足够,还是我必须使用: using(context) { } 用于我的 EFCityRepository 中的每个方法?
  • @user2384366:我认为您不必拥有using。处理上下文应该在自定义控制器工厂中完成(如果您使用 ninject,您可能有一个),在 ReleaseController 方法中。这是您在控制器上调用 Dispose 的地方(如果它们实现了 IDisposable),并且在每个控制器中,您在其 Dispose 中处理上下文。
  • @WiktorZychla 看来我以前的 ninject 绑定没有做任何事情 - 我在我的 EFDbContext 中覆盖了 Dispose 方法,它根本没有被击中......但是后来我发现了这篇文章: davepaquette.com/archive/2013/03/27/… 并添加了这个:ninjectKernel.Bind().ToSelf().InRequestScope();现在我可以看到每次我渲染页面时 Dispose 都会被击中 :) 仍然想要更好的方法来测试这个......关于这件事的任何想法;]?
【解决方案2】:

我建议使用下面的模式 UnitOfWork.Simple 实现。

public interface IUnitOfWork : IDisposable
{
    void Save();
}

public class EntityFrameworkUnitOfWork : IUnitOfWork
{
    private EFDbContext context = new EFDbContext ();
    internal ICityRepository cityRepo;
    public ICityRepository CityRepository
    {
        get
        {
            if (cityRepo== null)
            {
                cityRepo = new EFCityRepository(context);
            }
            return cityRepo;
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多