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
Rocko Bonaparte
Mar 12, 2002

Every day is Friday!
I was trying to watch for file changes in a PowerShell script and got bit. Specifically, I tried to use System.IO.FileSystemWatcher. I think there's a PowerShell thread but my questions are a bit more specific about .NET concurrency. I did something like this:
code:
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property @{IncludeSubdirectories = $false;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
...
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
    $found_files = $true
}
I can see the code get called but $found_files won't become $true from a loop polling on the outside. If I changed $found_files to a list and add the filename to it instead, then that would carry forward. It smelled to me like the boolean was a primitive that was not getting properly updated between threads (memory fence, etc). So it seemed like I had more than one thread involved here. I then tried to use System.Threading.Monitors, but it locked up on the Wait() statement. The FileCreated action wouldn't fire. It sounded to me like that event fires on the same thread instead of coming in from another thread.

BTW, I was using Monitor statements because there isn't a lock keyword in PowerShell, so I'd basically lock using Monitor.Enter, and then use Pulse/Wait depending on whether I was in that event callback (Pulse) or the invoking parent code (Wait).

I can try to post a more complete example, but I would have to fully reconstruct what I was doing. Right now, I'm just polling for the existence of the file, but I always try to make an effort to avoid polling.

Adbot
ADBOT LOVES YOU

LongSack
Jan 17, 2003

Apparently, I grossly misunderstand how assemblies work.

Working on another rewrite of my character portfolio app, this version contains a startup project targeting .NET framework and using WPF, and a data access project targeting .NET standard.

My idea was that only the DAL project would have to know anything about the database, EF Core and AutoMapper. The DAL would only expose interfaces for managing data, and a series of observable DTO classes. The DAL project also has a static DAL class which exposes an Initialize method which is responsible for instantiating and registering the AutoMapper Mapper class, as well as registering all of the concrete classes which go with the exposed interfaces.

Unfortunately, when I call DAL.Initialize() from the constructor of my MainViewModel, it throws exceptions for every library used by the DAL project. Apparently, I need to install all of the NuGet packages in the DAL in the startup project as well? This seems very counter-intuitive to me.

What am I missing? TIA

WorkerThread
Feb 15, 2012

Dependencies are transitive (mostly).

What version of the full framework are you using? In my experience, even in versions supposedly compatible with netstandard2.0, there is just a fractal of issues that prevent you from doing anything without sinking hours into workarounds, etc.

Maybe consider checking out the Core 3.0 preview, where I believe they are adding support for WPF.

e: also, static initializers in your library? :colbert:

WorkerThread fucked around with this message at 01:32 on Jul 23, 2019

LongSack
Jan 17, 2003

WorkerThread posted:

Dependencies are transitive (mostly).

What version of the full framework are you using? In my experience, even in versions supposedly compatible with netstandard2.0, there is just a fractal of issues that prevent you from doing anything without sinking hours into workarounds, etc.

Maybe consider checking out the Core 3.0 preview, where I believe they are adding support for WPF.

The startup targets .NET Framework 4.7.something. Even though the DAL is in a separate directory, and all the dependent libs exist there, the runtime is expecting to find them in the directory with the startup project. So what’s the point of separating out the code into a separate library? It’s not like this code (in this instance, at least) is reusable, unless I came up with a different program that used the same database.

And yes, I am looking forward to 3.0, but I’m not comfortable using preview releases :chloe:

quote:

e: also, static initializers in your library? :colbert:

OK, I’ll bite. The sole purpose of the initializer is to instantiate and register the Mapper and to register all the DAL classes. Why shouldn’t it be static? It tracks whether it had already been initialized and won’t do it twice if reinvoked. I mean, functionally, what’s the difference between DAL.Initialize() and new DALInitializer.Initialize()?

raminasi
Jan 25, 2005

a last drink with no ice

Rocko Bonaparte posted:

I was trying to watch for file changes in a PowerShell script and got bit. Specifically, I tried to use System.IO.FileSystemWatcher. I think there's a PowerShell thread but my questions are a bit more specific about .NET concurrency. I did something like this:
code:
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property @{IncludeSubdirectories = $false;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
...
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
    $found_files = $true
}
I can see the code get called but $found_files won't become $true from a loop polling on the outside. If I changed $found_files to a list and add the filename to it instead, then that would carry forward. It smelled to me like the boolean was a primitive that was not getting properly updated between threads (memory fence, etc). So it seemed like I had more than one thread involved here. I then tried to use System.Threading.Monitors, but it locked up on the Wait() statement. The FileCreated action wouldn't fire. It sounded to me like that event fires on the same thread instead of coming in from another thread.

BTW, I was using Monitor statements because there isn't a lock keyword in PowerShell, so I'd basically lock using Monitor.Enter, and then use Pulse/Wait depending on whether I was in that event callback (Pulse) or the invoking parent code (Wait).

I can try to post a more complete example, but I would have to fully reconstruct what I was doing. Right now, I'm just polling for the existence of the file, but I always try to make an effort to avoid polling.

