Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

Disclaimer: I am a VB/F# first developer that only recently started writing C# in anger:

Surprise T Rex posted:

Are there any good resources on how people tend to use things like Records/Record Structs, Pattern Matching, and other newer language features in the real world?

Everything I’ve seen so far is just toy examples (“Person(string name)” level stuff), but it’d be cool to see how people are using them to make things nicer.

Semi-related, what are peoples thoughts on stuff like using small private types internally in a class to essentially replace Tuples or anonymous objects? Useful or just noise?

I always use records instead of POCOs unless some specific library I am using doesn't like them.

Unless I want reference equality for some reason, I will also use records for what I call "DI funnels", i.e. classes that take in a bunch of arguments in the constructors, stick them as-is in read-only fields, and then only expose one or more pure methods that do not mutate the fields in any way. Works the same as a standard class, takes fewer lines.

I never use tuples to tie data together, I use tuples to return multiple values, deprecating the hack that is out parameters. Eg. instead of "SomeFlag SomeFunction(arg, out ActualContent? out)" I will just make SomeFunction return a tuple of (SomeFlag, ActualContent?). (Note: this is what F# does automatically, if you call int32.Parse from F# it gets automatically sugared to return a (bool, Option<int>) tuple).

For local datatype definitions, anonymous records are fine if and only if they're local to a single function scope. If the scope is more than one function or longer than a dozen lines, it gets promoted to a private nested record type.

Pattern matching is kind of a "depends" - a lot o f times it makes the dispatching look really nice, sometimes it just hides some assumptions. For example, just yesterday a coworker showed me this code:

C# code:
var thing = fetchSome(usershit);

if (thing is { SomeProperty = null }) {
   log("your poo poo is hosed");
   return;
}

DoSomething(thing.SomeProperty);

The guard clause desugars to "thing?.SomeProperty == null", so it will catch a null 'thing', which is the right choice. But having a specific property being null is a different error than the entire object being null (the former means the client hosed up, the latter could be the fetchSome function that hosed up).

Adbot
ADBOT LOVES YOU

Red Mike
Jul 11, 2011
I think that's generally the one place pattern matching really breaks down, if you're using nullable types. If you have "thing" not be nullable, and have "fetchSome" throw (or return a Result<T> or whatever) instead of just returning null, then pattern matching will generally do what you expect it to.

Kyte posted:

code:
			var tasks = (
				apiMaestros.ObtenerCuentaAsync(participante.beneficiario_rut),
				apiMaestros.ObtenerEtniasAsync(),
				apiMaestros.ObtenerDiscapacidadesAsync(),
				apiMaestros.ObtenerRegionAsync(contacto.id_region),
				apiMaestros.ObtenerProvinciaAsync(contacto.id_provincia),
				apiMaestros.ObtenerComunaAsync(contacto.id_comuna)
				);

			var (cuenta, etnias, discapacidades, region, provincia, comuna) = await tasks;

Personally I don't mind tuples even in production code as long as they're named, but I really dislike code that is built to use them as basically half-baked collections; that's not what tuples are. Same with anonymous types, although those are at least much more difficult to try to use like this (but not impossible as proven by code I've run into before).

You're basically building new boilerplate logic on top of code that has particular behaviours, in order to take advantage of one minor part of its behaviour (tuple deconstruction). Worse, you usually end up not even able to take full advantage of it, because for example in this code above: it's trivial for someone to change the order of two of the lines in the task definition, then not change the order of the results in the awaiting line. If the two changed lines return the same type (like say a string), you won't even notice.

What I tend to use tuples for is basically as a replacement for a struct/record while writing small bits of code that doesn't tend to move across layers of the application. I don't generally like returning tuples from public methods (and never from a library), but even returning from a public method is acceptable if the number of values is well defined and it's a one-off/rarely used request (e.g. you have a service that retrieves a bit of data, or the children of a bit of data, or both; if "both" is something that's only used in a minor place like a debug method/analytics tracker/etc, then I have no issue using a tuple instead of an actual type).

e: I also use tuples to avoid out parameters...because of all the places where out parameters don't work anyway. But again if it's a public method/library method, then I'll use a type unless it's a one-off/minor request that I don't care enough about. Usually even then I'll only use a tuple if it's a compound version of existing types.

