Code-First EF Core with Serilog to SQLite - ef-code-first

Application related architecture can be described as:
isolates logging from the application by using abstraction (e.g. Microsoft.Extensions.Logging);
uses use Code-First approach to create a database;
an application may record some information to log before calling the EnsureCreated from DatabaseFacade.
As a result:
Configuring Serilog is part of the IHost creation process when the application starts;
Serilog creates a database in front of EF Core on first run;
EF Core won't create a database because it was created by Serilog;
Questions:
Can I instruct EF Core to update the database schema without creating migration steps for the entire database?
Is it possible to delay/prohibit Serilog from creating a database (e.g. allow only to update existing database)?
Are there any other options for achieving a similar result?

Configure serilog
IDictionary<string, ColumnWriterBase> columnOptions = new Dictionary<string, ColumnWriterBase>
{
{ "message", new RenderedMessageColumnWriter(NpgsqlDbType.Text) },
{ "message_template", new MessageTemplateColumnWriter(NpgsqlDbType.Text) },
{ "level", new LevelColumnWriter(true, NpgsqlDbType.Varchar) },
{ "raise_date", new TimestampColumnWriter(NpgsqlDbType.TimestampTz) },
{ "exception", new ExceptionColumnWriter(NpgsqlDbType.Text) },
{ "properties", new LogEventSerializedColumnWriter(NpgsqlDbType.Jsonb) },
{ "props_test", new PropertiesColumnWriter(NpgsqlDbType.Jsonb) },
{ "machine_name", new SinglePropertyColumnWriter("MachineName", PropertyWriteMethod.ToString, NpgsqlDbType.Text, "l") },
};
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Destructure.UsingAttributes()
.MinimumLevel.Debug()
.WriteTo.PostgreSQL(
connectionString: connectionStringHere,
tableName: "Logs",
columnOptions: columnOptions,
needAutoCreateTable: false,
schemaName: "public",
useCopy: true,
queueLimit: 3000,
batchSizeLimit: 40,
period: new TimeSpan(0, 0, 10),
formatProvider: null)
.CreateLogger();
then you can create C# class mapping those database fields
[Keyless]
[Table("Logs")]
public class ActivityLogs
{
[Column("message")]
public string Message { get; set; }
[Column("message_template")]
public string MessageTemplate { get; set; }
[Column("level")]
public string Level { get; set; }
[Column("raise_date")]
public DateTime RaiseDate { get; set; }
[Column("exception")]
public string Exception { get; set; }
[Column("properties", TypeName = "jsonb")]
public string Properties { get; set; }
[Column("props_test", TypeName = "jsonb")]
public string PropsTest { get; set; }
}
if you want to add more columns then you need to modify your C# model class and columnOptions as well.
PS: Here I have tried to explain how to use Code-First approach to create a database for serilog. Let me know if you have any other question

Related

Json Deserialization in an ASP.NET not working properly

I have an API that returns the following response as string
[
{
"id": "62a9f8f90346133662624bd3",
"referenceID": "test1",
"additionalInfoList": ["string"]
},
{
"id": "62a9fba50346133662624bd4",
"referenceID": "111",
"additionalInfoList": ["string"]
}
]
edit: where the exact formatting of the string is as follows with escaping backslashes:
"[{\"id\":\"62a9f8f90346133662624bd3\",\"referenceID\":\"test1\",\"additionalInfoList\":[\"string\"]},{\"id\":\"62a9fba50346133662624bd4\",\"referenceID\":\"111\",\"additionalInfoList\":[\"string\"]}]"
and the following class model
public class IncidentModel
{
public string id { get; set; }
public string referenceID { get; set; }
public List<string> AdditionalInfoList { get; set; }
}
The problem arises in the code to deserialize. While I get a list with 2 elements, there is no data, only some generic metadata and fields that are not part of the model.
public async Task<JsonResult> OnGetIncidentsAsync()
{
List<IncidentModel> incidents = new List<IncidentModel>();
using (var httpClient = new HttpClient())
{
using (HttpResponseMessage response = await httpClient.GetAsync("api/Incident/GetAllIncidents.json/"))
{
string apiResponse = await response.Content.ReadAsStringAsync();
incidents = JsonConvert.DeserializeObject<List<IncidentModel>>(apiResponse);
}
}
return new JsonResult(incidents);
}
The attached image shows the data inside incidents.
How can I solve this?
So it turns out in Pages, I had a page named Incident. Because I use Razor pages, this page had a Model, named IncidentModel.cshtml.cs which was overriding the IncidentModel.cs from the Models folder. Renaming the model fixed the problem.