If your simplified version isn't too simplified, your problem isn't threading, it's scoping. As written, every time the handler files, it creates a new $found_files, sets it to true, and throws it away. If you initially declare $found_files outside of the handler it should work.

WorkerThread
Feb 15, 2012

LongSack posted:

The startup targets .NET Framework 4.7.something. Even though the DAL is in a separate directory, and all the dependent libs exist there, the runtime is expecting to find them in the directory with the startup project. So what’s the point of separating out the code into a separate library? It’s not like this code (in this instance, at least) is reusable, unless I came up with a different program that used the same database.

And yes, I am looking forward to 3.0, but I’m not comfortable using preview releases :chloe:

That sounds a little strange. Do you have any funky custom MSBuild stuff? How are you publishing your app or are you running it straight from VS? Also I'm assuming you're using packages.config and not PackageReference?

LongSack posted:

OK, I’ll bite. The sole purpose of the initializer is to instantiate and register the Mapper and to register all the DAL classes. Why shouldn’t it be static? It tracks whether it had already been initialized and won’t do it twice if reinvoked. I mean, functionally, what’s the difference between DAL.Initialize() and new DALInitializer.Initialize()?

I mean, like that, nothing :)

There is value in letting your application control the configuration its dependent libraries.

I'm definitely a stupid goon so don't take my word as gospel, but:
  1. your data layer can be reconfigured without recompiling
  2. application startup code is in one place
  3. tests become much, much simpler to write (also helps if you use interfaces)

My day job is to do software architect garbage, so obviously those points are a lot more important in that context, but I'd argue they're still worthwhile for solo projects, especially if you're using it as part of a portfolio/demo.

LongSack
Jan 17, 2003

WorkerThread posted:

That sounds a little strange. Do you have any funky custom MSBuild stuff? How are you publishing your app or are you running it straight from VS? Also I'm assuming you're using packages.config and not PackageReference

Nope. It’s a standard project built from my MVVM base with an added dotnetstandard2.0 library for the DAL. It’s not published yet, this is running it via F5 in VS.

quote:

I mean, like that, nothing :)

There is value in letting your application control the configuration its dependent libraries.

I'm definitely a stupid goon so don't take my word as gospel, but:
  1. your data layer can be reconfigured without recompiling
  2. application startup code is in one place
  3. tests become much, much simpler to write (also helps if you use interfaces)

My day job is to do software architect garbage, so obviously those points are a lot more important in that context, but I'd argue they're still worthwhile for solo projects, especially if you're using it as part of a portfolio/demo.

Point taken. But part of this project was that the main project wouldn’t have any knowledge of the DAL beyond the published interfaces and DTO classes. But, still, at some point the DI container needs to have concrete classes registered for those interfaces, and a static initializer seemed like a place for that.

edit: also, if my entity classes are all internal, EF Core won’t find the primary key. I have to use .HasKey to tell EF that a property named Id is the primary key.

LongSack fucked around with this message at 05:12 on Jul 23, 2019

NihilCredo
Jun 6, 2011

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

Did you add the DAL project dependency as a project reference or directly as an assembly reference? The latter is a "dumb" reference that doesn't autoupdate, you normally want the former.

Also, every reference has a "Copy to output folder" property (visible both in the .**proj file and in the Visual Studio properties), which should default to yes but might have been turned off for some reason.

Also, static and instance initializers are both bad. If your class needs to perform some operations that may or may not succeed before it's ready to fulfill its public APIs, then make the constructor private and expose a static factory method that can return a failure state.

LongSack
Jan 17, 2003

NihilCredo posted:

Did you add the DAL project dependency as a project reference or directly as an assembly reference? The latter is a "dumb" reference that doesn't autoupdate, you normally want the former.

Project reference (specifically using the "Project Dependencies" menu item)

quote:

Also, every reference has a "Copy to output folder" property (visible both in the .**proj file and in the Visual Studio properties), which should default to yes but might have been turned off for some reason.

The references are being copied to the output directory - of the library. For some reason, the runtime is looking for them in the directory of the startup project

quote:

Also, static and instance initializers are both bad. If your class needs to perform some operations that may or may not succeed before it's ready to fulfill its public APIs, then make the constructor private and expose a static factory method that can return a failure state.

The DAL library is 20+ entity classes, 20+ DTO classes, 20+ interfaces, 20+ DAL classes (each of those is one per table in the database) as well as some infrastructure classes (NotifyBase, for example). I wasn't sure where to put the initialization code that's required by the library as a whole, so thus the static initializer.

I think I'm going to punt and move everything back into a single project and use namespaces/folders to separate. In this case, there doesn't seem to be an advantage to having this stuff in a separate project and it's causing more problems than it's worth.

beuges
Jul 4, 2005
fluffy bunny butterfly broomstick

LongSack posted:

The references are being copied to the output directory - of the library. For some reason, the runtime is looking for them in the directory of the startup project

If your dependencies are set up right, with the startup project depending on the library project, then when it's built, the library plus its dependencies should be copied into the startup project's output folder, i.e. if your library depends on some additional stuff, you'd have
Library\bin\Debug\Library.dll
Library\bin\Debug\dependency1.dll
Library\bin\Debug\dependency2.dll