No Pants
Dec 10, 2000

NihilCredo posted:

C# code:
var thing = fetchSome(usershit);

if (thing is { SomeProperty = null }) {
   log("your poo poo is hosed");
   return;
}

DoSomething(thing.SomeProperty);

The guard clause desugars to "thing?.SomeProperty == null", so it will catch a null 'thing', which is the right choice. But having a specific property being null is a different error than the entire object being null (the former means the client hosed up, the latter could be the fetchSome function that hosed up).

I might be missing something, but that code block will throw a NullReferenceException if thing is null. A property pattern only matches an expression if the expression result is not null.

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

No Pants posted:

I might be missing something, but that code block will throw a NullReferenceException if thing is null. A property pattern only matches an expression if the expression result is not null.

Possibly, I'm on vacation and just going by what my coworker posted on Slack (where he said the guard clause triggered).

Red Mike
Jul 11, 2011
Property patterns won't throw if it's null, it'll just early quit and act as if it's not a match.

quote:

A property pattern checks that the input value is not null and recursively matches values extracted by the use of accessible properties or fields.
From https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/patterns#property-pattern.

No Pants
Dec 10, 2000

Red Mike posted:

Property patterns won't throw if it's null, it'll just early quit and act as if it's not a match.

From https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/patterns#property-pattern.

Yeah, that's what I posted. It'll throw an exception when it gets to DoSomething(thing.SomeProperty);

Red Mike
Jul 11, 2011
Oh sorry, I misread and thought you meant the property match would throw. Yeah, this is the general pattern where I'd expect thing to be a non-nullable, and then the entire thing does make sense.

epswing
Nov 4, 2003

Soiled Meat

Potassium Problems posted:

I want to say that returning a type that inherits from ObjectResult will ensure you don't have to materialize the list & will stream it to the client, but I don't know if that's true or not and I can't seem to find any documentation on it

Ok, so is there any practical difference between these two? Returning IActionResult is less explicit, because return Ok(); and return Ok(true); and return Ok(new List<Apple>() { new Apple() }); will all compile fine, but at runtime my endpoint could return basically anything. I guess that's desirable sometimes, though?

C# code:
public IActionResult GetApples()
{
    return Ok(service.GetApples());
}
C# code:
public ActionResult<IEnumerable<Apple>> GetApples()
{
    return Ok(service.GetApples());
}

epswing fucked around with this message at 15:26 on Jun 15, 2023

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

I think IActionResult simply predates ActionResult<T>. Before ASP.NET Core... 2 or 3 or something, returning IActionResult was the only option if you had a code path returning Ok(apples) and another returning NotFound("missing tree").

That said, all it really does is saving you from maintaining a [Produces(typeof(IEnumerable<Apple>)] attribute, for the benefit of OpenAPI generators and the like. Anything that's wrapped inside an ObjectResult still bypasses typechecking:

C# code:
public ActionResult<Apple> GetApple()
{
    return new Apple(); // ok
    return new Orange(); // compiler error

    return Ok(new Apple()); // ok
    return Ok(new Oranges()); // ok
} 

epswing
Nov 4, 2003

Soiled Meat

NihilCredo posted:

I think IActionResult simply predates ActionResult<T>. Before ASP.NET Core... 2 or 3 or something, returning IActionResult was the only option if you had a code path returning Ok(apples) and another returning NotFound("missing tree").

That said, all it really does is saving you from maintaining a [Produces(typeof(IEnumerable<Apple>)] attribute, for the benefit of OpenAPI generators and the like. Anything that's wrapped inside an ObjectResult still bypasses typechecking:

C# code:
public ActionResult<Apple> GetApple()
{
    return new Apple(); // ok
    return new Orange(); // compiler error

    return Ok(new Apple()); // ok
    return Ok(new Oranges()); // ok
} 

Ok, makes sense. Is it considered bad form for different code paths to return different types? This honestly makes my skin crawl, for some reason.

C# code:
public ActionResult<Apple> GetApple()
{
    try
    {
        var apple = service.GetApple();
        
        if (apple == null)
            return NotFound();
        
        return apple;
    }
    catch Exception(e)
    {
        return BadRequest(e);
    }
}

ChocolatePancake
Feb 25, 2007
Huh, I've always just returned an IActionResult and never really thought about it before.

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

epswing posted:

Ok, makes sense. Is it considered bad form for different code paths to return different types?

It's definitely both OK and idiomatic for HTTP endpoint functions specifically. It is the whole point of HTTP status codes, after all. Some error codes will be handled implicitly (eg. 401/403 for missing authn/authz, 400 for mangled JSON), but not always, and some others will typically have to be generated by your own code (404 most commonly, or 400 for formally-correct-but-still-invalid input).

For regular C# functions, returning different types is suboptimal. Sometimes it makes sense to return different implementations of the base class / interface indicated in your signature, if the consumer is able to meaningfully work with the base class / interface alone. A classic example is returning IEnumerable, but if you know that the number of items in your source is small and there are no side effects, you just return a List instead of a lazy enumerator, because it's going to be more efficient.

However, if you're returning a base class / interface and then expect the consumer to have to run a type test to figure out what to do with it, that's a bad pattern and you should look for a different approach.

The optimal solution would be union types, but C# doesn't really have them, unless you use the LanguageExt library which is admirable but still a huge hack.

Kyte
Nov 19, 2013

Never quacked for this

Red Mike posted:

Personally I don't mind tuples even in production code as long as they're named, but I really dislike code that is built to use them as basically half-baked collections; that's not what tuples are. Same with anonymous types, although those are at least much more difficult to try to use like this (but not impossible as proven by code I've run into before).

You're basically building new boilerplate logic on top of code that has particular behaviours, in order to take advantage of one minor part of its behaviour (tuple deconstruction). Worse, you usually end up not even able to take full advantage of it, because for example in this code above: it's trivial for someone to change the order of two of the lines in the task definition, then not change the order of the results in the awaiting line. If the two changed lines return the same type (like say a string), you won't even notice.

What I tend to use tuples for is basically as a replacement for a struct/record while writing small bits of code that doesn't tend to move across layers of the application. I don't generally like returning tuples from public methods (and never from a library), but even returning from a public method is acceptable if the number of values is well defined and it's a one-off/rarely used request (e.g. you have a service that retrieves a bit of data, or the children of a bit of data, or both; if "both" is something that's only used in a minor place like a debug method/analytics tracker/etc, then I have no issue using a tuple instead of an actual type).

e: I also use tuples to avoid out parameters...because of all the places where out parameters don't work anyway. But again if it's a public method/library method, then I'll use a type unless it's a one-off/minor request that I don't care enough about. Usually even then I'll only use a tuple if it's a compound version of existing types.

I just didn't want to have a big block of saving task to variables followed with another big block of awaiting all the tasks. It's just a waste of screen space in a function that was busy doing business things. Honestly I don't care for tuples at all outside of deconstruction and I use the gently caress out of that. No collection fulfills that need. Especially not when they're all different types.
Granted, a tuple of, say, (string, string) can be sus but those would be labeled in first place since the type isn't telling me anything.

LongSack
Jan 17, 2003

Question about generic classes and constructors.

I have this (abbreviated) class:
C# code:
public sealed class ServiceResult<TModel, TId> where TId: IEquatable<TId>
{
    public TId NewId { get; }
    public TModel? Result { get; init; }
    public IEnumerable<TModel>? Results { get; init; }
    public string? Message { get; }
    public string[]? Messages { get; }
    
    public ServiceResult() 
    {
        NewId = default!;
        Result = default!;
        Results = null;
        Message = null;
        Messages = null;
    }

    public ServiceResult(string message) : this() => Message = message;

    public ServiceResult(TId id) : this() => NewId = id;

    ...

}
If TId is string, then there are two constructors with the same signature. I expected a compilation error, but didn't get one. Expected a run time error, but nope. When I look at the list of constructors through reflection, there are indeed two constructors with identical signatures. The run time seems to always select the first, the second appears hidden.

I can work around it with factory methods, but this is definitely unexpected behavior (to me, anyway). Thoughts?

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

You don't need factory methods, you can just use named arguments to bypass the default overload resolution rules:

C# code:
var x = new ServiceResult<int, string>(id : "foo"); // will invoke the generic constructor

biznatchio
Mar 31, 2001


Buglord
And, incidentally, don't design your ServiceResult class to have both a singular Result property and an separate multi-value Results property. The methods you're calling that will be returning this object will know if they're going to be returning a single value or multiple values. Let them handle it by returning ModelResult<IList<ModelType>, long> instead of making a lot of annoying confusion for developers who will work on your codebase in the years to come as to whether they should be looking at Result or Results in every single result object they ever interact with.

Also, since I've seen this anti-pattern often enough to have an idea of what it might be used for: don't use ServiceResult objects internally between C# layers of your application. They're fine to shape Web API contract output, but a lot of developers insist on bringing over development practices from worse languages or have a massively misplaced fear of using exceptions for error reporting and end up making a huge mess of their internal codebases by building these sorts of bespoke result types rather than just returning the actual value you actually wanted from methods and using exceptions for failures.

epswing
Nov 4, 2003

Soiled Meat

I can’t find it but I’ll edit in the emote for the guy pointing and nodding later.

necrotic
Aug 2, 2005
I owe my brother big time for this!

epswing posted:

I can’t find it but I’ll edit in the emote for the guy pointing and nodding later.

:hmmyes:

LongSack
Jan 17, 2003

NihilCredo posted:

You don't need factory methods, you can just use named arguments to bypass the default overload resolution rules:

C# code:
var x = new ServiceResult<int, string>(id : "foo"); // will invoke the generic constructor

Thank you, I should have thought of that.

biznatchio posted:

And, incidentally, don't design your ServiceResult class to have both a singular Result property and an separate multi-value Results property. The methods you're calling that will be returning this object will know if they're going to be returning a single value or multiple values. Let them handle it by returning ModelResult<IList<ModelType>, long> instead of making a lot of annoying confusion for developers who will work on your codebase in the years to come as to whether they should be looking at Result or Results in every single result object they ever interact with.

OK, that makes sense.

quote:

Also, since I've seen this anti-pattern often enough to have an idea of what it might be used for: don't use ServiceResult objects internally between C# layers of your application. They're fine to shape Web API contract output, but a lot of developers insist on bringing over development practices from worse languages or have a massively misplaced fear of using exceptions for error reporting and end up making a huge mess of their internal codebases by building these sorts of bespoke result types rather than just returning the actual value you actually wanted from methods and using exceptions for failures.

I see what you’re saying. I’m consolidating results at the services layer when it would more properly be done at the API layer. Thanks.

nielsm
Jun 1, 2009



I have a .NET 5.0 (yes it needs to be upgraded) webservice running via Kestrel.
I have installed a new HTTPS certificate for the host in the Windows certificate store, and deleted the old certificate (with the same common name), but Kestrel keep serving with the old certificate, which has now expired.

Where does Kestrel find an old certificate that has been deleted from the Windows certificate store, and how do I make it pick up the new cert?

This is in production, dev certificates should not be involved in any way.


Never mind all this, it turns out the answer was that the certificate had to be installed in the per-user certificate store for the service account running the service, and I was looking in the machine certificate store, where someone else had also placed the certificate.

nielsm fucked around with this message at 14:27 on Jul 10, 2023

Admiral Snackbar
Mar 13, 2006

OUR SNEEZE SHIELDS CANNOT REPEL A HUNGER OF THAT MAGNITUDE
I have a question about including an expression in an EF select operation. If I run this code against SQL Server:
code:
var tc = ctx.TestClasses
            .Select(x => new
            {
                FullName = x.GroupName != null && x.DisplayName != null
                           ? $"{x.GroupName.Substring(0, 3).ToUpper()}: {x.DisplayName}"
                           : string.Empty
            })
            .ToList();
much of the work of the lambda is translated into SQL and run server-side:
code:
SELECT CASE
    WHEN ([t].[GroupName] IS NOT NULL) AND ([t].[DisplayName] IS NOT NULL) THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END, UPPER(SUBSTRING([t].[GroupName], 0 + 1, 3)), [t].[DisplayName]
FROM [TestClasses] AS [t]
Now that's great for this one case, but if I want to reuse the contents of the lambda elsewhere, I don't see a way to factor it out of this query but keep it running server-side. I think I should be able to do it with an expression, but doing this:
code:
Expression<Func<TestClass, string>> fullName = x => x.GroupName != null && x.DisplayName != null
                                                  ? $"{x.GroupName.Substring(0, 3).ToUpper()}: {x.DisplayName.Substring(0, 3)}"
                                                  : string.Empty;

