reorganization of database layer

This commit is contained in:
2020-07-25 22:15:58 +02:00
parent 2a86c16b85
commit 110663456d
25 changed files with 471 additions and 370 deletions

View File

@ -0,0 +1,73 @@
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
namespace UserService.DatabaseLayer.DataModels
{
public static class ModelBuilderExtensions
{
public static void Seed(this ModelBuilder modelBuilder)
{
var groups = new OrganizationUnit { CommonName = "Groups", Id = -1 };
var users = new OrganizationUnit { CommonName = "Users", Id = -2 };
var germany = new OrganizationUnit{CommonName = "Germany", Id = -6, ParentId = -2};
var usa = new OrganizationUnit{CommonName = "USA", Id = -5, ParentId = -2};
var arizona = new OrganizationUnit{CommonName = "Arizona" , Id = -4, ParentId = -5 };
var france = new OrganizationUnit{CommonName = "France" , Id = -3, ParentId = -2 };
modelBuilder.Entity<OrganizationUnit>().HasData(users, groups, germany, usa, arizona, france);
var user = new User { CommonName = "holger", IsActive = true, Id = -7, ParentId = germany.Id };
modelBuilder.Entity<User>().HasData(user);
var secGroup = new SecurityGroup { CommonName = "Global Admin", Id = -8, ParentId = groups.Id };
modelBuilder.Entity<SecurityGroup>().HasData(secGroup);
modelBuilder.Entity<UserMember>()
.HasData(new UserMember { MemberId = secGroup.Id, UserId = user.Id });
}
public static void CreateRelations(this ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserMember>()
.HasKey(bc => new { bc.MemberId, bc.UserId });
modelBuilder.Entity<UserMember>()
.HasOne(bc => bc.User)
.WithMany(b => b!.MemberOf)
.HasForeignKey(bc => bc.UserId);
modelBuilder.Entity<UserMember>()
.HasOne(bc => bc.Member)
.WithMany(c => c!.Members)
.HasForeignKey(bc => bc.MemberId);
modelBuilder.Entity<Node>()
.HasMany(c => c.Children)
.WithOne(e => e.Parent!)
.HasForeignKey(bc => bc.ParentId);
}
}
public static class UserExtensions
{
public static IEnumerable<SecurityGroup> GetSecurityGroups(this User user)
{
foreach (var userMember in user.MemberOf)
{
if (userMember.Member is SecurityGroup securityGroup)
{
yield return securityGroup;
}
}
}
}
public static class SecurityGroupExtensions
{
public static IEnumerable<User> GetUsers(this SecurityGroup securityGroup)
{
foreach (var userMember in securityGroup.Members)
{
if (userMember.User is null) continue;
yield return userMember.User;
}
}
}
}

View File

