实体框架 - 全局vs本地上下文与延迟加载

时间:2014-01-28 14:47:07

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

最终答案:

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

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

(注意:我替换了:

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

我在其中一条评论中提到过:

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和其他方法中)更改为“using”语句,如下所示:

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();

只是为了简化以我想要的方式工作,否则每次我尝试访问地址时,我得到:“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?它必须是存储库和控制器之间的另一层,对吗?

谢谢大家的建议。

祝你好运

2 个答案:

答案 0 :(得分:6)

不是让上下文本地存储库方法,为什么不反过来 - 使存储库独立于上下文:

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引擎还可以通过自动解析构造函数依赖性来显着帮助您。这是另一个故事,但可能是您的架构向前迈出的坚实一步。

答案 1 :(得分:1)

我建议使用使用模式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;
        }
    }
}