var tc = ctx.TestClasses
            .Select(x => new
            {
                FullName = fullName.Compile()(x)
            })
            .ToList();
causes the lambda to run client-side instead:
code:
SELECT [t].[Id], [t].[DisplayName], [t].[GroupName]
FROM [TestClasses] AS [t]
Is there a way to incorporate my expression into the EF expression tree to push the work back to the server?

Red Mike
Jul 11, 2011
Initially a caveat that it sounds like a pretty bad idea to do this at all, and even after finding something that works it'll be easy for normal code changes/refactors to accidentally break it in a way that means you're no longer running it server-side.

If that isn't an issue, then I haven't tested this but I expect this would work:

code:
public class MyTestClass {
  // your model properties here

  //the below could be a record/class/T
  public object ConvertedObject => new
            {
                FullName = x.GroupName != null && x.DisplayName != null
                           ? $"{x.GroupName.Substring(0, 3).ToUpper()}: {x.DisplayName}"
                           : string.Empty
            };
}
code:
var tc = ctx.TestClasses
            .Select(x => x.ConvertedObject)
            .ToList();
If that doesn't work, or if you want something a bit safer and resistant to change, I expect the "right" way to do this is to push a stored procedure/function to the server, and have the query specifically call it, that way it'll always do the same thing. I don't know how EF deals with this because historically the answer has been "very badly".

Mr.Hotkeys
Dec 27, 2008

you're just thinking too much
You're on the right track but are veering off course when you're compiling the expression. The IQueryable LINQ methods take expressions so that they can be broken down and reinterpreted as SQL. When trying to do what you're doing, pull everything out into the expression for reuse:
code:
Expression<Func<TestClass, object>> projectExpr = x => new
{
	FullName = x.GroupName != null && x.DisplayName != null
	   ? $"{x.GroupName.Substring(0, 3).ToUpper()}: {x.DisplayName}"
	   : string.Empty
}

var tc = ctx.TestClasses
            .Select(projectExpr)
            .ToList();
And then don't compile the expression, just pass it into the method as-is.

As an aside, you'll notice too that it can take an expression now, but, once it goes from IQueryable to IEnumerable (.ToList, .ToArray, .AsEnumerable, etc), it can't anymore. Paying attention to when you lose the IQueryable when building LINQ queries is a really good hint as to what work will be done where (though as you've seen it all kind of gets thrown out when EF decides to just be helpful and make it work at a huge performance penalty, but at least it gives you an idea of what definitely won't be done in the DB).

If you're not doing the exact same projection each time and truly want to just reuse just the part that creates FullName, you can do that too, but it's definitely more involved. As far as expression trees go, it's not a terribly complicated task, but there's a lot to learn to get there, kind of a whole new way of thinking to adopt, and it opens you up to some of the weirdness and nuances you get moving from the type safety of code you're writing by hand to late binding, plus some pretty rough, unhelpful error messages. If that's the route you want to go, let me know and I could scratch something up to give you an idea.

Kyte
Nov 19, 2013

Never quacked for this

Admiral Snackbar posted:

I have a question about including an expression in an EF select operation. If I run this code against SQL Server:
code:
var tc = ctx.TestClasses
            .Select(x => new
            {
                FullName = x.GroupName != null && x.DisplayName != null
                           ? $"{x.GroupName.Substring(0, 3).ToUpper()}: {x.DisplayName}"
                           : string.Empty
            })
            .ToList();
I'd split it out into a complete Select call.
Like,
code:
IQueryable<TestClassWithFullName> SelectWithFullName(this IQueryable<TestClass> q) {
	return q.Select(x => new TestClassWithFullName { Elem = x, FullName = (etc) });
}
and chain this into further selects to clean up the output.

Kyte fucked around with this message at 19:05 on Jul 11, 2023

Admiral Snackbar
Mar 13, 2006