@ -0,0 +1,62 @@
#nullable enable
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace UserService.DatabaseLayer.DataModels
{
public class OrganizationUnit : Node
{
public Member? Manager { get; set; }
}
public class SecurityGroup : Member
{
}
public class User : Member
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public bool IsActive { get; set; }
public string FullName => $"{FirstName} {LastName}";
public IEnumerable<UserMember> MemberOf { get; set; } = new List<UserMember>();
public User Clone() => (User)MemberwiseClone();
}
public class UserMember
{
public int MemberId { get; set; }
public Member? Member { get; set; }
public int UserId { get; set; }
public User? User { get; set; }
}
public abstract class Member : Node
{
[EmailAddress]
public string? EMail { get; set; }
public ICollection<UserMember> Members { get; set; } = new List<UserMember>();
}
public abstract class Node
{
public int Id { get; set; }
[Required] public string CommonName { get; set; } = null!;
public string? Description { get; set; }
public ICollection<Node> Children { get; set; } = new List<Node>();
public Node? Parent { get; set; } //Parent
public int? ParentId { get; set; }
public override string ToString()
{
return $"[{GetType().Name}] {Id:D5} {CommonName}";
}
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore;
namespace UserService.DatabaseLayer.DataModels
{
public class UserServiceDbContext : DbContext
{
public DbSet<User> Users { get; set; } = null!;
public DbSet<SecurityGroup> SecurityGroups { get; set; } = null!;
public DbSet<UserMember> UserMembers { get; set; } = null!;
public DbSet<OrganizationUnit> OrganizationUnits { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite(@"Data Source=C:\Users\holger\Desktop\UserService\UserService.db");
/// <inheritdoc />
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.CreateRelations();
modelBuilder.Seed();
}
}
}

View File

@ -0,0 +1,201 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using UserService.DatabaseLayer.DataModels;
namespace UserService.DatabaseLayer.Migrations
{
[DbContext(typeof(UserServiceDbContext))]
[Migration("20200725195658_initial")]
partial class initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.6");
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Node", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CommonName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("ParentId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("Node");
b.HasDiscriminator<string>("Discriminator").HasValue("Node");
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b =>
{
b.Property<int>("MemberId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("MemberId", "UserId");
b.HasIndex("UserId");
b.ToTable("UserMembers");
b.HasData(
new
{
MemberId = -8,
UserId = -7
});
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Member", b =>
{
b.HasBaseType("UserService.DatabaseLayer.DataModels.Node");
b.Property<string>("EMail")
.HasColumnType("TEXT");
b.HasDiscriminator().HasValue("Member");
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.OrganizationUnit", b =>
{
b.HasBaseType("UserService.DatabaseLayer.DataModels.Node");
b.Property<int?>("ManagerId")
.HasColumnType("INTEGER");
b.HasIndex("ManagerId");
b.HasDiscriminator().HasValue("OrganizationUnit");
b.HasData(
new
{
Id = -2,
CommonName = "Users"
},
new
{
Id = -1,
CommonName = "Groups"
},
new
{
Id = -6,
CommonName = "Germany",
ParentId = -2
},
new
{
Id = -5,
CommonName = "USA",
ParentId = -2
},
new
{
Id = -4,
CommonName = "Arizona",
ParentId = -5
},
new
{
Id = -3,
CommonName = "France",
ParentId = -2
});
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.SecurityGroup", b =>
{
b.HasBaseType("UserService.DatabaseLayer.DataModels.Member");
b.HasDiscriminator().HasValue("SecurityGroup");
b.HasData(
new
{
Id = -8,
CommonName = "Global Admin",
ParentId = -1
});
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.User", b =>
{
b.HasBaseType("UserService.DatabaseLayer.DataModels.Member");
b.Property<string>("FirstName")
.HasColumnType("TEXT");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<string>("LastName")
.HasColumnType("TEXT");
b.HasDiscriminator().HasValue("User");
b.HasData(
new
{
Id = -7,
CommonName = "holger",
ParentId = -6,
IsActive = true
});
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Node", b =>
{
b.HasOne("UserService.DatabaseLayer.DataModels.Node", "Parent")
.WithMany("Children")
.HasForeignKey("ParentId");
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b =>
{
b.HasOne("UserService.DatabaseLayer.DataModels.Member", "Member")
.WithMany("Members")
.HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("UserService.DatabaseLayer.DataModels.User", "User")
.WithMany("MemberOf")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.OrganizationUnit", b =>
{
b.HasOne("UserService.DatabaseLayer.DataModels.Member", "Manager")
.WithMany()
.HasForeignKey("ManagerId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,136 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace UserService.DatabaseLayer.Migrations
{
public partial class initial : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Node",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
CommonName = table.Column<string>(nullable: false),
Description = table.Column<string>(nullable: true),
ParentId = table.Column<int>(nullable: true),
Discriminator = table.Column<string>(nullable: false),
EMail = table.Column<string>(nullable: true),
FirstName = table.Column<string>(nullable: true),
LastName = table.Column<string>(nullable: true),
IsActive = table.Column<bool>(nullable: true),
ManagerId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Node", x => x.Id);
table.ForeignKey(
name: "FK_Node_Node_ParentId",
column: x => x.ParentId,
principalTable: "Node",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Node_Node_ManagerId",
column: x => x.ManagerId,
principalTable: "Node",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "UserMembers",
columns: table => new
{
MemberId = table.Column<int>(nullable: false),
UserId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserMembers", x => new { x.MemberId, x.UserId });
table.ForeignKey(
name: "FK_UserMembers_Node_MemberId",
column: x => x.MemberId,
principalTable: "Node",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_UserMembers_Node_UserId",
column: x => x.UserId,
principalTable: "Node",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.InsertData(
table: "Node",
columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "ManagerId" },
values: new object[] { -2, "Users", null, "OrganizationUnit", null, null });
migrationBuilder.InsertData(
table: "Node",
columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "ManagerId" },
values: new object[] { -1, "Groups", null, "OrganizationUnit", null, null });
migrationBuilder.InsertData(
table: "Node",
columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "ManagerId" },
values: new object[] { -6, "Germany", null, "OrganizationUnit", -2, null });
migrationBuilder.InsertData(
table: "Node",
columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "ManagerId" },
values: new object[] { -5, "USA", null, "OrganizationUnit", -2, null });
migrationBuilder.InsertData(
table: "Node",
columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "ManagerId" },
values: new object[] { -3, "France", null, "OrganizationUnit", -2, null });
migrationBuilder.InsertData(
table: "Node",
columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "EMail" },
values: new object[] { -8, "Global Admin", null, "SecurityGroup", -1, null });
migrationBuilder.InsertData(
table: "Node",
columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "EMail", "FirstName", "IsActive", "LastName" },
values: new object[] { -7, "holger", null, "User", -6, null, null, true, null });
migrationBuilder.InsertData(
table: "Node",
columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "ManagerId" },
values: new object[] { -4, "Arizona", null, "OrganizationUnit", -5, null });
migrationBuilder.InsertData(
table: "UserMembers",
columns: new[] { "MemberId", "UserId" },
values: new object[] { -8, -7 });
migrationBuilder.CreateIndex(
name: "IX_Node_ParentId",
table: "Node",
column: "ParentId");
migrationBuilder.CreateIndex(
name: "IX_Node_ManagerId",
table: "Node",
column: "ManagerId");
migrationBuilder.CreateIndex(
name: "IX_UserMembers_UserId",
table: "UserMembers",
column: "UserId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "UserMembers");
migrationBuilder.DropTable(
name: "Node");
}
}
}

View File

@ -0,0 +1,199 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using UserService.DatabaseLayer.DataModels;
namespace UserService.DatabaseLayer.Migrations
{
[DbContext(typeof(UserServiceDbContext))]
partial class UserServiceDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.6");
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Node", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CommonName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("ParentId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("Node");
b.HasDiscriminator<string>("Discriminator").HasValue("Node");
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b =>
{
b.Property<int>("MemberId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("MemberId", "UserId");
b.HasIndex("UserId");
b.ToTable("UserMembers");
b.HasData(
new
{
MemberId = -8,
UserId = -7
});
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Member", b =>
{
b.HasBaseType("UserService.DatabaseLayer.DataModels.Node");
b.Property<string>("EMail")
.HasColumnType("TEXT");
b.HasDiscriminator().HasValue("Member");
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.OrganizationUnit", b =>
{
b.HasBaseType("UserService.DatabaseLayer.DataModels.Node");
b.Property<int?>("ManagerId")
.HasColumnType("INTEGER");
b.HasIndex("ManagerId");
b.HasDiscriminator().HasValue("OrganizationUnit");
b.HasData(
new
{
Id = -2,
CommonName = "Users"
},
new
{
Id = -1,
CommonName = "Groups"
},
new
{
Id = -6,
CommonName = "Germany",
ParentId = -2
},
new
{
Id = -5,
CommonName = "USA",
ParentId = -2
},
new
{
Id = -4,
CommonName = "Arizona",
ParentId = -5
},
new
{
Id = -3,
CommonName = "France",
ParentId = -2
});
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.SecurityGroup", b =>
{
b.HasBaseType("UserService.DatabaseLayer.DataModels.Member");
b.HasDiscriminator().HasValue("SecurityGroup");
b.HasData(
new
{
Id = -8,
CommonName = "Global Admin",
ParentId = -1
});
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.User", b =>
{
b.HasBaseType("UserService.DatabaseLayer.DataModels.Member");
b.Property<string>("FirstName")
.HasColumnType("TEXT");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<string>("LastName")
.HasColumnType("TEXT");
b.HasDiscriminator().HasValue("User");
b.HasData(
new
{
Id = -7,
CommonName = "holger",
ParentId = -6,
IsActive = true
});
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Node", b =>
{
b.HasOne("UserService.DatabaseLayer.DataModels.Node", "Parent")
.WithMany("Children")
.HasForeignKey("ParentId");
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b =>
{
b.HasOne("UserService.DatabaseLayer.DataModels.Member", "Member")
.WithMany("Members")
.HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("UserService.DatabaseLayer.DataModels.User", "User")
.WithMany("MemberOf")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.OrganizationUnit", b =>
{
b.HasOne("UserService.DatabaseLayer.DataModels.Member", "Manager")
.WithMany()
.HasForeignKey("ManagerId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using UserService.DatabaseLayer.DataModels;
namespace UserService.DatabaseLayer.Repository
{
public class BaseRepository<T> where T : class
{
private readonly Func<UserServiceDbContext, DbSet<T>> _context;
protected BaseRepository(Func<UserServiceDbContext, DbSet<T>> context)
{
_context = context;
}
public async Task<IReadOnlyList<T>> GetAllAsync(CancellationToken token = default)
{
await using var db = new UserServiceDbContext();
return await _context(db).ToListAsync(token);
}
public async Task<T?> GetAsync(Expression<Func<T, bool>> predicate, CancellationToken token = default)
{
await using var db = new UserServiceDbContext();
return await _context(db).FirstOrDefaultAsync(predicate, token);
}
public async Task AddAsync(T entity, CancellationToken token = default)
{
await using var db = new UserServiceDbContext();
await _context(db).AddAsync(@entity, token);
await db.SaveChangesAsync(token);
}
public async Task UpdateAsync(T entity, CancellationToken token = default)
{
await using var db = new UserServiceDbContext();
_context(db).Update(entity);
await db.SaveChangesAsync(token);
}
public async Task DeleteAsync(T entity, CancellationToken token = default)
{
await using var db = new UserServiceDbContext();
_context(db).Remove(entity);
await db.SaveChangesAsync(token);
}
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using UserService.DatabaseLayer.DataModels;
namespace UserService.DatabaseLayer.Repository
{
public interface IRepository<T> where T : Node
{
Task<IReadOnlyList<T>> GetAllAsync(CancellationToken token = default);
Task<T?> GetAsync(Expression<Func<T, bool>> predicate, CancellationToken token = default);
Task AddAsync(T entity, CancellationToken token = default);
Task UpdateAsync(T entity, CancellationToken token = default);
Task DeleteAsync(T entity, CancellationToken token = default);
}
public interface IOrganizationUnitsRepository : IRepository<OrganizationUnit>
{
}
public interface ISecurityGroupsRepository : IRepository<SecurityGroup>
{
}
public interface IUsersRepository : IRepository<User>
{
}
public interface INodesRepository : IRepository<Node>
{
}
}

View File

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using UserService.DatabaseLayer.DataModels;
namespace UserService.DatabaseLayer.Repository
{
public class OrganizationUnitsRepository : BaseRepository<OrganizationUnit>, IOrganizationUnitsRepository
{
public OrganizationUnitsRepository() : base(x => x.OrganizationUnits)
{
}
}
public class SecurityGroupsRepository : BaseRepository<SecurityGroup>, ISecurityGroupsRepository
{
public SecurityGroupsRepository() : base(x=> x.SecurityGroups)
{
}
}
public class UsersRepository : BaseRepository<User>, IUsersRepository
{
public UsersRepository() : base(x => x.Users)
{
}
}
public class NodesRepository : INodesRepository
{
private readonly IUsersRepository _users;
private readonly ISecurityGroupsRepository _securityGroups;
private readonly IOrganizationUnitsRepository _organizationUnits;
public NodesRepository(IUsersRepository users, ISecurityGroupsRepository securityGroups, IOrganizationUnitsRepository organizationUnits)
{
_users = users;
_securityGroups = securityGroups;
_organizationUnits = organizationUnits;
}
public static async IAsyncEnumerable<Node> GetNodesAsync([EnumeratorCancellation] CancellationToken token = default)
{
await using var db = new UserServiceDbContext();
await foreach (var note in db.OrganizationUnits.AsAsyncEnumerable().WithCancellation(token))
{
yield return note;
}
await foreach (var node in db.SecurityGroups.AsAsyncEnumerable().WithCancellation(token))
{
yield return node;
}
await foreach (var node in db.Users.AsAsyncEnumerable().WithCancellation(token))
{
yield return node;
}
}
public async Task<IReadOnlyList<Node>> GetAllAsync(CancellationToken token = default)
{
var list = new List<Node>();
await foreach (var node in GetNodesAsync(token))
{
list.Add(node);
}
return list;
}
public Task<Node?> GetAsync(Expression<Func<Node, bool>> predicate, CancellationToken token = default)
{
throw new NotImplementedException();
}
public Task AddAsync(Node entity, CancellationToken token = default)
{
throw new NotImplementedException();
}
public Task UpdateAsync(Node entity, CancellationToken token = default)
{
throw new NotImplementedException();
}
public Task DeleteAsync(Node entity, CancellationToken token = default)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>8</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.6" />
</ItemGroup>
</Project>