Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

would mono T4 be interested in my fork?? netcore 3.x compatible #89

Open
kccarter76 opened this issue Aug 14, 2020 · 8 comments
Open

would mono T4 be interested in my fork?? netcore 3.x compatible #89

kccarter76 opened this issue Aug 14, 2020 · 8 comments

Comments

@kccarter76
Copy link

kccarter76 commented Aug 14, 2020

the fork is located here.

I have done a lot of work to ensure that ParsedTemplate, TemplateSettings and the Templating interfaces are serializable. This means they can pass through a named pipe connecting two different processes. This approach supports the following scenerios, framework to netcore, mono to framework, mono to netcore.

I have also done work on Compiled Template to ensure compatibility with the AssemblyLoadContext which replaces AppDomain for netcore implementations.

this fork will help in resolving #70 ground work for #47 as well. after all I built this for visual studio 2019 solution for t4 templating between .net framework and netcore.

on another note some of the code in this project depends on the hash key never changing. this does not work between processes, this was done by the runtimes to protect against a hash attack.

below is an example of T4 template that I have been able to generate

Settings.ttinclude

<#@ parameter type="System.String" name="$connectionKey$" #>
<#@ assembly name="SubSonic.Core.Abstractions" #>
<#@ assembly name="SubSonic.Core.DAL" #>
<#@ assembly name="SubSonic.Core.Extensions" #>
<#@ assembly name="System.Linq.Expressions" #>
<#@ assembly name="System.Linq.Queryable" #>
<#@ assembly name="System.Linq" #>

<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.Common" #>
<#@ import namespace="SubSonic" #>
<#@ import namespace="SubSonic.Core" #>
<#+
public class Settings
{
	public static ITextTemplatingEngineHost Host { get; set; }

	public static IEnumerable<string> ExcludeRelationships
	{
		get
		{
			return new string[] { };
		}
	}

	public static IEnumerable<string> ExcludeTables
	{
		get
		{ 
			return new string[]{
				"sysdiagrams",
				"BuildVersion",
				"__RefactorLog",
				"aspnet_Applications",
				"aspnet_Membership",
				"aspnet_Paths",
				"aspnet_PersonalizationAllUsers",
				"aspnet_PersonalizationPerUser",
				"aspnet_Profile",
				"aspnet_Roles",
				"aspnet_SchemaVersions",
				"aspnet_Users",
				"aspnet_UsersInRoles",
				"aspnet_WebEvent_Events"
				};
		}
	}

	public static string Connection
	{
		get
		{
			if (!string.IsNullOrEmpty($connectionKey$))
			{
				return $connectionKey$;
			}

			throw new InvalidOperationException("Connection string was not injected.");
		}
	}

	public static TService GetService<TService>()
	{
		if(((IServiceProvider)Host).GetService(typeof(TService)) is TService success)
		{
			return success;
		}
		return default;
	}
}
#>

SqlServer.ttinclude

<#@ include file="DataContextSettings.ttinclude" #>
<#@ assembly name="Microsoft.Extensions.DependencyInjection" #>
<#@ assembly name="Microsoft.Extensions.DependencyInjection.Abstractions" #>
<#@ assembly name="Microsoft.Extensions.Logging" #>
<#@ assembly name="Microsoft.Extensions.Logging.Abstractions" #>
<#@ assembly name="Microsoft.Extensions.Options" #>
<#@ assembly name="Microsoft.Extensions.Primitives" #>
<#@ assembly name="SubSonic.Extensions.SqlServer" #>

<#@ import namespace="Microsoft.Extensions.Logging" #>
<#@ import namespace="SubSonic.Extensions.SqlServer" #>
<#@ import namespace="SubSonic.CodeGenerator" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="Models = SubSonic.CodeGenerator.Models" #>
<#+
public class SqlGeneratorContext
    : GeneratorContext
{
    public SqlGeneratorContext(string connection)
        : base(connection, LogLevel.Debug) { }

    public SqlGeneratorContext(string connection, LogLevel logLevel)
        : base(connection, logLevel) { }

    protected override void OnDbConfiguring(DbContextOptionsBuilder builder)
    {
        base.OnDbConfiguring(builder);

        builder
            .UseSqlClient((config, options) =>
            {
                config.ConnectionString = ConnectionString;
            });
    }
}

protected SqlGeneratorContext Context { get; private set; } 
#>

DataContext.stt

