使用IdentityDbContext时BreezeJS AspNetCore无法返回元数据
我在结合IdentityDbContext和Breeze使用时,访问元数据接口时遇到了空引用错误,相关代码和错误信息如下:
上下文代码
public partial class MyContext : IdentityDbContext<ApplicationUser, ApplicationRole, string> { public MyContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<ApplicationUser>().HasMany(u => u.Claims).WithOne().HasForeignKey(c => c.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity<ApplicationUser>().HasMany(u => u.Roles).WithOne().HasForeignKey(r => r.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity<ApplicationRole>().HasMany(r => r.Claims).WithOne().HasForeignKey(c => c.RoleId).IsRequired().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity<ApplicationRole>().HasMany(r => r.Users).WithOne().HasForeignKey(r => r.RoleId).IsRequired().OnDelete(DeleteBehavior.Cascade); } }
ConfigureServices配置
// add identity services.AddIdentity<ApplicationUser, ApplicationRole>() .AddEntityFrameworkStores<MyContext>() .AddDefaultTokenProviders();
Breeze控制器代码
[Route("breeze/[action]")] [BreezeQueryFilter] public class DefaultController : Controller { private readonly NyContext _context; private readonly UnitOfWork _unitOfWork; private readonly MyPersistenceManager _persistenceManager; // called via DI public DefaultController(MyContext context) { _context = context; _unitOfWork = new UnitOfWork(context); _persistenceManager = new MyPersistenceManager(context); } [HttpGet] public IActionResult Metadata() { return Ok(_persistenceManager.Metadata()); } [HttpPost] public SaveResult SaveChanges([FromBody] JObject saveBundle) { return _unitOfWork.Commit(saveBundle); } }
错误信息
{"$id":"1","$type":"Breeze.AspNetCore.ErrorDto, Breeze.AspNetCore.NetCore","Code":0,"Message":"Object reference not set to an instance of an object.","StackTrace":" at Breeze.Persistence.EFCore.MetadataBuilder.BuildAssocName(INavigation prop) in C:\git\Breeze\breeze.server.net\AspNetCore\Breeze.Persistence.EFCore\MetadataBuilder.cs:line 138\r\n at Breeze.Persistence.EFCore.MetadataBuilder.CreateNavProperty(INavigation p) in C:\git\Breeze\breeze.server.net\AspNetCore\Breeze.Persistence.EFCore\MetadataBuilder.cs:line 128\r\n at System.Linq.Enumerable.WhereSelectEnumerableIterator
2.ToList()\r\n at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source)\r\n at Breeze.Persistence.EFCore.MetadataBuilder.CreateMetaType(IEntityType et, Dictionary2 dbSetMap) in C:\\git\\Breeze\\breeze.server.net\\AspNetCore\\Breeze.Persistence.EFCore\\MetadataBuilder.cs:line 89\r\n at System.Linq.Enumerable.WhereSelectEnumerableIterator2.ToList()\r\n at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source)\r\n at Breeze.Persistence.EFCore.MetadataBuilder.GetMetadataFromContext(DbContext dbContext) in C:\\git\\Breeze\\breeze.server.net\\AspNetCore\\Breeze.Persistence.EFCore\\MetadataBuilder.cs:line 21\r\n at Breeze.Persistence.EFCore.EFPersistenceManager1.BuildJsonMetadata() in C:\git\Breeze\breeze.server.net\AspNetCore\Breeze.Persistence.EFCore\EFPersistenceManager.cs:line 109\r\n at Breeze.Persistence.PersistenceManager.Metadata() in C:\git\Breeze\breeze.server.net\AspNetCore\Breeze.Persistence\PersistenceManager.cs:line 34\r\n at DictionaryPortalServer.Controllers.DefaultController.Metadata() in C:\Users\MrTou\Documents\Visual Studio 2017\Projects\Dictionary Project\DP Server\DictionaryPortalServer\Controllers\DefaultController.cs:line 32\r\n at lambda_method(Closure , Object , Object[] )\r\n at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__12.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__10.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__14."
问题分析与解决方案
从堆栈信息可以看出,错误发生在Breeze的MetadataBuilder.BuildAssocName方法中,这是因为EF Core模型中的导航属性缺少对应的逆导航关联信息,导致Breeze无法构建关联名称时抛出空引用。
核心原因
你在OnModelCreating中手动重复配置了Identity默认的实体关系,但IdentityDbContext的基类已经为ApplicationUser.Claims、ApplicationUser.Roles、ApplicationRole.Claims、ApplicationRole.Users这些导航属性配置了完整的关联(包括逆导航)。手动使用.WithOne()而不指定逆导航,会覆盖基类的配置,导致EF Core模型中丢失了这些导航属性的关联元数据,Breeze在解析时就会报错。
修复步骤
移除重复的Identity关系配置
修改你的MyContext类,删除OnModelCreating中重复的Identity实体关系配置,保留基类的默认配置即可:public partial class MyContext : IdentityDbContext<ApplicationUser, ApplicationRole, string> { public MyContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 这里只保留你自定义的其他实体配置(如果有的话),不要重复配置Identity默认的关系 } }确认PersistenceManager的继承关系
确保你的MyPersistenceManager正确继承自EFPersistenceManager<MyContext>,这样Breeze才能正确处理你的Identity实体元数据:public class MyPersistenceManager : EFPersistenceManager<MyContext> { public MyPersistenceManager(MyContext context) : base(context) { } }验证Identity实体的继承
确认ApplicationUser和ApplicationRole正确继承自Identity的基类,没有错误修改默认导航属性:public class ApplicationUser : IdentityUser<string> { // 你的自定义属性 } public class ApplicationRole : IdentityRole<string> { // 你的自定义属性 }
完成这些修改后,重新启动服务,访问http://localhost:3333/Breeze/MetaData应该就能正常获取元数据了。
内容的提问来源于stack exchange,提问作者MrTouya