and

Startup\bin\Debug\Startup.exe
Startup\bin\Debug\Library.dll
Startup\bin\Debug\dependency1.dll
Startup\bin\Debug\dependency2.dll

with Library.dll, dependency1.dll and dependency2.dll being copied from Library's output.

If you have Startup\bin\Debug\Startup.exe loading up Library\bin\Debug\Library.dll then something is not right.

SirViver
Oct 22, 2008

LongSack posted:

The references are being copied to the output directory - of the library. For some reason, the runtime is looking for them in the directory of the startup project
This is pretty much the expected/standard .NET assembly load behavior. All your assemblies must* be placed next to the executable respectively executing assembly. Anything else requires configuration or manual dynamic loading of assemblies or implementing your own assembly resolve logic.

* Of course, that is not the whole truth: see here.

LongSack
Jan 17, 2003

SirViver posted:

This is pretty much the expected/standard .NET assembly load behavior. All your assemblies must* be placed next to the executable respectively executing assembly. Anything else requires configuration or manual dynamic loading of assemblies or implementing your own assembly resolve logic.

* Of course, that is not the whole truth: see here.

Good link, thanks.

What was happening was that the library DLL was being copied to the startup project directory, but the library's dependent libraries weren't being copied.

All moot now, anyway, since I moved everything back into a single assembly. In this case, There's no real advantage to having a separate assembly versus just using folders / namespaces.

Cuntpunch
Oct 3, 2003

A monkey in a long line of kings

LongSack posted:

Project reference (specifically using the "Project Dependencies" menu item)

This is going to be part of your pain. All this says to VS is 'build Project X before Project Y'. It doesn't involve actual code references at all - how are you even accessing those classes without having added the DAL project as an assembly reference (in Solution Explorer, the References group under the Project root), you'll not only ensure that the build order is handled automatically, but also that the output gets copied over (by default).

Alternately, you could change the settings of both projects to output to a common folder, rather than the default output location.

bobua
Mar 23, 2003
I'd trade it all for just a little more.

I must not have ever understood .net core. I always thought of it as completely cross platform. Hearing that wpf is coming in core 3.0 makes me think I was wrong. What exactly does wpf in core mean?

Cuntpunch
Oct 3, 2003

A monkey in a long line of kings

bobua posted:

I must not have ever understood .net core. I always thought of it as completely cross platform. Hearing that wpf is coming in core 3.0 makes me think I was wrong. What exactly does wpf in core mean?

.NET Core is cross-platform. But WPF is a library that is rooted in .NET Framework, which is not cross-platform.

Or, for a little more detail: .NET Framework has an enormous API surface area. .NET Core only covers *part* of that. And with Core 3.0, WinForms and WPF - previously Framework-only libraries - will be offered in Core. *BUT* only for Windows-run Core.

Rocko Bonaparte
Mar 12, 2002

Every day is Friday!

raminasi posted:

If your simplified version isn't too simplified, your problem isn't threading, it's scoping. As written, every time the handler files, it creates a new $found_files, sets it to true, and throws it away. If you initially declare $found_files outside of the handler it should work.

Not a problem with scoping. I've already obsessively copy-pasted the variable from outside to all places of use just to make sure my beady eyes didn't mess it up somewhere.

I tidied things a bit and put it up on Pastebin: https://pastebin.com/hkY5SMBL

I also have the Monitor statements in there, but commented out. If you run this and put some xxx_returncode.txt file in C:\temp\poll_folder, then you'll see the event listener kick off, which should set $found_files to true. However, it's just a parade of false in the poll loop.

At any rate, I'd prefer not to poll and would rather just block and get kicked on either a timeout or a stimulus from the event. I can't figure out how to make that work. Monitor locking up like it did implies to me the event runs on the same thread as the main loop. When I call Monitor.Wait, I think I starve the event handler since the events don't fire at all any more. I'm assuming it's coroutine/async shenanigans, but I'm not familiar enough with FileSystemWatcher to know what how its events work.

LongSack
Jan 17, 2003

NihilCredo posted:

Also, static and instance initializers are both bad. If your class needs to perform some operations that may or may not succeed before it's ready to fulfill its public APIs, then make the constructor private and expose a static factory method that can return a failure state.

Going back to this, let me ask ...

Given a library that has over 100 classes (each table in the database has a POCO entity class, an observable DTO class, a config class (for use in ApplyConfiguration), a DAL class, and a DAL interface), where would you put code that is needed to Initialize the library as a whole, rather than any individual class?

Before the library is usable, I need to configure, instantiate and register the AutoMapper Mapper class, and register the concrete DAL classes that serve the interfaces.

I recognize that an exception in a static constructor renders the class unusable, but honestly, an error during the initialization of the DAL library is not recoverable anyway.