<#@ template hostspecific="true" language="C#" #>
<#@ include file="SqlServer.ttinclude" #>
<#
	Settings.Host = Host;
	using(Context = new SqlGeneratorContext(Settings.Connection))
    {
#>
using Microsoft.Extensions.DependencyInjection;
using SubSonic;
using System;

namespace TemplateIntegrationTest.DAL
{
	public partial class DataContext
		: SubSonicContext
	{
		private readonly IServiceCollection services = null;

		public DataContext(IServiceCollection services)
		{
			this.services = services ?? throw new ArgumentNullException(nameof(services));
		}

<#foreach(Models.Table table in Context.Tables) {
    if (Settings.ExcludeTables.Any(x => x.Equals(table.Name, StringComparison.OrdinalIgnoreCase)))
    {
        continue;
    }
#>
		public ISubSonicSetCollection<Models.<#=table.Name#>> <#=table.Name.Pluralize()#> { get; protected set; }
<#}#>
		protected override void OnDbModeling(DbModelBuilder builder)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }
<#foreach(Models.Table table in Context.Tables) {
    if (Settings.ExcludeTables.Any(x => x.Equals(table.Name, StringComparison.OrdinalIgnoreCase)))
    {
        continue;
    }
#>
            builder.AddEntityModel<Models.<#=table.Name#>>();
<#}#>
        }
	}
}
<#}#>

DataModels.stt

<#@ template hostspecific="true" language="C#" #>
<#@ include file="SqlServer.ttinclude" #>
<#@ import namespace="System.Linq" #>
<#
	Settings.Host = Host;
	using (Context = new SqlGeneratorContext(Settings.Connection))
    {
#>using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using SubSonic;

namespace TemplateIntegrationTest.DAL.Models
{
<#foreach(Models.Table table in Context.Tables) {
    if (Settings.ExcludeTables.Any(x => x.Equals(table.Name, StringComparison.OrdinalIgnoreCase)))
    {
        continue;
    }
#>
    [Table("<#=table.Name#>", Schema = "<#=table.Schema#>")]
<#if (Context.TableTypes.Any(x => x.SchemaOwner == table.Schema && x.Name == table.Name)) {
    Models.TableType tableType = Context.TableTypes.Single(x => x.SchemaOwner == table.Schema && x.Name == table.Name);
#>
    [DbUserDefinedTableType("<#=tableType.Name#>", SchemaName = "<#=tableType.SchemaOwner#>")]    
<#}#>
    public partial class <#=table.Name#>
    {
        public <#=table.Name#>() { }

<#foreach (Models.Column col in table.Columns.OrderBy(x => x.OrdinalPosition)) {#>
<#if (col.IsPrimaryKey) {#>
        [Key]
<#}#>
<#if (col.IsComputed) {#>
        [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
<#} else if (col.IsIdentity) {#>
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
<#}#>
        public <#=((col.IsIdentity || col.IsComputed) ? "" : "virtual ")#><#=col.ToSimpleType()#> <#=col.ColumnName#> { get; set; }
<#}#>
<#foreach (Models.Relationship relation in table.WithOneRelationships) {
    if (Settings.ExcludeRelationships.Any(x => x.Equals(table.Name, StringComparison.OrdinalIgnoreCase)))
    {
        continue;
    }
#>
        [ForeignKey(nameof(<#=relation.ColumnName#>))]
        public virtual <#=relation.ForiegnTableName#> <#=relation.ForiegnTableName#> { get; set; }

<#}#>
<#foreach (Models.Relationship relation in table.WithManyRelationships) {
    if (Settings.ExcludeRelationships.Any(x => x.Equals(table.Name, StringComparison.OrdinalIgnoreCase)))
    {
        continue;
    }
#>
        public virtual ISubSonicCollection<<#=relation.TableName#>> <#=relation.TableName.Pluralize()#> { get; set; }

<#}#>
    }

<#}#>
}
<#}#>
@davidnduffy
Copy link

I think I need this support also.

@user72356
Copy link

I sure am interested. I hope they seriously look into this.

@jogibear9988
Copy link

@kccarter76 could you release your fork as nuget? seems no one here is merging your changes...

@jogibear9988
Copy link

@kccarter76 and maybe also enable issues on your fork then

@mhutch
Copy link
Member

mhutch commented Nov 30, 2020

My apologies for the delay in responding. I'm definitely interested in merging any improvements, though I would want them to be split into separate PRs for each atomic change so that I can review them more easily.

@kccarter76
Copy link
Author

that would be very hard to do, because the fork is greater than 100 builds past the current pull request that I have pending.

there are significant changes made to ensure that the transformation host/session objects, could be serialized across a named pipe. this was necessary for a net core framework app built in visual studio which was a .net framework application.

actually net 5 does not change a thing in regards to debugging a t4 template. you still need the separate process in order to attach a debugger. what net 5 on both sides simplifies the serialization. It is very likely that Microsoft will roll there own with the next iteration of visual studio.

@mhutch
Copy link
Member

mhutch commented Dec 1, 2020

Fair enough. The offer to review/merge PRs individual features/fixes from your fork stands. I might cherry-pick some stuff myself, do you mind if I do? I'll make sure it's properly attributed.

@kccarter76
Copy link
Author

that is fine

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants