为何仓储Get/GetAsync方法抛EntityNotFoundException而非返回null?能否修改?
Hey Nicolas, great question about the behavior of Repository.Get() in module-zero-core-template—let’s break this down clearly:
1. Why does Get() throw an exception instead of returning null?
This is intentional design from the ABP framework (which module-zero is built on) following a fail-fast philosophy:
- The
Get(id)method’s semantic contract is: "I expect this entity to exist in the database. If it doesn’t, that’s an unexpected error that should be handled immediately." - For example, if you’re fetching a user ID that comes from an authenticated request or validated input, the absence of that user would indicate a data inconsistency or invalid state—not a normal business scenario.
- In contrast,
FirstOrDefault()is explicitly for cases where the entity might not exist, and your code is prepared to handle bothnulland a valid entity.
This design helps catch bugs early instead of letting null propagate through your code and cause harder-to-debug issues later (like NullReferenceExceptions in unrelated parts of the system).
2. Can you modify this behavior?
Absolutely—here are two practical approaches, depending on how broadly you want to change the behavior:
Option 1: Create a reusable extension method (safe, non-breaking)
If you just want a way to get null instead of exceptions without changing existing repository behavior, add an extension method to your project:
using Abp.Domain.Repositories; using Abp.Domain.Entities; public static class RepositoryExtensions { // Synchronous version public static T GetOrNull<T, TPrimaryKey>(this IRepository<T, TPrimaryKey> repository, TPrimaryKey id) where T : class, IEntity<TPrimaryKey> { return repository.FirstOrDefault(e => e.Id.Equals(id)); } // Async version public static async Task<T> GetOrNullAsync<T, TPrimaryKey>(this IRepository<T, TPrimaryKey> repository, TPrimaryKey id) where T : class, IEntity<TPrimaryKey> { return await repository.FirstOrDefaultAsync(e => e.Id.Equals(id)); } }
Now you can use this everywhere you need a nullable result:
var user = _userRepository.GetOrNull(model.UserId); // Or async: var user = await _userRepository.GetOrNullAsync(model.UserId);
This keeps the original Get() behavior intact for other parts of the codebase that rely on its exception-throwing semantics.
Option 2: Override the repository’s Get methods (global change)
If you want to change the behavior for all calls to UserRepository.Get(), you can create a custom repository implementation that overrides the base methods:
using Abp.Domain.Repositories; using Abp.EntityFrameworkCore.Repositories; using YourProjectName.EntityFrameworkCore; using YourProjectName.Users; public class CustomUserRepository : EfCoreRepositoryBase<YourDbContext, User, long>, IUserRepository { public CustomUserRepository(IDbContextProvider<YourDbContext> dbContextProvider) : base(dbContextProvider) { } public override User Get(long id) { return FirstOrDefault(u => u.Id == id); } public override async Task<User> GetAsync(long id) { return await FirstOrDefaultAsync(u => u.Id == id); } }
Then update your module’s dependency injection configuration to replace the default repository with your custom one:
public override void ConfigureServices(ServiceConfigurationContext context) { // ... other configs context.Services.Replace(ServiceDescriptor.Scoped<IUserRepository, CustomUserRepository>()); }
⚠️ Warning: This changes the behavior globally for all uses of IUserRepository. Make sure no existing code relies on Get() throwing an exception—otherwise, you could introduce silent failures where null is returned but the code expects a valid entity.
内容的提问来源于stack exchange,提问作者Nicolas Perego

