项目A和项目B均为ASP NET Core 2.2应用程序。
项目B使用Hangfire进行后台工作,而仅执行其他操作,因此使用Hangfire的事实甚至可能都不重要(在底部对此进行更多说明)。项目A在B的Hangfire上安排了工作。
现在,假设我的班级代表一个任务,叫做乔布。它包含在项目C中,该项目C是项目B引用的普通旧类库,并且依次引用了其他包含其正在使用的实体的项目。
依赖关系将通过构造函数注入到此类中:
public class Job
{
public Job(UserManager<ApplicationUser> userManager,
IThisRepository thisRepository,
IThatRepository thatRepository)
{
}
public void Execute(string userId)
{
// this is where the work gets done
}
}
并且在大多数情况下,它们do被注入:IThisRepository
和IThatRepository
被注入,并且它们主要起作用...
在项目B的Startup.cs
中,应该运行此作业,我手动并成功注册了这些接口,以及它们需要其他内容的DbContext
。
由于构造函数需要所有参数,因此UserManager很难手动注册,因此,由于我在工作中不需要[[really,因此我决定进行一些更改。
现在,我正在使用的实体的示例如下:public class Category
{
[Key]
public int Id { get; set; }
// several other properties of primitive types
public ApplicationUser User { get; set; }
[Required]
public string UserId { get; set; }
}
public class Dish
{
[Key]
public int Id { get; set; }
// several other properties of primitive types
public ApplicationUser User { get; set; }
[Required]
public string UserId { get; set; }
public Category Category { get; set; }
[Required]
public string CategoryId { get; set; }
}
现在的问题是这样的:在Job内,我尝试创建一个新的Dish,并将其与用户和类别相关联。因为我只有用户ID,但是没有访问UserManager
的权限,所以我要这样做:
// ... var category = await categoryRepository.FindByUserAndCode(userId, "ABC"); // this is a category that is guaranteed to exist var dish = new Dish(); dish.UserId = userId; // notice there's no dish.User assignment, because I don't have an ApplicationUser object dish.Category = category; dishRepository.Upsert(dish); (which internally either creates a new entity or updates the existing one as appropriate)
并且这都是崩溃的原因,因为它说已经存在一个我要插入的具有相同ID的类别,所以我正在尝试复制一个主键。依赖项已注册。由于该用户具有代码ABC的类别存在于数据库中,所以我认为这很奇怪。
这是东西:存储库返回的
Category
实例确实具有填充的UserId
属性,但User
属性是null
。我认为这是导致我的问题的原因:EF可能会看到该属性为
null
,并且将该对象视为一个新对象。我不知道为什么会出现
null
(甚至对于其他所有都具有引用用户属性的实体也是如此),但是我试图回溯,并且我想不仅仅使用用户ID,尝试让Hangfire实例化向其注入Job
的UserManager<ApplicationUser>
,因此至少我可以通过其ID获取用户的实例。值得注意的是,这在项目A的其他部分中也有效,只是当我执行后台作业时,发生了严重的错误,我无法终生弄清楚它是什么。
然而,
UserManager
的依赖关系很多,我担心我可能吠叫了错误的树,或者完全把它做错了。我说我正在使用Hangfire的事实可能并不重要,因为它的运行假设是:只要给我您的班级名称,我就会实例化它。[[只要
任何人以前都做过这件事,可以帮助我们了解一些情况吗?
dish.Category = category;
category
与上下文分离,则EF将由于此分配而尝试创建它,并且由于它已经存在,因此创建失败。我们看不到categoryRepository.FindByUserAndCode
中发生了什么,但是我想您是在查询中调用AsNoTracking
,还是自己手动创建Category
实例。无论哪种情况,该实例都将从上下文中分离出来。要再次附加它,只需执行以下操作:context.Attach(category);
但是,您在这里没有直接访问您的上下文的权限。这是数据层。您永远不要在EF中使用存储库模式的又一个原因。不好的建议或错误地尝试按照过去的习惯做事,给开发人员带来了如此多的痛苦。EF是ORM(对象关系映射器),这是一种说法,它是自身
DbContext
是工作单元,每个DbSet
已经是一个存储库...。存储库模式用于抽象low-level数据库访问(例如,构造SQL字符串的所有步骤)。 EF已经是一个高级抽象,试图将其塞入另一个存储库模式层只会破坏它,并导致类似您在此处遇到的问题。长而短,问题在于category
已分离。您需要确保一开始它永远不会分离(例如,不要使用AsNoTracking
),或者找到一种方法来确保以后重新连接它。但是,您最好的选择是完全丢弃所有存储库垃圾,而直接使用上下文。选择使用像EF这样的ORM只是选择使用第三方DAL,而不是自己编写。无论如何,在此之上编写自己的代码是错误的。您使用ASP.NET Core中的内置路由框架。您使用内置的模板引擎(即Razor)。您是否觉得有必要对它们进行一些抽象化?当然不是,那么DAL为什么有什么不同?如果仅必须创建一个抽象,则使用有意义的抽象,例如CQRS,服务层或微服务模式。