Auto Mapper Constructor initialization Mapping Issue

I have the following Mapping configurations:-
Initialized Data:-
private static IEnumerable<Source> InitializeData()
{
var source= new[]
{
new Source("John", "Doe", "1111111111"),
new Source("Jack", "Handsome", "2222222222"),
new Source("Joe", "Mackenze", "3333333333")
};
return source;
}
Source Model:
public class Source
{
private string First { get; set; }
private string Last { get; set; }
private string Phone { get; set; }
public Source(string first, string last, string phone)
{
First = first;
Last = last;
Phone = phone;
}
}
Destination Model
public class Destination
{
public string First { get; set; }
public string Last { get; set; }
public string Phone { get; set; }
}
Main
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
cfg.AllowNullCollections = true;
cfg.CreateMap<Source, Destination>().ReverseMap();
});
var mapper = new Mapper(config);
var source= InitializeData();
var people = mapper.Map<IEnumerable<Destination>>(source);
foreach (var p in people)
{
Console.WriteLine("Name: {0}-{1} Phone: {2}", p.First, p.Last, p.Phone);
}
Console.ReadLine();
}
Problem descriptions:
I have been struggled to understand the AutoMapper mapping between source and destination models.
My source model has a constructor to initialize or accept data from outside. It works fine when I removed the source constructor from the model that's mean flat mapping works fine but constructor initialization has the issue. When I debug in VS2019, it shows the number of records but all fields are empty/null.
What is wrong with the above mapping. I have gone through the AutoMapper reference docs but do not get a hold on this issue.
I highly appreciate your help!
Try calling AssertConfigurationIsValid. Check http://docs.automapper.org/en/latest/Configuration-validation.html.
Your Source properties are private. I assume you meant public.

Mapping to POCOs with top-level dictionary from Postgres jsonb column

I have a .NET Core 3.1 application using EF Core and a Postgres database. In the database I have one jsonb column that I now want to map to a well-defined set of classes in EF Core.
The content of the jsonb column looks like the following:
{
"entry1": {
"name": "entry1",
"contents": {
"entry1.1": {
"name": "entry1.1"
},
"entry1.2": {
"name": "entry1.2",
"contents": {
"entry1.2.1": {
"name": "entry1.2.1"
}
}
}
}
}
}
At the top level it is a dictionary mapping strings to entries. Each entry has a name and can have contents, which is again a dictionary mapping strings to entries.
public class Entry
{
public string name { get; set; }
public Dictionary<string, Entry> contents { get; set; }
}
The jsonb column itself is defined on a table like this:
public class MyTable {
[Column(TypeName = "jsonb")]
public Dictionary<string, Entry> Entries { get; set; }
}
The problem with this now is that it simply doesn't work. When I fetch an entry from the database with EF Core, the "Entries" property does contain indeed a dictionary with a single key "entry1", but the value of that key is an empty Entry object (name and contents are both null).
The Npgsql documentation on mapping jsonb columns to POCOs doesn't explain how to handle dictionaries in this case. I couldn't find any examples with a top-level dictionary in the jsonb column, so I'm not entirely sure I'm doing this right.
How can I wire this up correctly so that my jsonb column gets mapped to a dictionary of Entry objects?
The following seems to work well:
class Program
{
static void Main(string[] args)
{
using (var createCtx = new BlogContext())
{
createCtx.Database.EnsureDeleted();
createCtx.Database.EnsureCreated();
createCtx.Blogs.Add(new Blog
{
Entries = new Dictionary<string, Entry>
{
{ "bla", new Entry { Foo = "foo1" }}
}
});
createCtx.SaveChanges();
}
using var ctx = new BlogContext();
var results = ctx.Blogs.Single();
Console.WriteLine(results.Entries["bla"].Foo);
}
}
public class BlogContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
static ILoggerFactory ContextLoggerFactory
=> LoggerFactory.Create(b => b.AddConsole().AddFilter("", LogLevel.Information));
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseNpgsql(#"Host=localhost;Database=test;Username=npgsql_tests;Password=npgsql_tests")
.EnableSensitiveDataLogging()
.UseLoggerFactory(ContextLoggerFactory);
}
public class Blog
{
public int Id { get; set; }
[Column(TypeName = "jsonb")]
public Dictionary<string, Entry> Entries { get; set; }
}
public class Entry
{
public string Foo { get; set; }
}
In the database:
test=# select * from "Blogs"
test-# ;
Id | Name | Entries
----+------+--------------------------
1 | | {"bla": {"Foo": "foo1"}}
(1 row)

