我被要求使用存储过程进行CRUD,将ASP.NET Identity类映射到现有数据库视图以进行读取操作。有许多StackOverflow问题说明is possible to map to views,this question,this one以及最后this one。
我已将类映射到Views,如下所示 -
var applicationUser = modelBuilder.Entity<applicationUser>().HasKey(au => au.Id) //Specify our own View and Stored Procedure names instead of the default tables
.ToTable("User", "Users").MapToStoredProcedures(sp =>
{
sp.Delete(d => d.HasName("spUser_Delete", "Users"));
sp.Insert(i => i.HasName("spUser_Create", "Users"));
sp.Delete(u => u.HasName("spUser_Update", "Users"));
});
[Users]。[User]是从SQL表[Users]中检索数据的SQL视图。[tblUser]。
不幸的是,我不得不留下至少一个映射到表的类而不是View as Entity Framework生成以下SQL -
SELECT Count(*)
FROM INFORMATION_SCHEMA.TABLES AS t
WHERE t.TABLE_TYPE = 'BASE TABLE'
AND (t.TABLE_SCHEMA + '.' + t.TABLE_NAME IN ('Users.ApplicationRole','Users.User','Users.AuthenticationToken','Users.UserClaim','Users.UserLogin','Users.UserRole','Users.Department','Users.PasswordResetToken','Users.UserDepartment')
OR t.TABLE_NAME = 'EdmMetadata')
go
返回零,因为它们是视图而不是表。
因此,任何使用UserManager的尝试都会导致异常 -
值不能为空。参数名称:source
描述:执行期间发生了未处理的异常 当前的网络请求。请查看堆栈跟踪了解更多信息 有关错误的信息以及它在代码中的起源。
异常详细信息:System.ArgumentNullException:值不能为null。 参数名称:source
来源错误:
第48行:if(ModelState.IsValid)
第49行:{第50行:var userAccount = await UserManager.FindByNameAsync(model.UserName);
第51行:
第52行:if(userAccount == null)
手动将查询更改为 -
SELECT Count(*)
FROM INFORMATION_SCHEMA.TABLES AS t
WHERE (t.TABLE_SCHEMA + '.' + t.TABLE_NAME IN ('Users.ApplicationRole','Users.User','Users.AuthenticationToken','Users.UserClaim','Users.UserLogin','Users.UserRole','Users.Department','Users.PasswordResetToken','Users.UserDepartment')
OR t.TABLE_NAME = 'EdmMetadata')
go
返回正确的九个视图,可能不会导致错误。简单地将其中一个类映射到表就足以说服数据库是正确的并且正常进行。
有没有什么方法可以说服Entity Framework删除“是一个表”的要求,或断言这些表确实存在,因此完全跳过这一步?
编辑:根据请求,UserManager的代码包含在下方 -
AccountController.cs
[Authorize]
public class AccountController : Controller
{
public AccountController()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationIdentityDbContext())))
{
}
public AccountController(UserManager<ApplicationUser> userManager)
{
UserManager = userManager;
}
public UserManager<ApplicationUser> UserManager { get; private set; }
答案 0 :(得分:2)
我设法通过创建一个自定义数据库初始化程序来替换默认的CreateDatabaseIfNotExists初始值设定项来解决此问题。关于Understanding Database Initializers in Entity Framework Code First的Codeguru文章非常有助于我了解正在发生的事情。
解决方案代码 -
using System.Data.Entity;
namespace NexGen.Data.Identity
{
public class IdentityCustomInitializer : IDatabaseInitializer<ApplicationIdentityDbContext>
{
public void InitializeDatabase(ApplicationIdentityDbContext)
{
return; //Do nothing, database will already have been created using scripts
}
}
}
IdentityManager -
public class ApplicationIdentityDbContext: IdentityDbContext<ApplicationUser>
{
public ApplicationIdentityDbContext() : base("DefaultConnection")
{
Database.SetInitializer(new IdentityCustomInitializer());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
作为此代码的结果,Entity Framework不再有任何探测性查询尝试检查数据库是否存在(并且由于假设表格而不是视图被映射而失败) - 而是立即反对查询尝试检索用户数据的视图(然后在初始操作是注册或以其他方式更新用户的情况下执行存储过程)。
答案 1 :(得分:0)
请尝试
[Authorize]
public class AccountController : Controller
{
public AccountController()
{
InitAccountController(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationIdentityDbContext())))
}
private InitAccountController(UserManager<ApplicationUser> userManager)
{
UserManager = userManager;
}
public UserManager<ApplicationUser> UserManager { get; private set; }
}
答案 2 :(得分:0)
更多解释:
在EF6代码中我们可以看到以下函数(DatabaseTableChecker.cs):
public bool AnyModelTableExistsInDatabase(
ObjectContext context, DbConnection connection, List<EntitySet> modelTables, string edmMetadataContextTableName)
{
var modelTablesListBuilder = new StringBuilder();
foreach (var modelTable in modelTables)
{
modelTablesListBuilder.Append("'");
modelTablesListBuilder.Append((string)modelTable.MetadataProperties["Schema"].Value);
modelTablesListBuilder.Append(".");
modelTablesListBuilder.Append(GetTableName(modelTable));
modelTablesListBuilder.Append("',");
}
modelTablesListBuilder.Remove(modelTablesListBuilder.Length - 1, 1);
using (var command = new InterceptableDbCommand(
connection.CreateCommand(), context.InterceptionContext))
{
command.CommandText = @"
SELECT Count(*)
FROM INFORMATION_SCHEMA.TABLES AS t
WHERE t.TABLE_TYPE = 'BASE TABLE'
AND (t.TABLE_SCHEMA + '.' + t.TABLE_NAME IN (" + modelTablesListBuilder + @")
OR t.TABLE_NAME = '" + edmMetadataContextTableName + "')";
var executionStrategy = DbProviderServices.GetExecutionStrategy(connection);
try
{
return executionStrategy.Execute(
() =>
{
if (connection.State == ConnectionState.Broken)
{
connection.Close();
}
if (connection.State == ConnectionState.Closed)
{
connection.Open();
}
return (int)command.ExecuteScalar() > 0;
});
}
finally
{
if (connection.State != ConnectionState.Closed)
{
connection.Close();
}
}
}
}
对应于您发现的内容。
从这个函数我们可能会说,只有当/只有映射到模型的视图时才会出现问题。在这种情况下,初始化程序将数据库视为现有但为空,并尝试创建表。
这会产生问题,因为数据库中仍有/仍有与初始化程序要创建的表相同的视图。
因此,解决方案似乎至少有一个真实表映射到上下文。在这种情况下无需自定义初始化程序。
我认为这是一个问题:model only mapped to views
答案 3 :(得分:0)
根据我的理解和测试,不需要像pwdst那样实现具有空 InitializeDatabase 方法的 IDatabaseInitializer 。
根据我在Understanding Database Initializers in Entity Framework Code First看到的内容,只需拨打
即可Database.SetInitializer<ApplicationIdentityDbContext>(null);
当应用程序初始化时,或者更好的说,在第一次访问数据库之前。
我不会把它放在我的 DbContext 类的ctor中,以避免每次创建 DbContext 实例时都设置初始化程序。相反,我会将它放入应用程序的初始化方法或作为 Main()方法的第一个语句之一。
这适用于使用Entity Framework 6的我的应用程序。