So, if static initializers are bad, what’s the alternative? Factory methods are out, since I’m initializing the library as a whole, not any individual class. Also, keep in mind that class instantiation is not done by a framework (like in ASP.NET Core), so I can’t do DI just by putting parameters in a constructor. I have to go out and grab dependencies from the container, like
C# code:
public void Foo()
{
    var cdal = Registrar.Get<ICharacterDAL>();
        .
        .
        .
}
So, given these parameters, what is a good solution for initializing the library? Genuinely curious. TIA

nielsm
Jun 1, 2009



Adventures in making a local out-of-process COM server in C#/Framework 4.6

Continuing my previous question about local RPC, I did implement a working call mechanism over named pipes, only missing a way to start the server if it isn't running yet.
However I also kept looking into COM interop in .NET Framework, and came upon this blog post. It turns out creating a local COM server isn't that hard after all?

So I go about to declare an interface, give it an IID, make it ComVisible and dual. I can build my assembly, getting a typelib from it is more difficult. After seeing various warnings about regasm.exe and then trying tlbexp.exe, I figure out that the proper way is after all regasm.exe /tlb, to build the typelib TLB file and get it registered.
Next is implementing a class implementing the interface, give it a CLSID, and calling RegisterTypeForComClients on startup, and it fails? Okay the class needs to be marked public. Now it registers without errors.

Can I then instantiate it? Try with PowerShell: [System.Activator]::CreateInstance([System.Type]::GetTypeFromCLSID([guid]'C4086A64-17AB-48DC-AA1D-0650329210B5'))
It doesn't error!

Can I then call the returned object?
Nope, no useful methods on it. Trying to call Invoke on the Type object returned by GetTypeFromCLSID, nope the best I get is an error about wrong number of arguments to the call. I try calling both by name and by DispID. I can't query anything about the object's actual type.

Maybe the class registration is incomplete, and COM isn't associating the typelib with the object?
I try manually adding the CLSID to registry, pointing LocalServer32 to my EXE file, and referencing the TypeLib by GUID.
Nope, even restarting PowerShell doesn't get me any type information for the dispatch interface.

Well, can I at least have it start the server process when it's not running with class factory registered?
Uh, it hangs. It does start the process indicated in LocalServer32, but never detects the class factory registration. After lots of searching and experimentation, it turns out this only works if the assembly is compiled as x86 or x64, AnyCPU does not work. So far so good.

Okay then let's try calling it straight. I'll load up another C# project and add my typelib as a reference. Oh, that fails because it's produced from a .NET asssembly, it tells me to add the assembly directly. But I don't want to do that, I don't get out-of-process activation then.
Try to do it the classic way, get a Type object by CLSID, then instantiate it with Activator. Copy the interface declaration from the server project, and cast the instantiated object to the interface type.
I can create the object, the server starts, and the interface cast succeeds. Great!
Call the method? Fails. Error indicates something about wrong thread context. But it's an out-of-process object, thread-affinity should be a non issue?!? Just marshal the call and send it over.

This is where I give up and decide to just go ahead with the named pipe and do my own, custom server activation.
Classic answer: "How hard can it be?" Very hard.

Mr Shiny Pants
Nov 12, 2012
Yeah, that sounds about right. The best part in all of this that no one at MS who also had to do this took a step back and said: "How the gently caress did we end up here? Let's fix it."

NihilCredo
Jun 6, 2011

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

LongSack posted:

Going back to this, let me ask ...

Given a library that has over 100 classes (each table in the database has a POCO entity class, an observable DTO class, a config class (for use in ApplyConfiguration), a DAL class, and a DAL interface), where would you put code that is needed to Initialize the library as a whole, rather than any individual class?

Before the library is usable, I need to configure, instantiate and register the AutoMapper Mapper class, and register the concrete DAL classes that serve the interfaces.

I recognize that an exception in a static constructor renders the class unusable, but honestly, an error during the initialization of the DAL library is not recoverable anyway.

So, if static initializers are bad, what’s the alternative? Factory methods are out, since I’m initializing the library as a whole, not any individual class. Also, keep in mind that class instantiation is not done by a framework (like in ASP.NET Core), so I can’t do DI just by putting parameters in a constructor. I have to go out and grab dependencies from the container, like
C# code:
public void Foo()
{
    var cdal = Registrar.Get<ICharacterDAL>();
        .
        .
        .
}
So, given these parameters, what is a good solution for initializing the library? Genuinely curious. TIA

It's less about the individual class and more about the workflow. The idea is that you have to perform several steps to get your data, some of which may fail, and we're trying to make it as explicit and enforceable as possible that you should handle these failures.

Here's a rough guess at how your workflow may look like for some basic CRUD:

0) Get the connection strings and other options from environment variables or config files or whatnot. This isn't part of your library, but it's still external data that may or may not be successfully loaded.

1) Initialize the library, i.e. get the Context online. This may or may not succeed for reasons other than bugs, eg. database unavailable/borked.

2) Get an individual DAL object. The DAL classes are basically (I assume) a bundle of functions operating over the Context, so if the Context is valid then instantiating the DAL class can't really fail.

3) Get a POCO object with the content of a database row. This may or may not succeed for reasons other than bugs, eg. database suddenly unavailable/borked, or a primary key suddenly vanished.

4) Get a DTO object that can update/delete the database row. Again, the DTO class is just a bundle of functions and events wrapped around the POCO class, so you can't really fail to instantiate it. Its functions, of course, may fail.

5) Use the DTO object's methods to update/delete the original row. This may or may not succeed, of course.

6) You're done. All you need at this point is to know if the update/delete operation succeeded, or why it failed.

The above workflow could look like this in code:

code:
  try {
     
     var connectionString = Environment.GetEnvironmentVariable(ENV_CONN_STRING);
     var initializedContext = Library.Initialize(connectionString);
    
     // I'd want a generic type constraint on GetDAL<T> so that T must inherit from IDalBase, so I can't do GetDAL<IButtsPOCO>
     var buttsDal = initializedContext.GetDAL<IButtsDAL>();
     var buttPoco = buttsDal.GetPoco(somePrimaryKey);
     var buttDto = buttPoco.MakeDto();
     buttDto.size = 9999;
     var updateResult = buttDto.Update();
     return (updateResult.RowsAffected > 0);
  }
  catch (MissingEnvVarException e) { panic("No connection string!"); }
  catch (DatabaseNotFoundException e) { panic("Database not found!", e.ToString()); }
  catch (PocoNotFoundException e) { panic("Poco not found!"); }
  catch (UpdateFailedException e) { panic("Update failed!"); }

and it's already pretty good. You can't forget a step; each step past the first requires the result from the previous step. You don't need to consider "where do I put the Library.Initialize() call to make absolutely sure it gets executed". If it doesn't get executed, you don't have an initializedContext, and you can't use the rest of the library.

The extra step is to encode in the function calls themselves the fact that this particular operation may fail in that particular way. This removes the need for you to manually wire up those catch() blocks, which in the example above are absolutely unenforced - I didn't handle the WrongPocoColumnsException, for example, but it still compiles and can bite me at runtime. That's what functional-style error handling does, and I can write a post about that too if you want. Java's checked exceptions are another way to achieve the same safety, but unfortunately they can't be implemented in C#.

NihilCredo fucked around with this message at 20:43 on Jul 24, 2019

LongSack
Jan 17, 2003


Initializing the library itself has nothing to do with the library workflow (and the above is fairly accurate) except for the fact that the initialization is required for the library to work at all. It needs to happen once at application startup.

Here is what happens in the (formerly static) initializer:
  • create an AutoMapper MapperConfiguration object and pass to it all of the entity-to-DTO mappings (and the reverse mappings)
  • create an AutoMapper Mapper object from that config
  • register that Mapper object with DI as an object that is an IMapper
  • for each table in the database (currently 27), register an IFooDAL interface with the concrete FooDAL class
If the DAL were a monolithic class, this stuff would go in the constructor. But it’s not, it’s a collection of about 140 classes and interfaces, so where does this code go?

Since I moved everything back into a single assembly, I’m handling it in the constructor of my MainViewModel, although I suppose it could go in App.xaml.cs, but I’m curious to learn if there is a better way.

Xik
Mar 10, 2011

Dinosaur Gum
IMapper is in the constructor of every class that needs it and DI resolves it. Under normal circumstances there is just one global IMapper object containing all the mappings for every type.

Assuming you have assemblies/namespaces for various layers (request, controller, logic, dal etc ) the mapping config is setup so that you have a main rule file in each namespace and they each call the rule file in the assembly "below" it, keeping a clear heriachy of dependencies.

It doesn't matter if your DAL is one huge monolothic class or hundreds of classes, you just need that IMapper as a constructor dependency wherever it's needed, there is no extra overhead for you as a dev.

For testing, you just test the mappings seperatly. When testing every other part of the app you can inject a mock mapper.

No Pants
Dec 10, 2000


For where it goes, you usually put all that configuration and registration in your main assembly where it will execute once on application startup, somewhere sane people don't unit test. In a WPF application, that might be an overridden Application.OnStartup(StartupEventArgs).

You very probably do not want to put it in the constructor of a viewmodel, since that's not a viewmodel's responsibility, and it makes that viewmodel less testable.

LongSack
Jan 17, 2003

Xik posted:

IMapper is in the constructor of every class that needs it and DI resolves it. Under normal circumstances there is just one global IMapper object containing all the mappings for every type.

Except that this isn’t ASP.NET where the framework instantiates everything and DI happens by magic. My DAL classes all start out like
C# code:
public class FooDAL : IFooDAL
{
    private readonly IMapper _mapper;

    public FooDAL()
    {
        _mapper = Registrar.Get<IMapper>();
    }
    etc...
}

quote:

For where it goes, you usually put all that configuration and registration in your main assembly where it will execute once on application startup, somewhere sane people don't unit test. In a WPF application, that might be an overridden Application.OnStartup(StartupEventArgs).

OK, so App.xaml.cs is a good place. Thank you.

WorkerThread
Feb 15, 2012

LongSack posted:

Except that this isn’t ASP.NET where the framework instantiates everything and DI happens by magic. My DAL classes all start out like
C# code:
public class FooDAL : IFooDAL
{
    private readonly IMapper _mapper;