OUR SNEEZE SHIELDS CANNOT REPEL A HUNGER OF THAT MAGNITUDE
I appreciate everyone's feedback on this. I wanted to see if I could make this work by manipulating expression trees, and I put together a visitor that actually works (at least for anonymous types):
code:
internal class PropertyExpressionVisitor<T> : ExpressionVisitor
{
    private readonly Dictionary<string, Expression> propertyExpressions;

    public PropertyExpressionVisitor(Dictionary<string, Expression> propertyExpressions)
    {
        this.propertyExpressions = propertyExpressions;
    }

    [return: NotNullIfNotNull("node")]
    public override Expression? Visit(Expression? node)
    {
        if (node is not LambdaExpression lambda 
         || lambda.ReturnType != typeof(T)
         || lambda.Body is not NewExpression oldConstructorExp
         || oldConstructorExp.Constructor?.DeclaringType != typeof(T)
         || oldConstructorExp.Arguments == null)
            return base.Visit(node);

        var arguments = new List<Expression>(oldConstructorExp.Arguments);            

        foreach (var pe in propertyExpressions.Keys)
        {
            var membIndex = oldConstructorExp.Members?
                                             .Select((m, i) => new { m, i })
                                             .Where(mi => mi.m.Name == pe)
                                             .SingleOrDefault()?.i;

            if (!membIndex.HasValue)
                continue;

            var propExp = Expression.Invoke(propertyExpressions[pe], lambda.Parameters);
            arguments[membIndex.Value] = propExp;
        }

        var newConstructorExp = Expression.New(oldConstructorExp.Constructor, arguments);
        var newLambda = Expression.Lambda(newConstructorExp, lambda.Parameters);

        return newLambda;
    }
}
That paired with this thing successfully convice EF to do the operations server-side:
code:
public static IQueryable<TResult> SelectWithExpressionProperties<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector, params KeyValuePair<string, Expression>[] propertySelectors)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (selector == null) throw new ArgumentNullException(nameof(selector));
    if (propertySelectors == null) throw new ArgumentNullException(nameof(propertySelectors));

    var select = source.Select(selector);
    var visitor = new PropertyExpressionVisitor<TResult>(propertySelectors.ToDictionary(ps => ps.Key, ps => ps.Value));            
    var updatedSelect = select.InterceptWith(visitor);

    return updatedSelect;
}
Calling it looks like this (the anonymous type needs placeholder properties for this to work):
code:
var ctx = new Context();

Expression adder = (TestClass x) => x.Id + 17;

var tc = ctx.TestClasses
            .SelectWithExpressionProperties(x => new
                {
                    Id = 0,
                    FullName = ""
                }, 
                new KeyValuePair<string, Expression>("FullName", ExpressionsHelper.FullName),
                new KeyValuePair<string, Expression>("Id", adder))
            .ToList();
Which generates this SQL:
code:
SELECT [t].[Id] + 17, CASE
    WHEN ([t].[GroupName] IS NOT NULL) AND ([t].[DisplayName] IS NOT NULL) THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END, UPPER(SUBSTRING([t].[GroupName], 0 + 1, 3)), SUBSTRING([t].[DisplayName], 0 + 1, 3)
FROM [TestClasses] AS [t]
Now, whether or not this is actually a good idea is another question...

RC Cola
Aug 1, 2011

Dovie'andi se tovya sagain
I have to migrate from framework 4.6.1 to netcore 6 on a pretty complex bit of software. its uh. fun?

Also its only one day a week that I work on it lol

mystes
May 31, 2006

RC Cola posted:

I have to migrate from framework 4.6.1 to netcore 6 on a pretty complex bit of software. its uh. fun?

Also its only one day a week that I work on it lol
I think regardless of the complexity of the software it entirely just depends on whether you're using specific features/dependencies that don't work in .net 6 that you have to replace; otherwise there aren't a lot of backwards incompatible changes and it should be trivial

mystes fucked around with this message at 20:58 on Jul 12, 2023

RC Cola
Aug 1, 2011

Dovie'andi se tovya sagain

mystes posted:

I think regardless of the complexity of the software it entirely just depends on whether you're using specific features/dependencies that don't work in .net 6 that you have to replace; otherwise there aren't a lot of backwards incompatible changes and it should be trivial

there are so so so many specific features and dependencies that don't work anymore

mystes
May 31, 2006

I mean to be fair if you're dealing with enterprise software that for some reason hasn't been migrated so far it's very likely to be using stuff that won't work too