ASP.Net Core - How to get foreign key relation working on POST request for a web-api?

I am building a simple to-do list api using ASP.Net Core. It has two main two main models, a List model and a Task model. Each List has many Tasks. I build the models like this:
List Model:
namespace ToDoList.Models
{
public class List
{
[Key]
public int ListId { get; set; }
[Required]
[StringLength(25)]
public string Title { get; set; }
public string Colour { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
public List()
{
Tasks = new List<Task>();
Colour = "secondary";
}
}
}
Task Model:
namespace ToDoList.Models
{
public class Task
{
[Key]
public int TaskId { get; set; }
[Required]
[StringLength(50)]
public string Title { get; set; }
public bool Done { get; set; }
public int ListId { get; set; }
public virtual List List { get; set; }
public Task()
{
Done = false;
}
}
}
When I send a post request to create a new task I am struggling to get the created task to be added to the Icollection part of the List model.
My Controller looks like this:
// POST: api/Tasks
[HttpPost]
public async Task<ActionResult<Models.Task>> PostTask(Models.Task task)
{
_context.Tasks.Add(task);
await _context.SaveChangesAsync();
return CreatedAtAction("GetTask", new { id = task.TaskId }, task);
}
If I send this data as JSON as a POST request:
{ title: "A New Task", listId: 11 }
I create this Task:
{"taskId":16,"title":"A New Task","done":false,"listId":11,"list":null}
As you can see it has the right listId but the list attached is null.
Also the task does not get added to the list.Tasks collection.
{"listId":11,"title":"Learn ASP.Net Core","colour":"secondary","tasks":[]}
As you can see tasks is still empty.
How do I get it set up that when ever a task is created it is always add to List.Tasks and then Tasks.List has the correct list attached to it, not null.
Also On my SQL Sever Database I expected to see a Tasks in the Lists table but I don't. Can anyone explain why?
SQL Sever Database Columns Picture
You could load the List entity from your DbContext and add it to the Task object you are returning:
[HttpPost]
public async Task<ActionResult<Models.Task>> PostTask(Models.Task task)
{
_context.Tasks.Add(task);
await _context.SaveChangesAsync();
task.List = _context.Lists.Single(task.ListId);
return CreatedAtAction("GetTask", new { id = task.TaskId }, task);
}
or you could return an instance of the Task loaded from the DbContext with included List:
var taskFromDb = _context.Tasks.Include(x => x.List).Single(x => x.Id = task.Id);
return CreatedAtAction("GetTask", new { id = task.TaskId }, taskFromDb);
To get a list with tasks, it needs to be loaded from the DbContext:
var listWithTasks = _context.Lists.Include(x => x.Tasks).Single(x => x.Id == task.ListId);

EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType

I am working with Entity Framework Code First and MVC 5. When I created my application with Individual User Accounts Authentication I was given an Account controller and along with it all the required classes and code that is needed to get the Indiv User Accounts authentication to work.
Among the code already in place was this:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DXContext", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
But then I went ahead and created my own context using code first, so I now have the following too:
public class DXContext : DbContext
{
public DXContext() : base("DXContext")
{
}
public DbSet<ApplicationUser> Users { get; set; }
public DbSet<IdentityRole> Roles { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Paintings> Paintings { get; set; }
}
Finally I have the following seed method to add some data for me to work with whilst developing:
protected override void Seed(DXContext context)
{
try
{
if (!context.Roles.Any(r => r.Name == "Admin"))
{
var store = new RoleStore<IdentityRole>(context);
var manager = new RoleManager<IdentityRole>(store);
var role = new IdentityRole { Name = "Admin" };
manager.Create(role);
}
context.SaveChanges();
if (!context.Users.Any(u => u.UserName == "James"))
{
var store = new UserStore<ApplicationUser>(context);
var manager = new UserManager<ApplicationUser>(store);
var user = new ApplicationUser { UserName = "James" };
manager.Create(user, "ChangeAsap1#");
manager.AddToRole(user.Id, "Admin");
}
context.SaveChanges();
string userId = "";
userId = context.Users.FirstOrDefault().Id;
var artists = new List<Artist>
{
new Artist { FName = "Salvador", LName = "Dali", ImgURL = "http://i62.tinypic.com/ss8txxn.jpg", UrlFriendly = "salvador-dali", Verified = true, ApplicationUserId = userId },
};
artists.ForEach(a => context.Artists.Add(a));
context.SaveChanges();
var paintings = new List<Painting>
{
new Painting { Title = "The Persistence of Memory", ImgUrl = "http://i62.tinypic.com/xx8tssn.jpg", ArtistId = 1, Verified = true, ApplicationUserId = userId }
};
paintings.ForEach(p => context.Paintings.Add(p));
context.SaveChanges();
}
catch (DbEntityValidationException ex)
{
foreach (var validationErrors in ex.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
}
My solution builds fine, but when I try and access a controller that requires access to the database I get the following error:
DX.DOMAIN.Context.IdentityUserLogin: : EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType.
DX.DOMAIN.Context.IdentityUserRole: : EntityType 'IdentityUserRole' has no key defined. Define the key for this EntityType.
What am I doing wrong? Is it because I have two contexts?
UPDATE
After reading Augusto's reply, I went with Option 3. Here is what my DXContext class looks like now:
public class DXContext : DbContext
{
public DXContext() : base("DXContext")
{
// remove default initializer
Database.SetInitializer<DXContext>(null);
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Painting> Paintings { get; set; }
public static DXContext Create()
{
return new DXContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<Role>().ToTable("Roles");
}
public DbQuery<T> Query<T>() where T : class
{
return Set<T>().AsNoTracking();
}
}
I also added a User.cs and a Role.cs class, they look like this:
public class User
{
public int Id { get; set; }
public string FName { get; set; }
public string LName { get; set; }
}
public class Role
{
public int Id { set; get; }
public string Name { set; get; }
}
I wasn't sure if I would need a password property on the user, since the default ApplicationUser has that and a bunch of other fields!
Anyways, the above change builds fine, but again I get this error when the application is ran:
Invalid Column name UserId
UserId is an integer property on my Artist.cs
In my case I had inherited from the IdentityDbContext correctly (with my own custom types and key defined) but had inadvertantly removed the call to the base class's OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); // I had removed this
/// Rest of on model creating here.
}
Which then fixed up my missing indexes from the identity classes and I could then generate migrations and enable migrations appropriately.
The problem is that your ApplicationUser inherits from IdentityUser, which is defined like this:
IdentityUser : IdentityUser<string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>, IUser
....
public virtual ICollection<TRole> Roles { get; private set; }
public virtual ICollection<TClaim> Claims { get; private set; }
public virtual ICollection<TLogin> Logins { get; private set; }
and their primary keys are mapped in the method OnModelCreating of the class IdentityDbContext:
modelBuilder.Entity<TUserRole>()
.HasKey(r => new {r.UserId, r.RoleId})
.ToTable("AspNetUserRoles");
modelBuilder.Entity<TUserLogin>()
.HasKey(l => new {l.LoginProvider, l.ProviderKey, l.UserId})
.ToTable("AspNetUserLogins");
and as your DXContext doesn't derive from it, those keys don't get defined.
If you dig into the sources of Microsoft.AspNet.Identity.EntityFramework, you will understand everything.
I came across this situation some time ago, and I found three possible solutions (maybe there are more):
Use separate DbContexts against two different databases or the same database but different tables.
Merge your DXContext with ApplicationDbContext and use one database.
Use separate DbContexts against the same table and manage their migrations accordingly.
Option 1:
See update the bottom.
Option 2:
You will end up with a DbContext like this one:
public class DXContext : IdentityDbContext<User, Role,
int, UserLogin, UserRole, UserClaim>//: DbContext
{
public DXContext()
: base("name=DXContext")
{
Database.SetInitializer<DXContext>(null);// Remove default initializer
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
}
public static DXContext Create()
{
return new DXContext();
}
//Identity and Authorization
public DbSet<UserLogin> UserLogins { get; set; }
public DbSet<UserClaim> UserClaims { get; set; }
public DbSet<UserRole> UserRoles { get; set; }
// ... your custom DbSets
public DbSet<RoleOperation> RoleOperations { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// Configure Asp Net Identity Tables
modelBuilder.Entity<User>().ToTable("User");
modelBuilder.Entity<User>().Property(u => u.PasswordHash).HasMaxLength(500);
modelBuilder.Entity<User>().Property(u => u.Stamp).HasMaxLength(500);
modelBuilder.Entity<User>().Property(u => u.PhoneNumber).HasMaxLength(50);
modelBuilder.Entity<Role>().ToTable("Role");
modelBuilder.Entity<UserRole>().ToTable("UserRole");
modelBuilder.Entity<UserLogin>().ToTable("UserLogin");
modelBuilder.Entity<UserClaim>().ToTable("UserClaim");
modelBuilder.Entity<UserClaim>().Property(u => u.ClaimType).HasMaxLength(150);
modelBuilder.Entity<UserClaim>().Property(u => u.ClaimValue).HasMaxLength(500);
}
}
Option 3:
You will have one DbContext equal to the option 2. Let's name it IdentityContext. And you will have another DbContext called DXContext:
public class DXContext : DbContext
{
public DXContext()
: base("name=DXContext") // connection string in the application configuration file.
{
Database.SetInitializer<DXContext>(null); // Remove default initializer
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
// Domain Model
public DbSet<User> Users { get; set; }
// ... other custom DbSets
public static DXContext Create()
{
return new DXContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// IMPORTANT: we are mapping the entity User to the same table as the entity ApplicationUser
modelBuilder.Entity<User>().ToTable("User");
}
public DbQuery<T> Query<T>() where T : class
{
return Set<T>().AsNoTracking();
}
}
where User is:
public class User
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
[Required, StringLength(128)]
public string SomeOtherColumn { get; set; }
}
With this solution, I'm mapping the entity User to the same table as the entity ApplicationUser.
Then, using Code First Migrations you'll need to generate the migrations for the IdentityContext and THEN for the DXContext, following this great post from Shailendra Chauhan: Code First Migrations with Multiple Data Contexts
You'll have to modify the migration generated for DXContext. Something like this depending on which properties are shared between ApplicationUser and User:
//CreateTable(
// "dbo.User",
// c => new
// {
// Id = c.Int(nullable: false, identity: true),
// Name = c.String(nullable: false, maxLength: 100),
// SomeOtherColumn = c.String(nullable: false, maxLength: 128),
// })
// .PrimaryKey(t => t.Id);
AddColumn("dbo.User", "SomeOtherColumn", c => c.String(nullable: false, maxLength: 128));
and then running the migrations in order (first the Identity migrations) from the global.asax or any other place of your application using this custom class:
public static class DXDatabaseMigrator
{
public static string ExecuteMigrations()
{
return string.Format("Identity migrations: {0}. DX migrations: {1}.", ExecuteIdentityMigrations(),
ExecuteDXMigrations());
}
private static string ExecuteIdentityMigrations()
{
IdentityMigrationConfiguration configuration = new IdentityMigrationConfiguration();
return RunMigrations(configuration);
}
private static string ExecuteDXMigrations()
{
DXMigrationConfiguration configuration = new DXMigrationConfiguration();
return RunMigrations(configuration);
}
private static string RunMigrations(DbMigrationsConfiguration configuration)
{
List<string> pendingMigrations;
try
{
DbMigrator migrator = new DbMigrator(configuration);
pendingMigrations = migrator.GetPendingMigrations().ToList(); // Just to be able to log which migrations were executed
if (pendingMigrations.Any())
migrator.Update();
}
catch (Exception e)
{
ExceptionManager.LogException(e);
return e.Message;
}
return !pendingMigrations.Any() ? "None" : string.Join(", ", pendingMigrations);
}
}
This way, my n-tier cross-cutting entities don't end up inheriting from AspNetIdentity classes, and therefore I don't have to import this framework in every project where I use them.
Sorry for the extensive post. I hope it could offer some guidance on this. I have already used options 2 and 3 in production environments.
UPDATE: Expand Option 1
For the last two projects I have used the 1st option: having an AspNetUser class that derives from IdentityUser, and a separate custom class called AppUser. In my case, the DbContexts are IdentityContext and DomainContext respectively. And I defined the Id of the AppUser like this:
public class AppUser : TrackableEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
// This Id is equal to the Id in the AspNetUser table and it's manually set.
public override int Id { get; set; }
(TrackableEntity is the custom abstract base class that I use in the overridden SaveChanges method of my DomainContext context)
I first create the AspNetUser and then the AppUser. The drawback with this approach is that you have ensured that your "CreateUser" functionality is transactional (remember that there will be two DbContexts calling SaveChanges separately). Using TransactionScope didn't work for me for some reason, so I ended up doing something ugly but that works for me:
IdentityResult identityResult = UserManager.Create(aspNetUser, model.Password);
if (!identityResult.Succeeded)
throw new TechnicalException("User creation didn't succeed", new LogObjectException(result));
AppUser appUser;
try
{
appUser = RegisterInAppUserTable(model, aspNetUser);
}
catch (Exception)
{
// Roll back
UserManager.Delete(aspNetUser);
throw;
}
(Please, if somebody comes with a better way of doing this part I appreciate commenting or proposing an edit to this answer)
The benefits are that you don't have to modify the migrations and you can use any crazy inheritance hierarchy over the AppUser without messing with the AspNetUser. And actually, I use Automatic Migrations for my IdentityContext (the context that derives from IdentityDbContext):
public sealed class IdentityMigrationConfiguration : DbMigrationsConfiguration<IdentityContext>
{
public IdentityMigrationConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = false;
}
protected override void Seed(IdentityContext context)
{
}
}
This approach also has the benefit of avoiding to have your n-tier cross-cutting entities inheriting from AspNetIdentity classes.
By Changing The DbContext As Below;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}
Just adding in OnModelCreating method call to base.OnModelCreating(modelBuilder); and it becomes fine. I am using EF6.
Special Thanks To #The Senator
For those who use ASP.NET Identity 2.1 and have changed the primary key from the default string to either int or Guid, if you're still getting
EntityType 'xxxxUserLogin' has no key defined. Define the key for this EntityType.
EntityType 'xxxxUserRole' has no key defined. Define the key for this EntityType.
you probably just forgot to specify the new key type on IdentityDbContext:
public class AppIdentityDbContext : IdentityDbContext<
AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>
{
public AppIdentityDbContext()
: base("MY_CONNECTION_STRING")
{
}
......
}
If you just have
public class AppIdentityDbContext : IdentityDbContext
{
......
}
or even
public class AppIdentityDbContext : IdentityDbContext<AppUser>
{
......
}
you will get that 'no key defined' error when you are trying to add migrations or update the database.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
// relationship.DeleteBehavior = DeleteBehavior.Restrict;
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<IdentityRole<string>>().ToTable("Roles");
modelBuilder.Entity<IdentityUserToken<string>>().ToTable("UserTokens");
modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaims");
modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogins");
modelBuilder.Entity<IdentityRoleClaim<string>>().ToTable("RoleClaims");
modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRoles");
}
}
My issue was similar - I had a new table i was creating that ahd to tie in to the identity users. After reading the above answers, realized it had to do with IsdentityUser and the inherited properites. I already had Identity set up as its own Context, so to avoid inherently tying the two together, rather than using the related user table as a true EF property, I set up a non-mapped property with the query to get the related entities. (DataManager is set up to retrieve the current context in which OtherEntity exists.)
[Table("UserOtherEntity")]
public partial class UserOtherEntity
{
public Guid UserOtherEntityId { get; set; }
[Required]
[StringLength(128)]
public string UserId { get; set; }
[Required]
public Guid OtherEntityId { get; set; }
public virtual OtherEntity OtherEntity { get; set; }
}
public partial class UserOtherEntity : DataManager
{
public static IEnumerable<OtherEntity> GetOtherEntitiesByUserId(string userId)
{
return Connect2Context.UserOtherEntities.Where(ue => ue.UserId == userId).Select(ue => ue.OtherEntity);
}
}
public partial class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
[NotMapped]
public IEnumerable<OtherEntity> OtherEntities
{
get
{
return UserOtherEntities.GetOtherEntitiesByUserId(this.Id);
}
}
}

Resources