    public FooDAL()
    {
        _mapper = Registrar.Get<IMapper>();
    }
    etc...
}
OK, so App.xaml.cs is a good place. Thank you.

What prevents you from using constructor injection? You don't need a container at all. Back before I settled on Microsoft.Extensions.DependencyInjection, I would create/configure all my classes in the application root (Program.cs/App.xaml.cs/etc) manually. No magic required :)

I'm not saying that was especially smart, as using containers is actually very simple, but it's certainly possible.

raminasi
Jan 25, 2005

a last drink with no ice

Rocko Bonaparte posted:

Not a problem with scoping. I've already obsessively copy-pasted the variable from outside to all places of use just to make sure my beady eyes didn't mess it up somewhere.

I tidied things a bit and put it up on Pastebin: https://pastebin.com/hkY5SMBL

I also have the Monitor statements in there, but commented out. If you run this and put some xxx_returncode.txt file in C:\temp\poll_folder, then you'll see the event listener kick off, which should set $found_files to true. However, it's just a parade of false in the poll loop.

At any rate, I'd prefer not to poll and would rather just block and get kicked on either a timeout or a stimulus from the event. I can't figure out how to make that work. Monitor locking up like it did implies to me the event runs on the same thread as the main loop. When I call Monitor.Wait, I think I starve the event handler since the events don't fire at all any more. I'm assuming it's coroutine/async shenanigans, but I'm not familiar enough with FileSystemWatcher to know what how its events work.

Oh, Powershell closures apparently capture by value. One way around this is something called a reference object (apparently, I don't really know Powershell):
PowerShell code:
$found_files = $false
$found_files_ref = [ref]$found_files
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property @{IncludeSubdirectories = $false;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
...
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
    $found_files_ref.Value = $true
}
I just tested this with a simple Timer event; I reproduced your original problem and fixed it using the reference object.

LongSack
Jan 17, 2003

WorkerThread posted:

What prevents you from using constructor injection? You don't need a container at all. Back before I settled on Microsoft.Extensions.DependencyInjection, I would create/configure all my classes in the application root (Program.cs/App.xaml.cs/etc) manually. No magic required :)

I'm not saying that was especially smart, as using containers is actually very simple, but it's certainly possible.

I’m using my own homebrew DI container which doesn’t support it. I guess it’s time to look at Microsoft’s DI.

Does it support “object registration” if that’s the correct term? Like, my DI class supports “register” (a new instance is created every call), “register singleton” (the first call caches the created object which is returned on subsequent calls) and “register object” which adds an instantiated object directly to the cache, so it’s like “register singleton” except that the object is provided at registration time).

WorkerThread
Feb 15, 2012

LongSack posted:

I’m using my own homebrew DI container which doesn’t support it. I guess it’s time to look at Microsoft’s DI.

Does it support “object registration” if that’s the correct term? Like, my DI class supports “register” (a new instance is created every call), “register singleton” (the first call caches the created object which is returned on subsequent calls) and “register object” which adds an instantiated object directly to the cache, so it’s like “register singleton” except that the object is provided at registration time).

Got it. Microsoft's container should feel pretty familiar, then, except it supports constructor injection. You've got three scopes available-- Singleton, Scoped, and Transient (like what you called "register" above). You can also provide pre-built instances as well for tricky to construct types.

I prefer that container because it doesn't really do a lot of magic for you in the background: no auto-registration or assembly scanning, etc. I like to see the registrations for each type in use by my app.

LongSack
Jan 17, 2003

WorkerThread posted:

Got it. Microsoft's container should feel pretty familiar, then, except it supports constructor injection. You've got three scopes available-- Singleton, Scoped, and Transient (like what you called "register" above). You can also provide pre-built instances as well for tricky to construct types.

I prefer that container because it doesn't really do a lot of magic for you in the background: no auto-registration or assembly scanning, etc. I like to see the registrations for each type in use by my app.

Thanks! I’ll take a look at it tomorrow.

LongSack
Jan 17, 2003

LongSack posted:

Thanks! I’ll take a look at it tomorrow.

Rewrote the (thankfully only five I had already written) DALs to use Microsoft’s DI using constructor injection and finished the other 22 today. It was surprisingly (to me) easy to do. Then, in App.xaml.cs I created the ServiceCollection and registered all my services, and flipped that into a ServiceProvider. Now what do I do with that to make it available to the rest of the program? Ended up registering it with my original DI container.

Haven’t got to the point where I can actually run any of the code yet, so hopefully it all works.

Also had a bit of a breakthrough.

My DAL classes have standard Insert, Update and Delete methods, plus 2 types of read methods: Get methods return an IEnumerable always. If the read returns no rows, it returns an empty collection, but they always return a collection. In contrast, Read methods return either an object, or null, since Read is used to grab a single item, where Get is used to get a collection.

I realized, in a blinding flash of the obvious, that these various methods were basically the same except for the Where clause.

I realized that I could write a single Get method and pass it an Expression<Func<foo, bool>> parameter with a default value of null.

So now my GetForCharacter(int cid) method can simply be Get(x => x.CharacterId == cid);