Kyte
Nov 19, 2013

Never quacked for this

Admiral Snackbar posted:

I appreciate everyone's feedback on this. I wanted to see if I could make this work by manipulating expression trees, and I put together a visitor that actually works (at least for anonymous types):


Now, whether or not this is actually a good idea is another question...

ngl it's cool but I'd be cursing you the entire time I was asked to maintain it.
Hopefully you can squeeze as much reuse from it as possible. :v:

rarbatrol
Apr 17, 2011

Hurt//maim//kill.

RC Cola posted:

there are so so so many specific features and dependencies that don't work anymore

Haha, this hits home. My company had a contact at MS for helping us figure out some of the specifics, and... I guess it'll end up being a little over a year of working on those hard parts.

redleader
Aug 18, 2005

Engage according to operational parameters

RC Cola posted:

there are so so so many specific features and dependencies that don't work anymore

god help you if there's any asp.net in there at all

Ruud Hoenkloewen
Jan 24, 2020

RC Cola posted:

I have to migrate from framework 4.6.1 to netcore 6 on a pretty complex bit of software. its uh. fun?

Also its only one day a week that I work on it lol

I've been slowly hacking away at something like this for the past few months. Fun is definitely one word for it :shepicide:

The only advice I can give is mapping out exactly what all the blockers are and how they depend on each other to help you prioritise poo poo to jettison, transition or rewrite. That and trying to focus on incremental, bankable progress so you don't have to make a massive jump/merge six months from now that everything depends on. I'd say use tests to make sure you don't break anything, but if your situation is anything like mine I'm guessing meaningful tests are a distant dream.

My current hitlist is something like MSMQ, WCF, EF6, some in-house libraries that mess around with AppDomains. History is a nightmare we are trying to wake up from

Cold on a Cob
Feb 6, 2006

i've seen so much, i'm going blind
and i'm brain dead virtually

College Slice
Moq introduced some poo poo called SponsorLink that, if I understand correctly, uses a closed-source obfuscated dll to scan your git.config for your email and sends it (hashed but who cares) to a server to figure out if you're a sponsor of the project and then nags you if you aren't. They have since reverted it but hard to say what their future plans are, the primary dev seems to just not loving get why this is a problem.

Marc Gravell commented on it here

Edit: Author opened another issue to discuss the issues w/ oss developer support here as well

Cold on a Cob fucked around with this message at 16:22 on Aug 10, 2023

Freaksaus
Jun 13, 2007

Grimey Drawer
Thank you, guess we'll be looking into an alternative tomorrow at work, what a shame.

ChocolatePancake
Feb 25, 2007
I've heard good things about NSubstitute and FakeItEasy, but haven't had a chance to test myself yet.
For what it's worth, it looks like the change got rolled back for now due to an incompatibility with MacOS.

Cold on a Cob
Feb 6, 2006

i've seen so much, i'm going blind
and i'm brain dead virtually

College Slice
I've used FakeItEasy, it's fine and never had issues with it.

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

ChocolatePancake posted:

I've heard good things about NSubstitute and FakeItEasy, but haven't had a chance to test myself yet.
For what it's worth, it looks like the change got rolled back for now due to an incompatibility with MacOS.

I've read all the threads, since one of the projects I maintain uses Moq.

The guy is doubling down and actively looking for a way to add back Sponsorlink or some other form of nagware, despite everyone telling him that it's an awful idea. He rejects the idea of selling support contracts or any other form of monetisation that has been actually tried, and he's fixated on the idea of digging down the well until he reaches China getting money out of individual developer donation by guilt-tripping them.

So my recommendation is to permanently pin Moq to 4.18, since it's fortunately not a runtime dependency. Should you ever run into an incompatibility or have some spare time, move to one of the alternatives.

brap
Aug 23, 2004

Grimey Drawer
The idea of having employees pay for the software used by their employer’s product is insane to begin with. Yes on a fully voluntary basis it’s a nice gesture. But absolutely nowhere near a foundation for a business model. Putting aside the outrageous privacy issues.

Adbot
ADBOT LOVES YOU

raminasi
Jan 25, 2005

a last drink with no ice
I've used NSubstitute extensively and never really had to think about it.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply