commit d38f26291f07364a9a1fe3f7f51c55f58bfdc54c Author: Jonas Hinterdorfer Date: Thu Jan 15 14:41:25 2026 +0100 added proj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa1d9e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,403 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +Thumbs.db + +*.bak + +.idea/ diff --git a/Actor.csv b/Actor.csv new file mode 100644 index 0000000..a997c43 Binary files /dev/null and b/Actor.csv differ diff --git a/ActorType.csv b/ActorType.csv new file mode 100644 index 0000000..22ad4ca Binary files /dev/null and b/ActorType.csv differ diff --git a/Iot.sln b/Iot.sln new file mode 100644 index 0000000..cbdd9f5 --- /dev/null +++ b/Iot.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32210.238 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Iot", "Iot\Iot.csproj", "{29858458-29BE-4432-BADD-3F00367A34D5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {29858458-29BE-4432-BADD-3F00367A34D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29858458-29BE-4432-BADD-3F00367A34D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29858458-29BE-4432-BADD-3F00367A34D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29858458-29BE-4432-BADD-3F00367A34D5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8E039742-FC97-46AF-9602-39FD513A6A88} + EndGlobalSection +EndGlobal diff --git a/Iot.sln.DotSettings b/Iot.sln.DotSettings new file mode 100644 index 0000000..340a79b --- /dev/null +++ b/Iot.sln.DotSettings @@ -0,0 +1,113 @@ + + + ExplicitlyExcluded + ExplicitlyExcluded + 39FD3420-6140-4A24-82D3-2E9FB14B096E/d:ClientApp/d:dist + 972C43C8-8BF9-41FA-BA3C-299AC44A6938/d:Migrations + BD772F21-D4E2-4577-9AF3-FBE59BB72C03/d:Migrations + D2B2F798-28FC-4515-966F-B92C3D11D3F2/d:ClientApp/d:dist + + clipper.cs + package-lock.json + + SUGGESTION + SUGGESTION + + True + True + 1 + + True + True + True + True + True + True + True + True + True + True + True + False + True + + 250 + 20000 + 250 + True + True + + CRUD + CNC + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + + False + True + True + + DO_NOTHING + LIVE_MONITOR + LIVE_MONITOR + DO_NOTHING + LIVE_MONITOR + LIVE_MONITOR + LIVE_MONITOR + LIVE_MONITOR + LIVE_MONITOR + LIVE_MONITOR + LIVE_MONITOR + LIVE_MONITOR + DO_NOTHING + LIVE_MONITOR + + True + True + True + True + True + + True + True + True + True + True \ No newline at end of file diff --git a/Iot/CsvEntities/Actor.cs b/Iot/CsvEntities/Actor.cs new file mode 100644 index 0000000..7df9e1f --- /dev/null +++ b/Iot/CsvEntities/Actor.cs @@ -0,0 +1,11 @@ +namespace Iot.CsvEntities; + +using System; + +public class Actor +{ + public int Id { get; set; } + public string Name { get; set; } + public DateTime FirstSeen { get; set; } + public int ActorTypeId { get; set; } +} \ No newline at end of file diff --git a/Iot/CsvEntities/ActorType.cs b/Iot/CsvEntities/ActorType.cs new file mode 100644 index 0000000..b4d8b89 --- /dev/null +++ b/Iot/CsvEntities/ActorType.cs @@ -0,0 +1,7 @@ +namespace Iot.CsvEntities; + +public class ActorType +{ + public int Id { get; set; } + public string Name { get; set; } +} \ No newline at end of file diff --git a/Iot/CsvEntities/Measurement.cs b/Iot/CsvEntities/Measurement.cs new file mode 100644 index 0000000..04c149f --- /dev/null +++ b/Iot/CsvEntities/Measurement.cs @@ -0,0 +1,12 @@ +namespace Iot.CsvEntities; + +using System; + +public class Measurement +{ + public int Id { get; set; } + public DateTime Time { get; set; } + public int ActorId { get; set; } + public int MeasurementTypeId { get; set; } + public double Value { get; set; } +} \ No newline at end of file diff --git a/Iot/CsvEntities/MeasurementType.cs b/Iot/CsvEntities/MeasurementType.cs new file mode 100644 index 0000000..97781ba --- /dev/null +++ b/Iot/CsvEntities/MeasurementType.cs @@ -0,0 +1,8 @@ +namespace Iot.CsvEntities; + +public class MeasurementType +{ + public int Id { get; set; } + public string Name { get; set; } + public string Comment { get; set; } +} \ No newline at end of file diff --git a/Iot/Entities/Actor.cs b/Iot/Entities/Actor.cs new file mode 100644 index 0000000..414b4a1 --- /dev/null +++ b/Iot/Entities/Actor.cs @@ -0,0 +1,16 @@ +namespace Iot.Entities; + +using System; +using System.Collections.Generic; + +public class Actor +{ + public int Id { get; set; } + public string Name { get; set; } + public DateTime FirstSeen { get; set; } + public int ActorTypeId { get; set; } + + // Navigation Properties + public ActorType ActorType { get; set; } + public List Measurements { get; set; } = new List(); +} \ No newline at end of file diff --git a/Iot/Entities/ActorType.cs b/Iot/Entities/ActorType.cs new file mode 100644 index 0000000..3fb3dfe --- /dev/null +++ b/Iot/Entities/ActorType.cs @@ -0,0 +1,12 @@ +namespace Iot.Entities; + +using System.Collections.Generic; + +public class ActorType +{ + public int Id { get; set; } + public string Name { get; set; } + + // Navigation Property + public List Actors { get; set; } = new List(); +} \ No newline at end of file diff --git a/Iot/Entities/Entities.cd b/Iot/Entities/Entities.cd new file mode 100644 index 0000000..8ea776f --- /dev/null +++ b/Iot/Entities/Entities.cd @@ -0,0 +1,45 @@ + + + + + + AAACAAAAAAAAAAAEAAAACAQAAAAAAAAAAAAQAAAAAAQ= + Entities\Actor.cs + + + + + + + + + AAACAAAAAAAAgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA= + Entities\ActorType.cs + + + + + + + + + AAACAAAAAAAAgAAAAAAAAAAIAAAAIAAAAAAgAQAAAQA= + Entities\Measurement.cs + + + + + + + + + + AAACAAAEAAAAAAAAAAAAAAQAAAAAAAAAAAAQAAAAAAA= + Entities\MeasurementType.cs + + + + + + + \ No newline at end of file diff --git a/Iot/Entities/Measurement.cs b/Iot/Entities/Measurement.cs new file mode 100644 index 0000000..61af6cd --- /dev/null +++ b/Iot/Entities/Measurement.cs @@ -0,0 +1,16 @@ +namespace Iot.Entities; + +using System; + +public class Measurement +{ + public int Id { get; set; } + public DateTime Time { get; set; } + public int ActorId { get; set; } + public int MeasurementTypeId { get; set; } + public double Value { get; set; } + + // Navigation Properties + public Actor Actor { get; set; } + public MeasurementType MeasurementType { get; set; } +} \ No newline at end of file diff --git a/Iot/Entities/MeasurementType.cs b/Iot/Entities/MeasurementType.cs new file mode 100644 index 0000000..a8c1b93 --- /dev/null +++ b/Iot/Entities/MeasurementType.cs @@ -0,0 +1,13 @@ +namespace Iot.Entities; + +using System.Collections.Generic; + +public class MeasurementType +{ + public int Id { get; set; } + public string Name { get; set; } + public string Comment { get; set; } + + // Navigation Property + public List Measurements { get; set; } = new List(); +} \ No newline at end of file diff --git a/Iot/Iot.csproj b/Iot/Iot.csproj new file mode 100644 index 0000000..3dc3a5a --- /dev/null +++ b/Iot/Iot.csproj @@ -0,0 +1,23 @@ + + + + Exe + net9.0 + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/Iot/Program.cs b/Iot/Program.cs new file mode 100644 index 0000000..05bd201 --- /dev/null +++ b/Iot/Program.cs @@ -0,0 +1,195 @@ +/*-------------------------------------------------------------- +* HTBLA-Leonding / Class: 1xHIF +*-------------------------------------------------------------- +* Musterlösung-HA +*-------------------------------------------------------------- +* Description: Iot +*-------------------------------------------------------------- +*/ + +using System; +using System.Linq; +using System.Text; + +using Iot.Tools; + + +Console.WriteLine("Iot"); +Console.WriteLine("====================="); + +#region Import Csvs + +string fileNameActor = "Actor.csv"; +string fileNameActorType = "ActorType.csv"; +string fileNameMeasurement = "Measurement.csv"; +string fileNameMeasurementType = "MeasurementType.csv"; + +var actorsCsv = new CsvImport() +{ + DateFormat = "yyyy/MM/dd", + TimeFormat = "HH:mm:ss", + Encoding = Encoding.UTF8 +}.Read(fileNameActor); + +var actorsTypeCsv = new CsvImport() +{ + DateFormat = "yyyy/MM/dd", + TimeFormat = "HH:mm:ss", + Encoding = Encoding.UTF8 +}.Read(fileNameActorType); + +var measurementsCsv = new CsvImport() +{ + DateFormat = "yyyy/MM/dd", + TimeFormat = "HH:mm:ss", + Encoding = Encoding.UTF8 +}.Read(fileNameMeasurement); + +var measurementTypesCsv = new CsvImport() +{ + DateFormat = "yyyy/MM/dd", + TimeFormat = "HH:mm:ss", + Encoding = Encoding.UTF8 +}.Read(fileNameMeasurementType); + +#endregion + +#region Convert to Entities (with navigation properties) + +var actorTypes = actorsTypeCsv.Select(at => new Iot.Entities.ActorType +{ + Id = at.Id, + Name = at.Name +}).ToDictionary(x => x.Id, x => x); + +var actors = actorsCsv.Select(a => new Iot.Entities.Actor +{ + Id = a.Id, + Name = a.Name, + FirstSeen = a.FirstSeen, + ActorTypeId = a.ActorTypeId, + ActorType = actorTypes[a.ActorTypeId] +}).ToList(); + +var measurementTypes = measurementTypesCsv.Select(mt => new Iot.Entities.MeasurementType +{ + Id = mt.Id, + Name = mt.Name, + Comment = mt.Comment +}).ToList(); + +var measurementTypeDict = measurementTypes.ToDictionary(x => x.Id); +var actorDict = actors.ToDictionary(a => a.Id); + +var measurements = measurementsCsv.Select(m => +{ + actorDict.TryGetValue(m.ActorId, out var actor); + measurementTypeDict.TryGetValue(m.MeasurementTypeId, out var measurementType); + + return new Iot.Entities.Measurement + { + Id = m.Id, + Time = m.Time, + ActorId = m.ActorId, + MeasurementTypeId = m.MeasurementTypeId, + Value = m.Value, + Actor = actor, + MeasurementType = measurementType + }; +}).ToList(); + + +// Populate Actor.Measurements using ToLookup (O(n) instead of O(n*m)) +var measurementsByActor = measurements.ToLookup(m => m.ActorId); +foreach (var actor in actors) +{ + actor.Measurements = measurementsByActor[actor.Id].ToList(); +} + +// Populate MeasurementType.Measurements using ToLookup (O(n) instead of O(n*m)) +var measurementsByType = measurements.ToLookup(m => m.MeasurementTypeId); +foreach (var measurementType in measurementTypes) +{ + measurementType.Measurements = measurementsByType[measurementType.Id].ToList(); +} + +// Populate ActorType.Actors using ToLookup (O(n) instead of O(n*m)) +var actorsByType = actors.ToLookup(a => a.ActorTypeId); +foreach (var actorType in actorTypes.Values) +{ + actorType.Actors = actorsByType[actorType.Id].ToList(); +} + +#endregion + +#region some Reports + +Console.WriteLine("List of Actors"); +Console.WriteLine("Id Name Count"); +Console.WriteLine("=========================="); + +var actorReport = actors + .Select(a => new + { + a.Id, + a.Name, + Count = a.Measurements.Count + }) + .OrderBy(a => a.Id); + +foreach (var item in actorReport) +{ + Console.WriteLine($"{item.Id,2} {item.Name,-17} {item.Count,5}"); +} + +Console.WriteLine(); +Console.WriteLine("List of Measurement-Types"); +Console.WriteLine("Id Name Count"); +Console.WriteLine("=========================="); + +var measurementTypeReport = measurementTypes + .Select(mt => new + { + mt.Id, + mt.Name, + Count = mt.Measurements.Count + }) + .OrderBy(mt => mt.Id); + +foreach (var item in measurementTypeReport) +{ + Console.WriteLine($"{item.Id,2} {item.Name,-17} {item.Count,5}"); +} + +Console.WriteLine(); +Console.WriteLine("List of Actor by Measurement-Types"); +Console.WriteLine("Actor Measurement Count Min Max"); +Console.WriteLine("=============================================================="); + +var actorMeasurementReport = measurements + .GroupBy(m => new + { + ActorName = m.Actor?.Name ?? "Unknown", + MeasurementName = m.MeasurementType?.Name ?? "Unknown" + }) + .Select(g => + { + var values = g.Select(m => m.Value).ToList(); + return new + { + Actor = g.Key.ActorName, + Measurement = g.Key.MeasurementName, + Count = values.Count, + Min = values.Min(), + Max = values.Max() + }; + }) + .OrderBy(r => r.Actor) + .ThenBy(r => r.Measurement); + +foreach (var item in actorMeasurementReport) +{ + Console.WriteLine($"{item.Actor,-14} {item.Measurement,-18} {item.Count,5} {item.Min,10:F2} {item.Max,10:F2}"); +} + +#endregion \ No newline at end of file diff --git a/Iot/Tools/CsvImport.cs b/Iot/Tools/CsvImport.cs new file mode 100644 index 0000000..e21cb6d --- /dev/null +++ b/Iot/Tools/CsvImport.cs @@ -0,0 +1,253 @@ +/* + This file is part of https://github.com/aiten/Framework. + + Copyright (c) Herbert Aitenbichler + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +namespace Iot.Tools +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + + public class CsvImport : CsvImportBase where T : new() + { + public class ColumnMapping + { + public string ColumnName { get; set; } + public PropertyInfo MapTo { get; set; } + public bool Ignore { get; set; } + +#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + public Func GetValue { get; set; } + public Func AdjustValue { get; set; } + public Action SetValue { get; set; } + +#pragma warning restore CS8632 + + public bool IsConfigured => Ignore || MapTo != null || SetValue != null; + public bool IsMapped => !Ignore && MapTo != null; + public bool IsSetValue => !Ignore && SetValue != null; + } + + public ICollection IgnoreColumns { get; set; } + public IDictionary MapColumns { get; set; } + + public IList Read(string[] csvLines) + { + var lines = ReadStringMatrixFromCsv(csvLines, false); + return MapTo(lines); + } + + public IList Read(string fileName) + { + var lines = ReadStringMatrixFromCsv(fileName, false); + return MapTo(lines); + } + + public async Task> ReadAsync(string fileName) + { + var lines = await ReadStringMatrixFromCsvAsync(fileName, false); + return MapTo(lines); + } + + public IList MapTo(IList> lines) + { + // first line is columnLineHeader!!!! + + var mapping = GetPropertyMapping(lines[0]); + CheckPropertyMapping(mapping); + + var list = new List(); + var first = true; + + foreach (var line in lines) + { + if (first) + { + first = false; + } + else + { + list.Add(Map(line, mapping)); + } + } + + return list; + } + + private void CheckPropertyMapping(ColumnMapping[] mapping) + { + var notConfigured = mapping.Where(m => !m.IsConfigured).ToList(); + if (notConfigured.Any()) + { + var columnList = string.Join(", ", notConfigured.Select(m => m.ColumnName)); + throw new ArgumentException($"Column cannot be mapped: {columnList}"); + } + + var notCanWrite = mapping.Where(x => x.IsMapped && !x.MapTo.CanWrite).ToList(); + if (notCanWrite.Any()) + { + var columnList = string.Join(", ", notCanWrite.Select(m => m.ColumnName)); + throw new ArgumentException($"Column is readonly: {columnList}"); + } + } + + protected virtual ColumnMapping[] GetPropertyMapping(IList columnNames) + { + return columnNames + .Select(GetColumnMapping) + .ToArray(); + } + + public Action ConfigureColumnMapping { get; set; } + + protected virtual ColumnMapping GetColumnMapping(string columnName) + { + var ignoreColumn = IgnoreColumns?.Contains(columnName, StringComparer.InvariantCultureIgnoreCase) ?? false; + var mapToColumn = MapColumns?.ContainsKey(columnName) ?? false ? MapColumns[columnName] : columnName; + + var columnMapping = new ColumnMapping + { + ColumnName = columnName, + Ignore = ignoreColumn, + MapTo = ignoreColumn ? null : GetPropertyInfo(mapToColumn), + }; + + ConfigureColumnMapping?.Invoke(columnMapping); + return columnMapping; + } + + public static PropertyInfo GetPropertyInfo(string columnName) + { + return typeof(T).GetProperty(columnName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + } + + private T Map(IList line, ColumnMapping[] mapping) + { + var newT = new T(); + + var idx = 0; + foreach (var column in line) + { + AssignProperty(newT, column, mapping[idx++]); + } + + return newT; + } + +#pragma warning disable 8632 + private object GetValue(string valueAsString, Type type) +#pragma warning restore 8632 + { + if (type.IsGenericType && type.Name.StartsWith(@"Nullable")) + { + if (string.IsNullOrEmpty(valueAsString)) + { + return null; + } + + type = type.GenericTypeArguments[0]; + } + + if (type == typeof(string)) + { + return ExcelString(valueAsString); + } + else if (type == typeof(int)) + { + return ExcelInt(valueAsString); + } + else if (type == typeof(long)) + { + return ExcelLong(valueAsString); + } + else if (type == typeof(short)) + { + return ExcelShort(valueAsString); + } + else if (type == typeof(uint)) + { + return ExcelUInt(valueAsString); + } + else if (type == typeof(ulong)) + { + return ExcelULong(valueAsString); + } + else if (type == typeof(ushort)) + { + return ExcelUShort(valueAsString); + } + else if (type == typeof(decimal)) + { + return ExcelDecimal(valueAsString); + } + else if (type == typeof(byte)) + { + return ExcelByte(valueAsString); + } + else if (type == typeof(bool)) + { + return ExcelBool(valueAsString); + } + else if (type == typeof(DateTime)) + { + return ExcelDateOrDateTime(valueAsString); + } + else if (type == typeof(TimeSpan)) + { + return ExcelTimeSpan(valueAsString); + } + else if (type == typeof(double)) + { + return ExcelDouble(valueAsString); + } + else if (type.IsEnum) + { + return ExcelEnum(type, valueAsString); + } + else if (type == typeof(byte[])) + { + return ExcelImage(valueAsString); + } + + throw new NotImplementedException(); + } + + private void AssignProperty(object obj, string valueAsString, ColumnMapping mapping) + { + if (mapping.IsSetValue) + { + mapping.SetValue((T)obj, valueAsString); + } + else if (mapping.IsMapped) + { + var mapTo = mapping.MapTo; +#pragma warning disable 8632 + object val = mapping.GetValue != null + ? mapping.GetValue(valueAsString) + : GetValue(valueAsString, mapTo.PropertyType); +#pragma warning restore 8632 + + if (mapping.AdjustValue != null) + { + val = mapping.AdjustValue(val); + } + + mapTo.SetValue(obj, val); + } + } + } +} \ No newline at end of file diff --git a/Iot/Tools/CsvImportBase.cs b/Iot/Tools/CsvImportBase.cs new file mode 100644 index 0000000..af8a164 --- /dev/null +++ b/Iot/Tools/CsvImportBase.cs @@ -0,0 +1,410 @@ +/* + This file is part of https://github.com/aiten/Framework. + + Copyright (c) Herbert Aitenbichler + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +namespace Iot.Tools +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Text; + using System.Threading.Tasks; + + public class CsvImportBase + { + private readonly NumberFormatInfo _nfi; + + public Encoding Encoding { get; set; } = Encoding.Default; + + public string DateFormat { get; set; } = "yyyy/MM/dd"; + public string TimeFormat { get; set; } = "HH:mm:ss"; + public string Fraction3Format { get; set; } = ".fff"; + public string Fraction5Format { get; set; } = ".fffff"; + + public string DateTimeFormat => GetDateTimeFormat(DateFormat, TimeFormat); + public string DateTimeFraction1Format => GetDateTimeFormat(DateFormat, TimeFormat, Fraction3Format); + public string DateTimeFraction5Format => GetDateTimeFormat(DateFormat, TimeFormat, Fraction5Format); + + public string GetDateTimeFormat(string dateFormat, string timeFormat, string fractionFormat = null) => $"{dateFormat} {timeFormat}{fractionFormat ?? ""}"; + + public event EventHandler> ReadFirstLine; + + public char ListSeparatorChar { get; set; } = ';'; + + public string NewLineInString { get; set; } = "\n"; + + public CsvImportBase() + { + // Retrieve a writable NumberFormatInfo object. + var enUS = CultureInfo.CreateSpecificCulture("en-US"); + _nfi = enUS.NumberFormat; + } + + public NumberFormatInfo NumberFormat => _nfi; + + public void SetAustriaNumberFormat() + { + _nfi.NumberDecimalSeparator = ","; + _nfi.NumberGroupSeparator = "."; + } + + #region read + + public IList> ReadStringMatrixFromCsv(string[] lines, bool skipTitleLine) + { + var elements = new List>(); + var lineIdx = 0; + var readLineIdx = 0; + var compareLineIdx = skipTitleLine ? 1 : 0; + + while (true) + { + var row = ReadLine(() => + { + if (readLineIdx >= lines.Length) + { + return null; + } + + return lines[readLineIdx++]; + }); + + if (row == null) + { + break; + } + + if (lineIdx == 0) + { + ReadFirstLine?.Invoke(this, row); + } + + if (lineIdx >= compareLineIdx) + { + elements.Add(row); + } + + lineIdx++; + } + + return elements; + } + + public IList> ReadStringMatrixFromCsv(string fileName, bool skipTitleLine) + { + var lines = File.ReadAllLines(fileName, Encoding); + return ReadStringMatrixFromCsv(lines, skipTitleLine); + } + + public async Task>> ReadStringMatrixFromCsvAsync(string fileName, bool skipTitleLine) + { + var lines = await File.ReadAllLinesAsync(fileName, Encoding); + return ReadStringMatrixFromCsv(lines, skipTitleLine); + } + + private IList ReadLine(Func getNextLine) + { + var line = getNextLine(); + if (line == null) + { + return null; + } + + var columns = new List(); + var sb = new StringBuilder(line.Length); + var noQuoteChar = '\0'; + var quoteChar = noQuoteChar; + + while (true) + { + for (var idx = 0; idx < line.Length; idx++) + { + var ch = line[idx]; + + if (ch == quoteChar) + { + // end of " or "" + if (idx + 1 < line.Length && line[idx + 1] == quoteChar) + { + idx++; + sb.Append(ch); + } + else + { + quoteChar = noQuoteChar; + } + } + else if (quoteChar == noQuoteChar && ch == '"') + { + quoteChar = ch; + } + else if (quoteChar == noQuoteChar && ch == ListSeparatorChar) + { + columns.Add(sb.ToString()); + sb.Clear(); + } + + else if (quoteChar == noQuoteChar && ch == '|') + { + columns.Add(sb.ToString()); + sb.Clear(); + } + else + { + sb.Append(ch); + } + } + + if (quoteChar == noQuoteChar) + { + break; + } + + sb.Append(NewLineInString); + + line = getNextLine(); + } + + columns.Add(sb.ToString()); + + return columns; + } + + #endregion + + #region convert + + public string ExcelString(string excelField) + { + return excelField; + } + + public short ExcelShort(string excelField) + { + return short.Parse(excelField); + } + + public int ExcelInt(string excelField) + { + return int.Parse(excelField); + } + + public long ExcelLong(string excelField) + { + return long.Parse(excelField); + } + + public ushort ExcelUShort(string excelField) + { + return ushort.Parse(excelField); + } + + public uint ExcelUInt(string excelField) + { + return uint.Parse(excelField); + } + + public ulong ExcelULong(string excelField) + { + return ulong.Parse(excelField); + } + + public float ExcelFloat(string excelField) + { + return float.Parse(excelField, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands, _nfi); + } + + public byte ExcelByte(string excelField) + { + return byte.Parse(excelField); + } + + public bool ExcelBool(string excelField) + { + switch (excelField) + { + case @"0": + case @"false": + return false; + case @"1": + case @"true": + return true; + default: + throw new ArgumentOutOfRangeException(nameof(excelField), excelField, $@"cannot convert '{excelField}' to 'bool'."); + } + } + + public decimal ExcelDecimal(string excelField) + { + return decimal.Parse(excelField, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands, _nfi); + } + + public double ExcelDouble(string excelField) + { + return double.Parse(excelField, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands, _nfi); + } + + public DateTime ExcelDateOrDateTime(string excelField) + { + if (excelField.Length > DateFormat.Length) + { + return ExcelDateTime(excelField, DateFormat, TimeFormat, Fraction3Format, Fraction3Format); + } + + return ExcelDate(excelField, DateFormat); + } + + public DateTime ExcelDate(string excelField, string format) + { + try + { + return DateTime.ParseExact(excelField, format, CultureInfo.InvariantCulture); + } + catch (Exception) + { + throw; + } + } + + public DateTime ExcelDate(string excelField) + { + return ExcelDate(excelField, DateFormat); + } + + public DateTime ExcelDateDMY(string excelField) + { + // Parse date and time with custom specifier. + // e.g. string dateString = "19.01.2018"; + return ExcelDate(excelField, "dd.MM.yyyy"); + } + + public DateTime ExcelDateYMD(string excelField) + { + // Parse date and time with custom specifier. + // e.g. string dateString = "19.01.2018"; + return ExcelDate(excelField, "yyyy/MM/dd"); + } + + public object ExcelTimeSpan(string excelField) + { + try + { + var timeSpan = TimeSpan.Parse(excelField); + return timeSpan; + } + catch (Exception e) + { + throw new Exception(e.StackTrace); + } + } + + public DateTime ExcelDateTime(string excelField, string formatDate, string formatTime, string formatFraction3, string formatFraction5) + { + try + { + var fractionFormat = string.Empty; + + var dotIdx = excelField.LastIndexOf('.'); + if (dotIdx > formatDate.Length) + { + var fractionLength = excelField.Length - dotIdx - 1; + fractionFormat = fractionLength == 3 ? formatFraction3 : formatFraction5; + } + + return DateTime.ParseExact(excelField, GetDateTimeFormat(formatDate, formatTime, fractionFormat), CultureInfo.InvariantCulture); + } + catch (Exception) + { + throw; + } + } + + public DateTime ExcelDateTime(string excelField) + { + return ExcelDateTime(excelField, DateFormat, TimeFormat, Fraction3Format, Fraction5Format); + } + + public DateTime ExcelDateTimeYMD(string excelField) + { + return ExcelDateTime(excelField, "yyyy/MM/dd", TimeFormat, Fraction3Format, Fraction5Format); + } + + public object ExcelEnum(Type enumType, string excelField) + { + try + { + var enumValue = Enum.Parse(enumType, excelField); + return enumValue; + } + catch (Exception) + { + throw; + } + } + + public byte[] ExcelImage(string excelField) + { + byte[] bytes; + + if (excelField.StartsWith(@"0x")) + { + if (excelField.Length % 2 == 1) + { + throw new ArgumentException(@"string has odd length.", nameof(excelField)); + } + + int length = (excelField.Length - 2) / 2; + int chIdx = 2; + + bytes = new byte[length]; + + for (int i = 0; i < length; i++) + { + bytes[i] = (byte)(ToHex(excelField[chIdx]) * 16 + ToHex(excelField[chIdx + 1])); + chIdx += 2; + } + } + else + { + bytes = Convert.FromBase64String(excelField); + } + + return bytes; + } + + private int ToHex(char ch) + { + if (ch >= '0' && ch <= '9') + { + return ch - '0'; + } + + if (ch >= 'a' && ch <= 'f') + { + return 10 + ch - 'a'; + } + + if (ch >= 'A' && ch <= 'F') + { + return 10 + ch - 'F'; + } + + throw new ArgumentException(nameof(ch), $@"'{ch}' is not a hex digit."); + } + + #endregion + } +} \ No newline at end of file diff --git a/Measurement.csv b/Measurement.csv new file mode 100644 index 0000000..8d9f99b Binary files /dev/null and b/Measurement.csv differ diff --git a/MeasurementType.csv b/MeasurementType.csv new file mode 100644 index 0000000..f5b8ae2 Binary files /dev/null and b/MeasurementType.csv differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..a7bca64 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +"# 10-Iot + +## Lehrziele +- LINQ to Object +- Navigation Properties +- Wiederholung: Projektion, Join, GroupBy + +## Aufgabenstellung +In einem IoT Projekt werden Messdaten gesammelt und in einer Datenbank abgespeichert. Von dieser Datenbank sind CSV Exportdateien vorhanden: + +### Datenstruktur + +#### ActorType.csv +Kategorisierung der Aktoren. +- Id: Eindeutige ID +- Name: Bezeichnung der Aktorkategorie + +#### Actor.csv +Liste aller im System vorhandenen Aktoren. +- Id: Eindeutige ID +- Name: Bezeichnung des Aktors +- FirstSeen: Zeitpunkt der ersten Registrierung +- ActorTypeId: Beziehung zu den Aktoren-Kategorien + +#### MeasurementType.csv +Liste aller Messwert-Kategorien. +- Id: Eindeutige ID +- Name: Bezeichnung (z.B. "Temperature") +- Comment: Kommentar/Beschreibung + +#### Measurement.csv +Liste aller (historischen) Messwerte. +- Id: Eindeutige ID +- Time: Zeitpunkt der Messung +- ActorId: Zuordnung zu einem Aktor +- MeasurementTypeId: Zuordnung zu einer Messwert-Kategorie +- Value: Der gemessene Wert + +## Implementierung + +### Entitäten +Das Projekt enthält zwei Arten von Entitäten: + +1. **CsvEntities**: Einfache DTOs für den CSV-Import (ohne Beziehungen) +2. **Entities**: Domain-Entitäten mit Navigation Properties + +### Navigation Properties +Die Entities implementieren folgende Beziehungen: +- `ActorType` → `Actors` (1:n) +- `Actor` → `ActorType` (n:1) +- `Actor` → `Measurements` (1:n) +- `MeasurementType` → `Measurements` (1:n) +- `Measurement` → `Actor` (n:1) +- `Measurement` → `MeasurementType` (n:1) + +### LINQ Queries +Das Programm demonstriert verschiedene LINQ-Operationen: + +#### Report 1: Liste aller Aktoren mit Anzahl der Messungen +- **Verwendete LINQ-Operationen**: Select, Projection, OrderBy + +#### Report 2: Liste aller Messwert-Typen mit Anzahl +- **Verwendete LINQ-Operationen**: Select, Projection, OrderBy + +#### Report 3: Aktoren nach Messwert-Typen gruppiert mit Statistiken +- **Verwendete LINQ-Operationen**: GroupBy, Select, Projection, Min, Max, Count, OrderBy +- Zeigt für jede Kombination von Aktor und Messwert-Typ: + - Anzahl der Messungen + - Minimaler Wert + - Maximaler Wert + +## Ausführung +```bash +dotnet run --project Iot/Iot.csproj +``` +" + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..747d7fe --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1768305791, + "narHash": "sha256-AIdl6WAn9aymeaH/NvBj0H9qM+XuAuYbGMZaP0zcXAQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1412caf7bf9e660f2f962917c14b1ea1c3bc695e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..a11d11d --- /dev/null +++ b/flake.nix @@ -0,0 +1,25 @@ +{ + description = "Dev shell with .NET 9"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + }; + in { + devShells.default = pkgs.mkShell { + packages = [ + pkgs.dotnet-sdk_9 + # alternativ/zusätzlich: + # pkgs.dotnetCorePackages.sdk_9_0 + ]; + }; + }); +} +