Even the reads can use the same method. Read(int id) becomes Get(x => x.Id == id).SingleOrDefault();

This localizes all of the read code into a single method, which helps with maintainability.

Maybe this is obvious to y’all, but it was a breakthrough for me.

Oh, and if you’re wondering why use Expression<Func<foo, bool>> rather than just Func<foo, bool>, it’s because Where with the latter returns an IEnumerable<foo>, so you can’t add, say, an .AsNoTracking(). Where with the former returns an IQueryable<foo>, so you can.

beuges
Jul 4, 2005
fluffy bunny butterfly broomstick

LongSack posted:

Oh, and if you’re wondering why use Expression<Func<foo, bool>> rather than just Func<foo, bool>, it’s because Where with the latter returns an IEnumerable<foo>, so you can’t add, say, an .AsNoTracking(). Where with the former returns an IQueryable<foo>, so you can.

Just so you know, there are .AsQueryable<T> and .AsEnumerable<T> methods that are available from entity framework that will transform from one to the other.

Cuntpunch
Oct 3, 2003

A monkey in a long line of kings

LongSack posted:

Rewrote the (thankfully only five I had already written) DALs to use Microsoft’s DI using constructor injection and finished the other 22 today. It was surprisingly (to me) easy to do. Then, in App.xaml.cs I created the ServiceCollection and registered all my services, and flipped that into a ServiceProvider. Now what do I do with that to make it available to the rest of the program? Ended up registering it with my original DI container.

What do you mean 'the rest of the program'?

You've setup your container at the application root. Everything your program needs should be configured and ready to go, shouldn't it?

EssOEss
Oct 23, 2006
128-bit approved

beuges posted:

Just so you know, there are .AsQueryable<T> and .AsEnumerable<T> methods that are available from entity framework that will transform from one to the other.

Be very very careful with these, however!

An IQueryable<T> from Entity Framework represents a database query being constructed.

If you do .AsEnumerable() it stops being a database query - whatever you had before that will be executed on enumeration and any further .Where() etc statements will not reach the database and will be executed in memory.

The same applies if you pass an IQueryable<T> to a function taking IEnumerable<T> as input.

You cannot undo this effect with .AsQueryable() (that just means "pretend there is a fake query being constructed but really all we have is this in memory list").

This is the main reason why many programmers create database reading code that downloads the entire table into app memory on every query. Don't let IQueryable become IEnumerable!

Munkeymon
Aug 14, 2003

Motherfucker's got an
armor-piercing crowbar! Rigoddamndicu𝜆ous.



LongSack posted:

Rewrote the (thankfully only five I had already written) DALs to use Microsoft’s DI using constructor injection and finished the other 22 today. It was surprisingly (to me) easy to do. Then, in App.xaml.cs I created the ServiceCollection and registered all my services, and flipped that into a ServiceProvider. Now what do I do with that to make it available to the rest of the program? Ended up registering it with my original DI container.

Haven’t got to the point where I can actually run any of the code yet, so hopefully it all works.

Also had a bit of a breakthrough.

My DAL classes have standard Insert, Update and Delete methods, plus 2 types of read methods: Get methods return an IEnumerable always. If the read returns no rows, it returns an empty collection, but they always return a collection. In contrast, Read methods return either an object, or null, since Read is used to grab a single item, where Get is used to get a collection.

I realized, in a blinding flash of the obvious, that these various methods were basically the same except for the Where clause.

I realized that I could write a single Get method and pass it an Expression<Func<foo, bool>> parameter with a default value of null.

So now my GetForCharacter(int cid) method can simply be Get(x => x.CharacterId == cid);

Even the reads can use the same method. Read(int id) becomes Get(x => x.Id == id).SingleOrDefault();

This localizes all of the read code into a single method, which helps with maintainability.

Maybe this is obvious to y’all, but it was a breakthrough for me.

Oh, and if you’re wondering why use Expression<Func<foo, bool>> rather than just Func<foo, bool>, it’s because Where with the latter returns an IEnumerable<foo>, so you can’t add, say, an .AsNoTracking(). Where with the former returns an IQueryable<foo>, so you can.

Sounds like you've more-or-less discovered the Repository Pattern https://deviq.com/repository-pattern/

LongSack
Jan 17, 2003

Cuntpunch posted:

What do you mean 'the rest of the program'?

You've setup your container at the application root. Everything your program needs should be configured and ready to go, shouldn't it?

Well, constructor injection only works with classes being instantiated by DI, right? This isn't ASP.NET Core where everything is basically instantiated by the framework. Let's take my MainViewModel. It needs access to services in DI, so how does it get access to the ServiceProvider? It gets instantiated in the code-behind of the main Window as
C# code:
    DataContext = new MainViewModel();
If i stick an IServiceProvider constructor parameter, the code-behind will have to get access to it somehow in order to pass it to the ViewModel.

I suppose I could move the DI initialization code into the code-behind of the main Window and then it could pass the provider to the VM, but that feels like the wrong place for that (that code is currently in App.xaml.cs)

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

LongSack posted:

Well, constructor injection only works with classes being instantiated by DI, right? This isn't ASP.NET Core where everything is basically instantiated by the framework. Let's take my MainViewModel. It needs access to services in DI, so how does it get access to the ServiceProvider? It gets instantiated in the code-behind of the main Window as
C# code:
    DataContext = new MainViewModel();
If i stick an IServiceProvider constructor parameter, the code-behind will have to get access to it somehow in order to pass it to the ViewModel.

I suppose I could move the DI initialization code into the code-behind of the main Window and then it could pass the provider to the VM, but that feels like the wrong place for that (that code is currently in App.xaml.cs)

Constructor injection just means "an implementation is passed in via the constructor".

DataContext = new MainViewModel(someRequiredServiceImplementation);

However, the way it typically works with a DI container is that you tell the DI container "give me a MainViewModel that's constructed correctly" and it handles creating it for you with all of the appropriate dependencies already injected.

New Yorp New Yorp fucked around with this message at 16:27 on Jul 26, 2019

Cuntpunch
Oct 3, 2003

A monkey in a long line of kings

LongSack posted:

Well, constructor injection only works with classes being instantiated by DI, right? This isn't ASP.NET Core where everything is basically instantiated by the framework. Let's take my MainViewModel. It needs access to services in DI, so how does it get access to the ServiceProvider? It gets instantiated in the code-behind of the main Window as
C# code:
    DataContext = new MainViewModel();
If i stick an IServiceProvider constructor parameter, the code-behind will have to get access to it somehow in order to pass it to the ViewModel.

I suppose I could move the DI initialization code into the code-behind of the main Window and then it could pass the provider to the VM, but that feels like the wrong place for that (that code is currently in App.xaml.cs)

You've referenced ASP.NET a few times now and I think you may need to peek behind the curtain and see the real Wizard of Oz - because ASP.NET has some standard behavior around its DI stuff, but there's really nothing magic about it. It's just hiding the boilerplate from you.

In the case of XAML based stuff, I've tended to follow Bugnion's ViewModelLocator pattern.
Have a Locator class that, at construction time, registers all of your Interfaces, Implementations, AND ViewModels with your DI container.
This class should also expose each ViewModel as a get-only property, resolved out of the DI container.
In your App.xaml, add an instance of this class to a resource dictionary.
Then in any other XAML view you've got, you'll be able to just do something like DataContext={FooViewModel, Source={StaticResource Locator}}

Bang, you've 'wired up' your entire application effectively at application start. Bonus: you can now setup Design-Time implementations of your services so that the UI designer well be more useful.

LongSack
Jan 17, 2003

New Yorp New Yorp posted:

Constructor injection just means "an implementation is passed in via the constructor".

DataContext = new MainViewModel(someRequiredServiceImplementation);

However, the way it typically works with a DI container is that you tell the DI container "give me a MainViewModel that's constructed correctly" and it handles creating it for you with all of the appropriate dependencies already injected.

Right, but in that instance - unless I am fundamentally misunderstanding things (always a possibility) - the code-behind still need access to the ServiceProvider in order to pass it to the MainViewModel. So either I create and initialize the DI in the code-behind for the main window (which feels wrong), or I do it in the application startup code and find a way to make the provider available to the code-behind so it can pass the provider to the view model. I suppose i could add it to Application.Resources as a way of doing it, but currently i'm registering it with my old DI registrar.

LongSack
Jan 17, 2003

Cuntpunch posted:

You've referenced ASP.NET a few times now and I think you may need to peek behind the curtain and see the real Wizard of Oz - because ASP.NET has some standard behavior around its DI stuff, but there's really nothing magic about it. It's just hiding the boilerplate from you.

In the case of XAML based stuff, I've tended to follow Bugnion's ViewModelLocator pattern.
Have a Locator class that, at construction time, registers all of your Interfaces, Implementations, AND ViewModels with your DI container.
This class should also expose each ViewModel as a get-only property, resolved out of the DI container.
In your App.xaml, add an instance of this class to a resource dictionary.
Then in any other XAML view you've got, you'll be able to just do something like DataContext={FooViewModel, Source={StaticResource Locator}}

Bang, you've 'wired up' your entire application effectively at application start. Bonus: you can now setup Design-Time implementations of your services so that the UI designer well be more useful.

This looks interesting, and since I haven't really started coding the view models yet, I'll look into it. I Hadn't planned on putting the view models into DI, but I can see the advantages of the above. As for the bonus, that's always been available using {d:DesignInstance Type=FooViewModel, IsDesignTimeCreatable=False}, hasn't it?

Adbot
ADBOT LOVES YOU

Cuntpunch
Oct 3, 2003

A monkey in a long line of kings

LongSack posted:

This looks interesting, and since I haven't really started coding the view models yet, I'll look into it. I Hadn't planned on putting the view models into DI, but I can see the advantages of the above. As for the bonus, that's always been available using {d:DesignInstance Type=FooViewModel, IsDesignTimeCreatable=False}, hasn't it?

Sure - but if FooViewModel *requires* an IBarService, that starts to get messy.

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