From f72a8d7f3415b510231a6ec60e020b1dde358059 Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Mon, 19 Aug 2019 15:43:47 +0800 Subject: Add avatar 304. --- Timeline.Tests/DatabaseTest.cs | 37 +++++++++++ Timeline.Tests/Helpers/MyWebApplicationFactory.cs | 4 +- Timeline.Tests/IntegratedTests/UserAvatarTests.cs | 12 ++++ Timeline.Tests/Mock/Data/TestDatabase.cs | 12 ++-- Timeline.Tests/Mock/Data/TestUsers.cs | 40 ++++++------ Timeline.Tests/UserAvatarServiceTest.cs | 17 +++-- Timeline/Controllers/UserAvatarController.cs | 16 ++++- Timeline/Entities/DatabaseContext.cs | 2 +- Timeline/Entities/UserAvatar.cs | 24 ++++++- Timeline/Services/UserAvatarService.cs | 76 +++++++++++++---------- Timeline/Services/UserService.cs | 2 +- 11 files changed, 171 insertions(+), 71 deletions(-) create mode 100644 Timeline.Tests/DatabaseTest.cs diff --git a/Timeline.Tests/DatabaseTest.cs b/Timeline.Tests/DatabaseTest.cs new file mode 100644 index 00000000..e280637c --- /dev/null +++ b/Timeline.Tests/DatabaseTest.cs @@ -0,0 +1,37 @@ +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; +using Timeline.Entities; +using Timeline.Tests.Mock.Data; +using Xunit; + +namespace Timeline.Tests +{ + public class DatabaseTest : IDisposable + { + private readonly TestDatabase _database; + private readonly DatabaseContext _context; + + public DatabaseTest() + { + _database = new TestDatabase(); + _context = _database.DatabaseContext; + } + + public void Dispose() + { + _database.Dispose(); + } + + [Fact] + public void DeleteUserShouldAlsoDeleteAvatar() + { + _context.UserAvatars.Count().Should().Be(2); + var user = _context.Users.First(); + _context.Users.Remove(user); + _context.SaveChanges(); + _context.UserAvatars.Count().Should().Be(1); + } + } +} diff --git a/Timeline.Tests/Helpers/MyWebApplicationFactory.cs b/Timeline.Tests/Helpers/MyWebApplicationFactory.cs index e96d11fe..1a9fe01e 100644 --- a/Timeline.Tests/Helpers/MyWebApplicationFactory.cs +++ b/Timeline.Tests/Helpers/MyWebApplicationFactory.cs @@ -46,9 +46,7 @@ namespace Timeline.Tests.Helpers using (var context = new DatabaseContext(options)) { - context.Database.EnsureCreated(); - context.Users.AddRange(MockUsers.Users); - context.SaveChanges(); + TestDatabase.InitDatabase(context); }; } diff --git a/Timeline.Tests/IntegratedTests/UserAvatarTests.cs b/Timeline.Tests/IntegratedTests/UserAvatarTests.cs index efe63346..794f251b 100644 --- a/Timeline.Tests/IntegratedTests/UserAvatarTests.cs +++ b/Timeline.Tests/IntegratedTests/UserAvatarTests.cs @@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats.Png; using System; using System.IO; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using Timeline.Controllers; using Timeline.Services; @@ -63,6 +64,17 @@ namespace Timeline.Tests.IntegratedTests await GetReturnDefault(); await GetReturnDefault("admin"); + { + var request = new HttpRequestMessage() + { + RequestUri = new Uri(client.BaseAddress, "users/user/avatar"), + Method = HttpMethod.Get, + }; + request.Headers.Add("If-Modified-Since", DateTime.Now.ToString("r")); + var res = await client.SendAsync(request); + res.Should().HaveStatusCode(HttpStatusCode.NotModified); + } + { var res = await client.PutByteArrayAsync("users/user/avatar", new[] { (byte)0x00 }, "image/notaccept"); res.Should().HaveStatusCode(HttpStatusCode.UnsupportedMediaType); diff --git a/Timeline.Tests/Mock/Data/TestDatabase.cs b/Timeline.Tests/Mock/Data/TestDatabase.cs index 09c77dce..7b4bc65e 100644 --- a/Timeline.Tests/Mock/Data/TestDatabase.cs +++ b/Timeline.Tests/Mock/Data/TestDatabase.cs @@ -8,6 +8,13 @@ namespace Timeline.Tests.Mock.Data { public class TestDatabase : IDisposable { + public static void InitDatabase(DatabaseContext context) + { + context.Database.EnsureCreated(); + context.Users.AddRange(MockUsers.CreateMockUsers()); + context.SaveChanges(); + } + private readonly SqliteConnection _databaseConnection; private readonly DatabaseContext _databaseContext; @@ -26,10 +33,7 @@ namespace Timeline.Tests.Mock.Data _databaseContext = new DatabaseContext(options); - // init with mock data - _databaseContext.Database.EnsureCreated(); - _databaseContext.Users.AddRange(MockUsers.Users); - _databaseContext.SaveChanges(); + InitDatabase(_databaseContext); } public void Dispose() diff --git a/Timeline.Tests/Mock/Data/TestUsers.cs b/Timeline.Tests/Mock/Data/TestUsers.cs index f34f62c5..378fc280 100644 --- a/Timeline.Tests/Mock/Data/TestUsers.cs +++ b/Timeline.Tests/Mock/Data/TestUsers.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Timeline.Entities; @@ -10,38 +11,39 @@ namespace Timeline.Tests.Mock.Data { static MockUsers() { - var mockUsers = new List(); - var passwordService = new PasswordService(); + var mockUserInfos = CreateMockUsers().Select(u => UserUtility.CreateUserInfo(u)).ToList(); + UserUserInfo = mockUserInfos[0]; + AdminUserInfo = mockUserInfos[1]; + UserInfos = mockUserInfos; + } - mockUsers.Add(new User + public const string UserUsername = "user"; + public const string AdminUsername = "admin"; + public const string UserPassword = "user"; + public const string AdminPassword = "admin"; + + // emmmmmmm. Never reuse the user instances because EF Core uses them which will cause strange things. + internal static IEnumerable CreateMockUsers() + { + var users = new List(); + var passwordService = new PasswordService(); + users.Add(new User { Name = UserUsername, EncryptedPassword = passwordService.HashPassword(UserPassword), RoleString = UserUtility.IsAdminToRoleString(false), - Version = 0, + Avatar = UserAvatar.Create(DateTime.Now) }); - mockUsers.Add(new User + users.Add(new User { Name = AdminUsername, EncryptedPassword = passwordService.HashPassword(AdminPassword), RoleString = UserUtility.IsAdminToRoleString(true), - Version = 0, + Avatar = UserAvatar.Create(DateTime.Now) }); - - Users = mockUsers; - - var mockUserInfos = mockUsers.Select(u => UserUtility.CreateUserInfo(u)).ToList(); - UserUserInfo = mockUserInfos[0]; - AdminUserInfo = mockUserInfos[1]; - UserInfos = mockUserInfos; + return users; } - public const string UserUsername = "user"; - public const string AdminUsername = "admin"; - public const string UserPassword = "user"; - public const string AdminPassword = "admin"; - - internal static IReadOnlyList Users { get; } public static IReadOnlyList UserInfos { get; } public static UserInfo AdminUserInfo { get; } diff --git a/Timeline.Tests/UserAvatarServiceTest.cs b/Timeline.Tests/UserAvatarServiceTest.cs index 03b64a6b..f11da4f0 100644 --- a/Timeline.Tests/UserAvatarServiceTest.cs +++ b/Timeline.Tests/UserAvatarServiceTest.cs @@ -17,11 +17,15 @@ namespace Timeline.Tests { public class MockDefaultUserAvatarProvider : IDefaultUserAvatarProvider { - public static Avatar Avatar { get; } = new Avatar { Type = "image/test", Data = Encoding.ASCII.GetBytes("test") }; + public static AvatarInfo AvatarInfo { get; } = new AvatarInfo + { + Avatar = new Avatar { Type = "image/test", Data = Encoding.ASCII.GetBytes("test") }, + LastModified = DateTime.Now + }; - public Task GetDefaultAvatar() + public Task GetDefaultAvatar() { - return Task.FromResult(Avatar); + return Task.FromResult(AvatarInfo); } } @@ -153,7 +157,7 @@ namespace Timeline.Tests public async Task GetAvatar_ShouldReturn_Default() { const string username = MockUsers.UserUsername; - (await _service.GetAvatar(username)).Should().BeEquivalentTo(await _mockDefaultUserAvatarProvider.GetDefaultAvatar()); + (await _service.GetAvatar(username)).Avatar.Should().BeEquivalentTo((await _mockDefaultUserAvatarProvider.GetDefaultAvatar()).Avatar); } [Fact] @@ -173,7 +177,7 @@ namespace Timeline.Tests await context.SaveChangesAsync(); } - (await _service.GetAvatar(username)).Should().BeEquivalentTo(MockAvatar); + (await _service.GetAvatar(username)).Avatar.Should().BeEquivalentTo(MockAvatar); } [Fact] @@ -223,7 +227,8 @@ namespace Timeline.Tests // delete await _service.SetAvatar(username, null); - user.Avatar.Should().BeNull(); + user.Avatar.Type.Should().BeNull(); + user.Avatar.Data.Should().BeNull(); } } } diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs index 710ca764..89d2650c 100644 --- a/Timeline/Controllers/UserAvatarController.cs +++ b/Timeline/Controllers/UserAvatarController.cs @@ -57,10 +57,22 @@ namespace Timeline.Controllers [Authorize] public async Task Get(string username) { + const string IfModifiedSinceHeaderKey = "If-Modified-Since"; try { - var avatar = await _service.GetAvatar(username); - return File(avatar.Data, avatar.Type); + var avatarInfo = await _service.GetAvatar(username); + var avatar = avatarInfo.Avatar; + if (Request.Headers.TryGetValue(IfModifiedSinceHeaderKey, out var value)) + { + var t = DateTime.Parse(value); + if (t > avatarInfo.LastModified) + { + Response.Headers.Add(IfModifiedSinceHeaderKey, avatarInfo.LastModified.ToString("r")); + return StatusCode(StatusCodes.Status304NotModified); + } + } + + return File(avatar.Data, avatar.Type, new DateTimeOffset(avatarInfo.LastModified), null); } catch (UserNotExistException e) { diff --git a/Timeline/Entities/DatabaseContext.cs b/Timeline/Entities/DatabaseContext.cs index f32e5992..b12db46e 100644 --- a/Timeline/Entities/DatabaseContext.cs +++ b/Timeline/Entities/DatabaseContext.cs @@ -28,6 +28,7 @@ namespace Timeline.Entities [Column("version"), Required] public long Version { get; set; } + [Required] public UserAvatar Avatar { get; set; } } @@ -42,7 +43,6 @@ namespace Timeline.Entities protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().Property(e => e.Version).HasDefaultValue(0); - } public DbSet Users { get; set; } diff --git a/Timeline/Entities/UserAvatar.cs b/Timeline/Entities/UserAvatar.cs index d7c24403..a2fd6821 100644 --- a/Timeline/Entities/UserAvatar.cs +++ b/Timeline/Entities/UserAvatar.cs @@ -1,5 +1,7 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Timeline.Services; namespace Timeline.Entities { @@ -9,10 +11,26 @@ namespace Timeline.Entities [Column("id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long Id { get; set; } - [Column("data"), Required] + [Column("data")] public byte[] Data { get; set; } - [Column("type"), Required] + [Column("type")] public string Type { get; set; } + + [Column("last_modified"), Required] + public DateTime LastModified { get; set; } + + public long UserId { get; set; } + + public static UserAvatar Create(DateTime lastModified) + { + return new UserAvatar + { + Id = 0, + Data = null, + Type = null, + LastModified = lastModified + }; + } } } diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs index dd0e5e7c..a83b8a52 100644 --- a/Timeline/Services/UserAvatarService.cs +++ b/Timeline/Services/UserAvatarService.cs @@ -18,6 +18,12 @@ namespace Timeline.Services public byte[] Data { get; set; } } + public class AvatarInfo + { + public Avatar Avatar { get; set; } + public DateTime LastModified { get; set; } + } + /// /// Thrown when avatar is of bad format. /// @@ -61,7 +67,7 @@ namespace Timeline.Services /// /// Get the default avatar. /// - Task GetDefaultAvatar(); + Task GetDefaultAvatar(); } public interface IUserAvatarValidator @@ -80,10 +86,10 @@ namespace Timeline.Services /// Get avatar of a user. If the user has no avatar, a default one is returned. /// /// The username of the user to get avatar of. - /// The avatar. + /// The avatar info. /// Thrown if is null or empty. /// Thrown if the user does not exist. - Task GetAvatar(string username); + Task GetAvatar(string username); /// /// Set avatar for a user. @@ -106,12 +112,17 @@ namespace Timeline.Services _environment = environment; } - public async Task GetDefaultAvatar() + public async Task GetDefaultAvatar() { - return new Avatar + var path = Path.Combine(_environment.ContentRootPath, "default-avatar.png"); + return new AvatarInfo { - Type = "image/png", - Data = await File.ReadAllBytesAsync(Path.Combine(_environment.ContentRootPath, "default-avatar.png")) + Avatar = new Avatar + { + Type = "image/png", + Data = await File.ReadAllBytesAsync(path) + }, + LastModified = File.GetLastWriteTime(path) }; } } @@ -134,7 +145,7 @@ namespace Timeline.Services } catch (UnknownImageFormatException e) { - throw new AvatarDataException(avatar, AvatarDataException.ErrorReason.CantDecode, "Failed to decode image. See inner exception.", e); + throw new AvatarDataException(avatar, AvatarDataException.ErrorReason.CantDecode, "Failed to decode image. See inner exception.", e); } }); } @@ -158,7 +169,7 @@ namespace Timeline.Services _avatarValidator = avatarValidator; } - public async Task GetAvatar(string username) + public async Task GetAvatar(string username) { if (string.IsNullOrEmpty(username)) throw new ArgumentException("Username is null or empty.", nameof(username)); @@ -170,16 +181,26 @@ namespace Timeline.Services await _database.Entry(user).Reference(u => u.Avatar).LoadAsync(); var avatar = user.Avatar; - if (avatar == null) + if ((avatar.Type == null) == (avatar.Data == null)) + _logger.LogCritical("Database corupted! One of type and data of a avatar is null but the other is not."); + // TODO: Throw an exception to indicate this. + + if (avatar.Data == null) { - return await _defaultUserAvatarProvider.GetDefaultAvatar(); + var defaultAvatar = await _defaultUserAvatarProvider.GetDefaultAvatar(); + defaultAvatar.LastModified = defaultAvatar.LastModified > avatar.LastModified ? defaultAvatar.LastModified : avatar.LastModified; + return defaultAvatar; } else { - return new Avatar + return new AvatarInfo { - Type = avatar.Type, - Data = avatar.Data + Avatar = new Avatar + { + Type = avatar.Type, + Data = avatar.Data + }, + LastModified = avatar.LastModified }; } } @@ -206,34 +227,25 @@ namespace Timeline.Services if (avatar == null) { - if (avatarEntity == null) + if (avatarEntity.Data == null) return; else { - _database.UserAvatars.Remove(avatarEntity); + avatarEntity.Data = null; + avatarEntity.Type = null; + avatarEntity.LastModified = DateTime.Now; await _database.SaveChangesAsync(); - _logger.LogInformation("Removed an entry in user_avatars."); + _logger.LogInformation("Updated an entry in user_avatars."); } } else { await _avatarValidator.Validate(avatar); - - if (avatarEntity == null) - { - user.Avatar = new UserAvatar - { - Type = avatar.Type, - Data = avatar.Data - }; - } - else - { - avatarEntity.Type = avatar.Type; - avatarEntity.Data = avatar.Data; - } + avatarEntity.Type = avatar.Type; + avatarEntity.Data = avatar.Data; + avatarEntity.LastModified = DateTime.Now; await _database.SaveChangesAsync(); - _logger.LogInformation("Added or modified an entry in user_avatars."); + _logger.LogInformation("Updated an entry in user_avatars."); } } } diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 96c3e256..347b8cbb 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -376,7 +376,7 @@ namespace Timeline.Services Name = username, EncryptedPassword = _passwordService.HashPassword(password), RoleString = IsAdminToRoleString(administrator), - Version = 0 + Avatar = UserAvatar.Create(DateTime.Now) }; await _databaseContext.AddAsync(newUser); await _databaseContext.SaveChangesAsync(); -- cgit v1.2.3 From 2e1aaf583bb099e175ea9358a6961834a761d861 Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Mon, 19 Aug 2019 16:03:47 +0800 Subject: Add migration. --- ...0190819074906_AddAvatarLastModified.Designer.cs | 86 ++++++++++++++++ .../20190819074906_AddAvatarLastModified.cs | 114 +++++++++++++++++++++ .../Migrations/DatabaseContextModelSnapshot.cs | 23 +++-- 3 files changed, 213 insertions(+), 10 deletions(-) create mode 100644 Timeline/Migrations/20190819074906_AddAvatarLastModified.Designer.cs create mode 100644 Timeline/Migrations/20190819074906_AddAvatarLastModified.cs diff --git a/Timeline/Migrations/20190819074906_AddAvatarLastModified.Designer.cs b/Timeline/Migrations/20190819074906_AddAvatarLastModified.Designer.cs new file mode 100644 index 00000000..a6fe7941 --- /dev/null +++ b/Timeline/Migrations/20190819074906_AddAvatarLastModified.Designer.cs @@ -0,0 +1,86 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Timeline.Entities; + +namespace Timeline.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20190819074906_AddAvatarLastModified")] + partial class AddAvatarLastModified + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Timeline.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("id"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnName("password"); + + b.Property("Name") + .IsRequired() + .HasColumnName("name") + .HasMaxLength(26); + + b.Property("RoleString") + .IsRequired() + .HasColumnName("roles"); + + b.Property("Version") + .ValueGeneratedOnAdd() + .HasColumnName("version") + .HasDefaultValue(0L); + + b.HasKey("Id"); + + b.ToTable("users"); + }); + + modelBuilder.Entity("Timeline.Entities.UserAvatar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("id"); + + b.Property("Data") + .HasColumnName("data"); + + b.Property("LastModified") + .HasColumnName("last_modified"); + + b.Property("Type") + .HasColumnName("type"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("user_avatars"); + }); + + modelBuilder.Entity("Timeline.Entities.UserAvatar", b => + { + b.HasOne("Timeline.Entities.User") + .WithOne("Avatar") + .HasForeignKey("Timeline.Entities.UserAvatar", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Timeline/Migrations/20190819074906_AddAvatarLastModified.cs b/Timeline/Migrations/20190819074906_AddAvatarLastModified.cs new file mode 100644 index 00000000..d9b5e8cf --- /dev/null +++ b/Timeline/Migrations/20190819074906_AddAvatarLastModified.cs @@ -0,0 +1,114 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Timeline.Migrations +{ + public partial class AddAvatarLastModified : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_users_user_avatars_AvatarId", + table: "users"); + + migrationBuilder.DropIndex( + name: "IX_users_AvatarId", + table: "users"); + + migrationBuilder.DropColumn( + name: "AvatarId", + table: "users"); + + migrationBuilder.AlterColumn( + name: "type", + table: "user_avatars", + nullable: true, + oldClrType: typeof(string)); + + migrationBuilder.AlterColumn( + name: "data", + table: "user_avatars", + nullable: true, + oldClrType: typeof(byte[])); + + migrationBuilder.AddColumn( + name: "last_modified", + table: "user_avatars", + nullable: false, + defaultValue: DateTime.Now); + + migrationBuilder.AddColumn( + name: "UserId", + table: "user_avatars", + nullable: false, + defaultValue: 0L); + + migrationBuilder.CreateIndex( + name: "IX_user_avatars_UserId", + table: "user_avatars", + column: "UserId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_user_avatars_users_UserId", + table: "user_avatars", + column: "UserId", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + // Note! Remember to manually create avatar entities for all users. + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_user_avatars_users_UserId", + table: "user_avatars"); + + migrationBuilder.DropIndex( + name: "IX_user_avatars_UserId", + table: "user_avatars"); + + migrationBuilder.DropColumn( + name: "last_modified", + table: "user_avatars"); + + migrationBuilder.DropColumn( + name: "UserId", + table: "user_avatars"); + + migrationBuilder.AddColumn( + name: "AvatarId", + table: "users", + nullable: true); + + migrationBuilder.AlterColumn( + name: "type", + table: "user_avatars", + nullable: false, + oldClrType: typeof(string), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "data", + table: "user_avatars", + nullable: false, + oldClrType: typeof(byte[]), + oldNullable: true); + + migrationBuilder.CreateIndex( + name: "IX_users_AvatarId", + table: "users", + column: "AvatarId"); + + migrationBuilder.AddForeignKey( + name: "FK_users_user_avatars_AvatarId", + table: "users", + column: "AvatarId", + principalTable: "user_avatars", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + } + } +} diff --git a/Timeline/Migrations/DatabaseContextModelSnapshot.cs b/Timeline/Migrations/DatabaseContextModelSnapshot.cs index 152bdea4..de96dc40 100644 --- a/Timeline/Migrations/DatabaseContextModelSnapshot.cs +++ b/Timeline/Migrations/DatabaseContextModelSnapshot.cs @@ -23,8 +23,6 @@ namespace Timeline.Migrations .ValueGeneratedOnAdd() .HasColumnName("id"); - b.Property("AvatarId"); - b.Property("EncryptedPassword") .IsRequired() .HasColumnName("password"); @@ -45,8 +43,6 @@ namespace Timeline.Migrations b.HasKey("Id"); - b.HasIndex("AvatarId"); - b.ToTable("users"); }); @@ -57,23 +53,30 @@ namespace Timeline.Migrations .HasColumnName("id"); b.Property("Data") - .IsRequired() .HasColumnName("data"); + b.Property("LastModified") + .HasColumnName("last_modified"); + b.Property("Type") - .IsRequired() .HasColumnName("type"); + b.Property("UserId"); + b.HasKey("Id"); + b.HasIndex("UserId") + .IsUnique(); + b.ToTable("user_avatars"); }); - modelBuilder.Entity("Timeline.Entities.User", b => + modelBuilder.Entity("Timeline.Entities.UserAvatar", b => { - b.HasOne("Timeline.Entities.UserAvatar", "Avatar") - .WithMany() - .HasForeignKey("AvatarId"); + b.HasOne("Timeline.Entities.User") + .WithOne("Avatar") + .HasForeignKey("Timeline.Entities.UserAvatar", "UserId") + .OnDelete(DeleteBehavior.Cascade); }); #pragma warning restore 612, 618 } -- cgit v1.2.3 From 9168f07d1f35b5ba670b3c1d900f186ac868284b Mon Sep 17 00:00:00 2001 From: 杨宇千 Date: Mon, 19 Aug 2019 16:09:11 +0800 Subject: Add index for username. --- Timeline/Entities/DatabaseContext.cs | 1 + Timeline/Entities/UserAvatar.cs | 1 - .../20190819080823_AddIndexForUserName.Designer.cs | 88 ++++++++++++++++++++++ .../20190819080823_AddIndexForUserName.cs | 22 ++++++ .../Migrations/DatabaseContextModelSnapshot.cs | 2 + 5 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 Timeline/Migrations/20190819080823_AddIndexForUserName.Designer.cs create mode 100644 Timeline/Migrations/20190819080823_AddIndexForUserName.cs diff --git a/Timeline/Entities/DatabaseContext.cs b/Timeline/Entities/DatabaseContext.cs index b12db46e..bc06b9df 100644 --- a/Timeline/Entities/DatabaseContext.cs +++ b/Timeline/Entities/DatabaseContext.cs @@ -43,6 +43,7 @@ namespace Timeline.Entities protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().Property(e => e.Version).HasDefaultValue(0); + modelBuilder.Entity().HasIndex(e => e.Name); } public DbSet Users { get; set; } diff --git a/Timeline/Entities/UserAvatar.cs b/Timeline/Entities/UserAvatar.cs index a2fd6821..b941445d 100644 --- a/Timeline/Entities/UserAvatar.cs +++ b/Timeline/Entities/UserAvatar.cs @@ -1,7 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using Timeline.Services; namespace Timeline.Entities { diff --git a/Timeline/Migrations/20190819080823_AddIndexForUserName.Designer.cs b/Timeline/Migrations/20190819080823_AddIndexForUserName.Designer.cs new file mode 100644 index 00000000..d45a057d --- /dev/null +++ b/Timeline/Migrations/20190819080823_AddIndexForUserName.Designer.cs @@ -0,0 +1,88 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Timeline.Entities; + +namespace Timeline.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20190819080823_AddIndexForUserName")] + partial class AddIndexForUserName + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Timeline.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("id"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnName("password"); + + b.Property("Name") + .IsRequired() + .HasColumnName("name") + .HasMaxLength(26); + + b.Property("RoleString") + .IsRequired() + .HasColumnName("roles"); + + b.Property("Version") + .ValueGeneratedOnAdd() + .HasColumnName("version") + .HasDefaultValue(0L); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("users"); + }); + + modelBuilder.Entity("Timeline.Entities.UserAvatar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("id"); + + b.Property("Data") + .HasColumnName("data"); + + b.Property("LastModified") + .HasColumnName("last_modified"); + + b.Property("Type") + .HasColumnName("type"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("user_avatars"); + }); + + modelBuilder.Entity("Timeline.Entities.UserAvatar", b => + { + b.HasOne("Timeline.Entities.User") + .WithOne("Avatar") + .HasForeignKey("Timeline.Entities.UserAvatar", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Timeline/Migrations/20190819080823_AddIndexForUserName.cs b/Timeline/Migrations/20190819080823_AddIndexForUserName.cs new file mode 100644 index 00000000..b910a174 --- /dev/null +++ b/Timeline/Migrations/20190819080823_AddIndexForUserName.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Timeline.Migrations +{ + public partial class AddIndexForUserName : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_users_name", + table: "users", + column: "name"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_users_name", + table: "users"); + } + } +} diff --git a/Timeline/Migrations/DatabaseContextModelSnapshot.cs b/Timeline/Migrations/DatabaseContextModelSnapshot.cs index de96dc40..0eb85997 100644 --- a/Timeline/Migrations/DatabaseContextModelSnapshot.cs +++ b/Timeline/Migrations/DatabaseContextModelSnapshot.cs @@ -43,6 +43,8 @@ namespace Timeline.Migrations b.HasKey("Id"); + b.HasIndex("Name"); + b.ToTable("users"); }); -- cgit v1.2.3