Skip to content

Commit

Permalink
[ODS-5870] Move Admin.DataAccess library to EF Core (#842)
Browse files Browse the repository at this point in the history
* Move Admin.DataAccess library to EF Core

* Cleanup and some small fixes

* Package version updated

* Package version updated

* Package versions updated

* Remove extra project reference

* Align package versions

* Cleanup

* Update EdFi.Suite3.Admin.DataAccess package version

* Small fixes

* Update EdFi.Suite3.Admin.DataAccess package version
  • Loading branch information
mjaksn authored Oct 9, 2023
1 parent b4540f7 commit 8f748c8
Show file tree
Hide file tree
Showing 53 changed files with 820 additions and 1,187 deletions.
121 changes: 17 additions & 104 deletions Application/EdFi.Admin.DataAccess/Contexts/IUsersContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,132 +4,45 @@
// See the LICENSE and NOTICES files in the project root for more information.

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Threading;
using System.Threading.Tasks;
using EdFi.Admin.DataAccess.Models;
using Microsoft.EntityFrameworkCore;

namespace EdFi.Admin.DataAccess.Contexts
{
public interface IUsersContext : IDisposable
{
IDbSet<User> Users { get; set; }
DbSet<User> Users { get; set; }

IDbSet<ApiClient> Clients { get; set; }
DbSet<ApiClient> ApiClients { get; set; }

IDbSet<ClientAccessToken> ClientAccessTokens { get; set; }
DbSet<ClientAccessToken> ClientAccessTokens { get; set; }

IDbSet<Vendor> Vendors { get; set; }
DbSet<Vendor> Vendors { get; set; }

IDbSet<Application> Applications { get; set; }
DbSet<Application> Applications { get; set; }

IDbSet<Profile> Profiles { get; set; }
DbSet<Profile> Profiles { get; set; }

IDbSet<OdsInstance> OdsInstances { get; set; }
DbSet<OdsInstance> OdsInstances { get; set; }

IDbSet<OdsInstanceContext> OdsInstanceContexts { get; set; }
DbSet<OdsInstanceContext> OdsInstanceContexts { get; set; }

IDbSet<OdsInstanceDerivative> OdsInstanceDerivatives { get; set; }
DbSet<OdsInstanceDerivative> OdsInstanceDerivatives { get; set; }

IDbSet<ApplicationEducationOrganization> ApplicationEducationOrganizations { get; set; }
DbSet<ApplicationEducationOrganization> ApplicationEducationOrganizations { get; set; }

IDbSet<VendorNamespacePrefix> VendorNamespacePrefixes { get; set; }
DbSet<VendorNamespacePrefix> VendorNamespacePrefixes { get; set; }

IDbSet<OwnershipToken> OwnershipTokens { get; set; }
DbSet<OwnershipToken> OwnershipTokens { get; set; }

DbSet<ApiClientOdsInstance> ApiClientOdsInstances { get; set; }

DbSet<ApiClientOwnershipToken> ApiClientOwnershipTokens { get; set; }

DbSet<ApiClientOdsInstance> ApiClientOdsInstances { get; set; }

int SaveChanges();

Task<int> SaveChangesAsync();

/// <summary>
/// Asynchronously executes a raw SQL statement with only a scalar result (e.g. row count).
/// </summary>
/// <param name="sqlStatement">Query to execute, optionally containing parameter strings using @ symbol token</param>
/// <param name="parameters">Optional parameters</param>
/// <returns>Statement result (row count by default)</returns>
/// <example>
/// <para>
/// No parameters:
/// </para>
/// <code>
/// <![CDATA[
/// string sqlCommand = "UPDATE dbo.Something SET FieldOne = null";
/// IReadOnlyList<Something> result = await _usersContext.ExecuteQueryAsync<Something>(sqlCommand);
/// ]]>
/// </code>
/// <para>
/// Single parameter, @p0. The name does not matter; @p0 is a good standard to follow to indicate to the reader that it is the first parameter.
/// </para>
/// <code>
/// <![CDATA[
/// int fieldTwoLowerLimit = 30;
/// string sqlCommand = "UPDATE dbo.Something SET FieldOne = null WHERE FieldTwo > @p0";
/// IReadOnlyList<Something> result = await _usersContext.ExecuteQueryAsync<Something>(sqlCommand, fieldTwoLowerLimit);
/// ]]>
/// </code>
/// <para>
/// Two parameters, second one for a CreateDate field that we're not going to return.
/// </para>
/// <code>
/// <![CDATA[
/// int fieldTwoLowerLimit = 30;
/// DateTime createDateUpperLimit = DateTime.Parse(2020,1,1);
/// string sqlCommand = "UPDATE dbo.Something SET FieldOne = null WHERE FieldTwo > @p0 AND CreateDate < '@p1'";
/// IReadOnlyList<Something> result = await _usersContext.ExecuteQueryAsync<Something>(sqlCommand, fieldTwoLowerLimit, createDateUpperLimit);
/// ]]>
/// </code>
/// </example>
Task<int> ExecuteSqlCommandAsync(string sqlStatement, params object[] parameters);

/// <summary>
/// Asynchronously executes a raw SQL query and maps the results to an object of type <typeparamref name="TReturn"/>.
/// </summary>
/// <typeparam name="TReturn">Any class with properties matching the column names in the query</typeparam>
/// <param name="sqlStatement">Query to execute, optionally containing parameter strings using @ symbol token</param>
/// <param name="parameters">Optional parameters</param>
/// <returns>Readonly list of <typeparamref name="TReturn"/></returns>
/// <example>
/// <para>
/// Given this return entity:
/// </para>
/// <code>
/// public class Something { public string FieldOne { get; set; } public int FieldTwo { get; set; } }
/// </code>
/// <para>
/// No parameters:
/// </para>
/// <code>
/// <![CDATA[
/// string sqlCommand = "SELECT FieldOne, FieldTwo FROM dbo.Something";
/// IReadOnlyList<Something> result = await _usersContext.ExecuteQueryAsync<Something>(sqlCommand);
/// ]]>
/// </code>
/// <para>
/// Single parameter, @p0. The name does not matter; @p0 is a good standard to follow to indicate to the reader that it is the first parameter.
/// </para>
/// <code>
/// <![CDATA[
/// int fieldTwoLowerLimit = 30;
/// string sqlCommand = "SELECT FieldOne, FieldTwo FROM dbo.Something WHERE FieldTwo > @p0";
/// IReadOnlyList<Something> result = await _usersContext.ExecuteQueryAsync<Something>(sqlCommand, fieldTwoLowerLimit);
/// ]]>
/// </code>
/// <para>
/// Two parameters, second one for a CreateDate field that we're not going to return.
/// </para>
/// <code>
/// <![CDATA[
/// int fieldTwoLowerLimit = 30;
/// DateTime createDateUpperLimit = DateTime.Parse(2020,1,1);
/// string sqlCommand = "SELECT FieldOne, FieldTwo FROM dbo.Something WHERE FieldTwo > @p0 AND CreateDate < '@p1'";
/// IReadOnlyList<Something> result = await _usersContext.ExecuteQueryAsync<Something>(sqlCommand, fieldTwoLowerLimit, createDateUpperLimit);
/// ]]>
/// </code>
/// </example>
Task<IReadOnlyList<TReturn>> ExecuteQueryAsync<TReturn>(string sqlStatement, params object[] parameters);
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}
}
67 changes: 18 additions & 49 deletions Application/EdFi.Admin.DataAccess/Contexts/PostgresUsersContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,68 +3,37 @@
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
using EdFi.Admin.DataAccess.Extensions;
using EdFi.Admin.DataAccess.Models;
using EdFi.Common;
using EdFi.Common.Utils.Extensions;
using Microsoft.EntityFrameworkCore;

namespace EdFi.Admin.DataAccess.Contexts
{
public class PostgresUsersContext : UsersContext
{
public PostgresUsersContext(string connectionString) : base(connectionString) { }
public PostgresUsersContext(DbContextOptions options)
: base(options) { }

protected override void ApplyProviderSpecificMappings(DbModelBuilder modelBuilder)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// The column name in this linking table had to be shortened for Postgres
modelBuilder.Entity<ApiClient>()
.HasMany(t => t.ApplicationEducationOrganizations)
.WithMany(t => t.Clients)
.Map(
m =>
{
m.ToTable("ApiClientApplicationEducationOrganizations", "dbo");
m.MapLeftKey("ApiClient_ApiClientId");
m.MapRightKey("ApplicationEdOrg_ApplicationEdOrgId");
});
base.OnModelCreating(modelBuilder);

modelBuilder.Conventions.Add<ForeignKeyLowerCaseNamingConvention>();
modelBuilder.Conventions.Add<TableLowerCaseNamingConvention>();
modelBuilder.Model.GetEntityTypes().ForEach(
entityType =>
entityType.SetSchema("dbo"));

modelBuilder.Properties().Configure(c => c.HasColumnName(c.ClrPropertyInfo.Name.ToLowerInvariant()));
}

private class TableLowerCaseNamingConvention : IStoreModelConvention<EntitySet>
{
public void Apply(EntitySet entitySet, DbModel model)
{
Preconditions.ThrowIfNull(entitySet, nameof(entitySet));
Preconditions.ThrowIfNull(model, nameof(model));

entitySet.Table = entitySet.Table.ToLowerInvariant();
}
}

private class ForeignKeyLowerCaseNamingConvention : IStoreModelConvention<AssociationType>
{
public void Apply(AssociationType association, DbModel model)
{
Preconditions.ThrowIfNull(association, nameof(association));
Preconditions.ThrowIfNull(model, nameof(model));

if (!association.IsForeignKey)
{
return;
}
modelBuilder.Model.GetEntityTypes().Single(e => e.ClrType.Name == nameof(ApiClientApplicationEducationOrganization))
.GetProperty("ApplicationEducationOrganizationId")
.SetColumnName("applicationedorg_applicationedorgid");

association.Constraint.FromProperties.ForEach(PropertyNamesToLowerInvariant);
association.Constraint.ToProperties.ForEach(PropertyNamesToLowerInvariant);
modelBuilder
.Entity<Profile>()
.Property(e => e.ProfileDefinition)
.HasColumnType("xml");

void PropertyNamesToLowerInvariant(EdmProperty property) => property.Name = property.Name.ToLowerInvariant();
}
modelBuilder.MakeDbObjectNamesLowercase();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,13 @@
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using System.Data.Entity;
using EdFi.Admin.DataAccess.Models;
using Microsoft.EntityFrameworkCore;

namespace EdFi.Admin.DataAccess.Contexts
{
public class SqlServerUsersContext : UsersContext
{
public SqlServerUsersContext(string connectionString) : base(connectionString) { }

protected override void ApplyProviderSpecificMappings(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ApiClient>()
.HasMany(t => t.ApplicationEducationOrganizations)
.WithMany(t => t.Clients)
.Map(
m =>
{
m.ToTable("ApiClientApplicationEducationOrganizations", "dbo");
});
}
public SqlServerUsersContext(DbContextOptions options)
: base(options) { }
}
}
Loading

0 comments on commit 8f748c8

Please sign in to comment.