<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>C# Reflection and Attributes on Jeppe Spanggaard - Software Developer | .NET, Azure &amp; Microsoft 365</title><link>https://jeppe-spanggaard.dk/tags/reflection/</link><description>Recent content in C# Reflection and Attributes on Jeppe Spanggaard - Software Developer | .NET, Azure &amp; Microsoft 365</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Sun, 18 Feb 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://jeppe-spanggaard.dk/tags/reflection/index.xml" rel="self" type="application/rss+xml"/><item><title>Web Api logging attribute</title><link>https://jeppe-spanggaard.dk/blogs/logging-before-execution-w-attributes/</link><pubDate>Sun, 18 Feb 2024 00:00:00 +0000</pubDate><author>Jeppe</author><guid>https://jeppe-spanggaard.dk/blogs/logging-before-execution-w-attributes/</guid><description>Implement logging only with a attribute on each method in a controller</description><content:encoded><![CDATA[<h2 id="have-logging-ever-irritated-you">Have logging ever irritated you?</h2>
<p>If yes, then you&rsquo;re definitely not alone! Often when I work with Web APIs in C#, logging tends to take up a lot of space, making the code quickly look &ldquo;ugly.&rdquo; I like to log what parameters the method is called with, which controller the method is in, etc. Therefore, a method often looks like this for me, in a simplified way:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#a6e22e">[HttpGet(&#34;{id}&#34;)]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> ActionResult&lt;WeatherForecast&gt; Get(<span style="color:#66d9ef">int</span> id)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    _logger.LogInformation(<span style="color:#e6db74">$&#34;Starting to find weather forecast with ID: {id}&#34;</span>);
</span></span><span style="display:flex;"><span>    { ... }
</span></span><span style="display:flex;"><span>    _logger.LogInformation(<span style="color:#e6db74">$&#34;Weather forecast with id {id} is fetched&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">new</span> WeatherForecast();
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>Output</strong></p>
<pre tabindex="0"><code>Starting to find weather forecast with ID: 234
Weather forecast with id 234 is fetched
</code></pre><p>I definitely don&rsquo;t find it visually appealing, and it contributes to my classes sometimes becoming extremely long. Therefore, I&rsquo;ve been thinking that it should be possible to make it easier and also more elegant. It got me thinking about how you can do so many cool things with custom attributes, so why not create some nice logging with an attribute?</p>
<p>And, as so many times before, almost anything is possible with C#; it&rsquo;s mainly just a matter of having the idea for it. So what I&rsquo;ve come up with is just an attribute that inherits from IActionFilter, since IActionFilter already implements OnActionExecuted and OnActionExecuting.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#a6e22e">[ActionLogger]</span> <span style="color:#75715e">// &lt;-- Custom attribute</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">[HttpGet(&#34;{id}&#34;)]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> ActionResult&lt;WeatherForecast&gt; Get(<span style="color:#66d9ef">int</span> id)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">new</span> WeatherForecast();
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>Output</strong></p>
<pre tabindex="0"><code>[GET][WeatherForecastController][/WeatherForecast/234] Executing with parameters: [id, 234]
[GET][WeatherForecastController][/WeatherForecast/234] Executed
</code></pre><h2 id="how-did-i-go-about-implementing-it-then">How did I go about implementing it then?</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">ActionLoggerAttribute</span> : Attribute, IActionFilter
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">void</span> OnActionExecuted(ActionExecutedContext context)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        ILogger? logger = GetLogger(context, context.Controller.GetType());
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> httpMethod = context.HttpContext.Request.Method;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> routeUrl = context.HttpContext.Request.Path;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> controllerName = context.Controller.GetType().Name;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (logger == <span style="color:#66d9ef">null</span>)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        logger.LogInformation(<span style="color:#e6db74">$&#34;[{httpMethod}][{controllerName}][{routeUrl}] Executed&#34;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">void</span> OnActionExecuting(ActionExecutingContext context)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        ILogger? logger = GetLogger(context, context.Controller.GetType());
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> parameters = context.ActionArguments;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> httpMethod = context.HttpContext.Request.Method;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> routeUrl = context.HttpContext.Request.Path;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> controllerName = context.Controller.GetType().Name;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (logger == <span style="color:#66d9ef">null</span>)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">string</span> baseMessage = <span style="color:#e6db74">$&#34;[{httpMethod}][{controllerName}][{routeUrl}] Executing&#34;</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">switch</span> (httpMethod)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">case</span> <span style="color:#e6db74">&#34;POST&#34;</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Get the type of body content</span>
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">var</span> bodyType = context.ActionDescriptor.Parameters
</span></span><span style="display:flex;"><span>                    .FirstOrDefault(p =&gt; p.BindingInfo?.BindingSource?.DisplayName == <span style="color:#e6db74">&#34;Body&#34;</span>)
</span></span><span style="display:flex;"><span>                    ?.Name;
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Get the value of bodytype</span>
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">var</span> bodyContent = bodyType != <span style="color:#66d9ef">null</span> ? JsonSerializer.Serialize(parameters[bodyType]) : <span style="color:#66d9ef">null</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> (bodyContent != <span style="color:#66d9ef">null</span>)
</span></span><span style="display:flex;"><span>                {
</span></span><span style="display:flex;"><span>                    logger.LogInformation(<span style="color:#e6db74">$&#34;{baseMessage} with body content: {bodyContent}&#34;</span>);
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                logger.LogInformation(<span style="color:#e6db74">$&#34;{baseMessage} without body content&#34;</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">case</span> <span style="color:#e6db74">&#34;GET&#34;</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> (parameters.Count &gt; <span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>                {
</span></span><span style="display:flex;"><span>                    logger.LogInformation(<span style="color:#e6db74">$&#34;{baseMessage} with parameters: {string.Join(&#34;</span>, <span style="color:#e6db74">&#34;, parameters)}&#34;</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                logger.LogInformation(<span style="color:#e6db74">$&#34;{baseMessage} without parameters&#34;</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>                logger.LogInformation(<span style="color:#e6db74">$&#34;{baseMessage} with parameters: {string.Join(&#34;</span>, <span style="color:#e6db74">&#34;, parameters)}&#34;</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> ILogger&lt;T&gt;? GetLogger&lt;T&gt;(FilterContext context, T type)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> (ILogger&lt;T&gt;?)context.HttpContext.RequestServices.GetService(<span style="color:#66d9ef">typeof</span>(ILogger&lt;T&gt;));
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>As mentioned, I simply created an attribute that inherits from IActionFilter, allowing me to override what should happen in OnActionExecuted and OnActionExecuting. In the specific code I&rsquo;ve written for my test to see what&rsquo;s possible, I might have gone a bit overkill – but then again, it&rsquo;s just to test the limits. I don&rsquo;t think in a &ldquo;real&rdquo; setup you&rsquo;d want to log the entire received request.</p>
<h2 id="best-practice">Best practice?</h2>
<p>I know it&rsquo;s not the best practice or the least resource-intensive way to log, but regardless, I&rsquo;m thinking of using this type of attribute in some future projects because it&rsquo;s easy, quick, and doesn&rsquo;t clutter the code. Often, for me, it&rsquo;s just as good, as it doesn&rsquo;t necessarily matter much for the solutions I create whether it takes 40ms or 80ms, but rather how quickly I can pinpoint the root cause when an error occurs.</p>
<p><a href="https://github.com/jeppesc11/WebApi-ActionLoggerAttribute">[Link to code]</a></p>
]]></content:encoded></item><item><title>SQL save changes w. trigger</title><link>https://jeppe-spanggaard.dk/blogs/trigger-in-mssql-for-version-history/</link><pubDate>Sat, 03 Feb 2024 00:00:00 +0000</pubDate><author>Jeppe</author><guid>https://jeppe-spanggaard.dk/blogs/trigger-in-mssql-for-version-history/</guid><description>Easiest way to keep track of which changes have been made to an object, and likewise, who has carried out these changes.</description><content:encoded><![CDATA[<p>The other day at work, I had to work on an old solution (some C# web API with an SQL) that a colleague had created a long time ago. Part of this solution was to create a kind of version history of objects and who had made the individual changes.</p>
<p>As I opened up the project, I saw some of the wildest code (in my opinion) I&rsquo;ve seen in a long time to solve an otherwise simple problem😮. Without going too deep into what was done, I can say that the person who created it was very fond of reflection in C#.</p>
<h2 id="how-would-i-have-done-it">How would I have done it?</h2>
<p>Since I&rsquo;ve worked a lot with SQL, it would be obvious for me to look at how to solve this directly in the database, rather than having to create a lot of generic C# code that almost no one would be able to read afterwards. So the obvious choice would be to create an SQL trigger on the exact table that should have a version history.</p>
<p><strong>So let&rsquo;s take an example</strong></p>
<p>Let&rsquo;s say we have a Person table like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> [dbo].[Persons](
</span></span><span style="display:flex;"><span>	[ID] [int] <span style="color:#66d9ef">IDENTITY</span>(<span style="color:#ae81ff">1</span>,<span style="color:#ae81ff">1</span>) <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Firstname] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Lastname] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Email] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Phone] [int] <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Address] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[ModifiedAt] [datetime] <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[ModifiedBy] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">CONSTRAINT</span> [PK_ID] <span style="color:#66d9ef">PRIMARY</span> <span style="color:#66d9ef">KEY</span> CLUSTERED 
</span></span><span style="display:flex;"><span> ( [ID] <span style="color:#66d9ef">ASC</span> )
</span></span><span style="display:flex;"><span>) <span style="color:#66d9ef">ON</span> [<span style="color:#66d9ef">PRIMARY</span>]
</span></span></code></pre></div><p>From this table, we want to log every time a change occurs, not just that a change happens, but also what individual value changes from and to. This should, of course, be stored in a table as well, and such a table could look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> [dbo].[Persons_Changes](
</span></span><span style="display:flex;"><span>	[TriggerID] [int] <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Firstname_Old] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Firstname_New] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Lastname_Old] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Lastname_New] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Email_Old] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Email_New] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Phone_Old] [int] <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Phone_New] [int] <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Address_Old] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[Address_New] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[ModifiedBy] [nvarchar](<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[ModifiedAt] [datetime] <span style="color:#66d9ef">NULL</span>
</span></span><span style="display:flex;"><span>) <span style="color:#66d9ef">ON</span> [<span style="color:#66d9ef">PRIMARY</span>]
</span></span></code></pre></div><p>Now for the fun part🎉 - namely, the logic that triggers each time a row in the Person table is updated. The trigger is designed to only save the values that are actually changed; the others that remain unchanged are simply set to NULL.</p>
<p><strong>Trigger code</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TRIGGER</span> TriggerPerson_AfterUpdate
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">ON</span> dbo.Persons
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">AFTER</span> <span style="color:#66d9ef">UPDATE</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">AS</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">BEGIN</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">DECLARE</span> <span style="color:#f92672">@</span>ColumnOrdinalTotal INT <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">SELECT</span> <span style="color:#f92672">@</span>ColumnOrdinalTotal <span style="color:#f92672">=</span> <span style="color:#f92672">@</span>ColumnOrdinalTotal 
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">+</span> POWER (
</span></span><span style="display:flex;"><span>                <span style="color:#ae81ff">2</span> 
</span></span><span style="display:flex;"><span>                , COLUMNPROPERTY(t.object_id,<span style="color:#66d9ef">c</span>.name,<span style="color:#e6db74">&#39;ColumnID&#39;</span>) <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>            )
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">FROM</span> sys.schemas s
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">INNER</span> <span style="color:#66d9ef">JOIN</span> sys.tables t <span style="color:#66d9ef">ON</span> s.schema_id <span style="color:#f92672">=</span> t.schema_id
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">INNER</span> <span style="color:#66d9ef">JOIN</span> sys.columns <span style="color:#66d9ef">c</span> <span style="color:#66d9ef">ON</span> t.object_id <span style="color:#f92672">=</span> <span style="color:#66d9ef">c</span>.object_id
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">WHERE</span> s.name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;dbo&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">AND</span> t.name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Persons&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">AND</span> <span style="color:#66d9ef">c</span>.name <span style="color:#66d9ef">IN</span> (
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;Firstname&#39;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#e6db74">&#39;Lastname&#39;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#e6db74">&#39;Email&#39;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#e6db74">&#39;Phone&#39;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#e6db74">&#39;Address&#39;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#e6db74">&#39;ModifiedAt&#39;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#e6db74">&#39;ModifiedBy&#39;</span>
</span></span><span style="display:flex;"><span>        );
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">IF</span> (COLUMNS_UPDATED() <span style="color:#f92672">&amp;</span> <span style="color:#f92672">@</span>ColumnOrdinalTotal) <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">BEGIN</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> dbo.Persons_Changes
</span></span><span style="display:flex;"><span>        (
</span></span><span style="display:flex;"><span>			dbo.Persons_Changes.TriggerID,
</span></span><span style="display:flex;"><span>			dbo.Persons_Changes.Firstname_Old,
</span></span><span style="display:flex;"><span>			dbo.Persons_Changes.Firstname_New,
</span></span><span style="display:flex;"><span>			dbo.Persons_Changes.Lastname_Old,
</span></span><span style="display:flex;"><span>			dbo.Persons_Changes.Lastname_New,
</span></span><span style="display:flex;"><span>			dbo.Persons_Changes.Email_Old,
</span></span><span style="display:flex;"><span>			dbo.Persons_Changes.Email_New,
</span></span><span style="display:flex;"><span>			dbo.Persons_Changes.ModifiedBy,
</span></span><span style="display:flex;"><span>			dbo.Persons_Changes.ModifiedAt
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">SELECT</span> d.ID
</span></span><span style="display:flex;"><span>            , <span style="color:#66d9ef">CASE</span> <span style="color:#66d9ef">WHEN</span> d.Firstname <span style="color:#f92672">=</span> i.Firstname <span style="color:#66d9ef">THEN</span> <span style="color:#66d9ef">NULL</span> <span style="color:#66d9ef">ELSE</span> d.Firstname <span style="color:#66d9ef">END</span>
</span></span><span style="display:flex;"><span>			, <span style="color:#66d9ef">CASE</span> <span style="color:#66d9ef">WHEN</span> d.Firstname <span style="color:#f92672">=</span> i.Firstname <span style="color:#66d9ef">THEN</span> <span style="color:#66d9ef">NULL</span> <span style="color:#66d9ef">ELSE</span> i.Firstname <span style="color:#66d9ef">END</span>
</span></span><span style="display:flex;"><span>			, <span style="color:#66d9ef">CASE</span> <span style="color:#66d9ef">WHEN</span> d.Lastname <span style="color:#f92672">=</span> i.Lastname <span style="color:#66d9ef">THEN</span> <span style="color:#66d9ef">NULL</span> <span style="color:#66d9ef">ELSE</span> d.Lastname <span style="color:#66d9ef">END</span>
</span></span><span style="display:flex;"><span>			, <span style="color:#66d9ef">CASE</span> <span style="color:#66d9ef">WHEN</span> d.Lastname <span style="color:#f92672">=</span> i.Lastname <span style="color:#66d9ef">THEN</span> <span style="color:#66d9ef">NULL</span> <span style="color:#66d9ef">ELSE</span> i.Lastname <span style="color:#66d9ef">END</span>
</span></span><span style="display:flex;"><span>			, <span style="color:#66d9ef">CASE</span> <span style="color:#66d9ef">WHEN</span> d.Email <span style="color:#f92672">=</span> i.Email <span style="color:#66d9ef">THEN</span> <span style="color:#66d9ef">NULL</span> <span style="color:#66d9ef">ELSE</span> d.Email <span style="color:#66d9ef">END</span>
</span></span><span style="display:flex;"><span>			, <span style="color:#66d9ef">CASE</span> <span style="color:#66d9ef">WHEN</span> d.Email <span style="color:#f92672">=</span> i.Email <span style="color:#66d9ef">THEN</span> <span style="color:#66d9ef">NULL</span> <span style="color:#66d9ef">ELSE</span> i.Email <span style="color:#66d9ef">END</span>
</span></span><span style="display:flex;"><span>			, i.ModifiedBy
</span></span><span style="display:flex;"><span>			, i.ModifiedAt
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">FROM</span> inserted i 
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">LEFT</span> <span style="color:#66d9ef">JOIN</span> deleted d <span style="color:#66d9ef">ON</span> i.ID <span style="color:#f92672">=</span> d.ID;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">END</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">END</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">GO</span>
</span></span></code></pre></div><p>Now there should be a row like these every time a Person is updated</p>
<table>
  <thead>
      <tr>
          <th>TriggerID</th>
          <th>Firstname_Old</th>
          <th>Firstname_New</th>
          <th>Lastname_Old</th>
          <th>Lastname_New</th>
          <th>&hellip;</th>
          <th>ModifiedBy</th>
          <th>ModifiedAt</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>Jeppe1</td>
          <td>Jeppe</td>
          <td>NULL</td>
          <td>NULL</td>
          <td>&hellip;</td>
          <td>JSC</td>
          <td>2024-02-03 11:30:19</td>
      </tr>
      <tr>
          <td>1</td>
          <td>NULL</td>
          <td>NULL</td>
          <td>Spanggaard</td>
          <td>Christensen</td>
          <td>&hellip;</td>
          <td>JSC</td>
          <td>2024-02-03 11:35:23</td>
      </tr>
  </tbody>
</table>
<p><strong>And now just for fun - how can this be used in C#?</strong>
First of all, we need to create the DTO classes, one for the Persons table and one for Persons_Changes. Unlike many, I hardly ever use scaffold with Entity Framework; I create the models myself.</p>
<p><strong>Persons class:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#a6e22e">    [Table(&#34;Persons&#34;, Schema = &#34;dbo&#34;)]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">internal</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Person</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;ID&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">int</span> Id { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Firstname&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> Firstname { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Lastname&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> Lastname { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Email&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> Email { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Phone&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">int?</span> Phone { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Address&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> Address { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;ModifiedAt&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> DateTime? ModifiedAt { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;ModifiedBy&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> ModifiedBy { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p><strong>Persons_Changes:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#a6e22e">    [Table(&#34;Persons_Changes&#34;, Schema = &#34;dbo&#34;)]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">internal</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">PersonChange</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;TriggerID&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">int</span> Id { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Firstname_Old&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> FirstnameOld { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Firstname_New&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> FirstnameNew { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Lastname_Old&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> LastnameOld { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Lastname_New&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> LastnameNew { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Email_Old&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> EmailOld { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Email_New&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> EmailNew { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Phone_Old&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">int?</span> PhoneOld { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Phone_New&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">int?</span> PhoneNew { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Address_Old&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> AddressOld { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;Address_New&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> AddressNew { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;ModifiedAt&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> DateTime? ModifiedAt { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">        [Column(&#34;ModifiedBy&#34;)]</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> ModifiedBy { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p><strong>DbContext:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span>    <span style="color:#66d9ef">internal</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">TestDbContext</span> : DbContext
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> DbSet&lt;Person&gt; Persons { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> DbSet&lt;PersonChange&gt; PersonChanges { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">protected</span> <span style="color:#66d9ef">override</span> <span style="color:#66d9ef">void</span> OnConfiguring(DbContextOptionsBuilder optionsBuilder)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            optionsBuilder.UseSqlServer(<span style="color:#e6db74">&#34;CONNECTION_STRING&#34;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">protected</span> <span style="color:#66d9ef">void</span> OnModelCreation(ModelBuilder modelBuilder)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">base</span>.OnModelCreating(modelBuilder);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>Once we have this in place, we can start playing with the data in C#, and for fun, I&rsquo;m thinking of creating a &rsquo;nice&rsquo; display of who and when the individual values were changed. And even though I started joking about reflection, I use it myself (when it makes sense)😜.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#66d9ef">internal</span> <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">string</span> FormatChange&lt;T&gt;(T changeObj)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        List&lt;Tuple&lt;<span style="color:#66d9ef">string</span>,<span style="color:#66d9ef">string?</span>,<span style="color:#66d9ef">string?</span>&gt;&gt; changes = <span style="color:#66d9ef">new</span> List&lt;Tuple&lt;<span style="color:#66d9ef">string</span>, <span style="color:#66d9ef">string?</span>, <span style="color:#66d9ef">string?</span>&gt;&gt;();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> properties = <span style="color:#66d9ef">typeof</span>(T).GetProperties();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> filteredProperties = properties
</span></span><span style="display:flex;"><span>            .Where(x =&gt; x.Name.Contains(<span style="color:#e6db74">&#34;Old&#34;</span>) || x.Name.Contains(<span style="color:#e6db74">&#34;New&#34;</span>));
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> distinctProperties = filteredProperties
</span></span><span style="display:flex;"><span>            .Select(x =&gt; x.Name.Substring(<span style="color:#ae81ff">0</span>, x.Name.Length - <span style="color:#ae81ff">3</span>))
</span></span><span style="display:flex;"><span>            .Distinct();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">foreach</span> (<span style="color:#66d9ef">var</span> property <span style="color:#66d9ef">in</span> distinctProperties)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">var</span> oldValueProperty = <span style="color:#66d9ef">typeof</span>(T).GetProperty(<span style="color:#e6db74">$&#34;{property}Old&#34;</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">var</span> newValueProperty = <span style="color:#66d9ef">typeof</span>(T).GetProperty(<span style="color:#e6db74">$&#34;{property}New&#34;</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (oldValueProperty != <span style="color:#66d9ef">null</span> &amp;&amp; newValueProperty != <span style="color:#66d9ef">null</span>)
</span></span><span style="display:flex;"><span>            {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">var</span> oldValue = oldValueProperty.GetValue(changeObj);
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">var</span> newValue = newValueProperty.GetValue(changeObj);
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> (oldValue != <span style="color:#66d9ef">null</span> &amp;&amp; newValue != <span style="color:#66d9ef">null</span>)
</span></span><span style="display:flex;"><span>                {
</span></span><span style="display:flex;"><span>                    changes.Add(
</span></span><span style="display:flex;"><span>                        <span style="color:#66d9ef">new</span> Tuple&lt;<span style="color:#66d9ef">string</span>, <span style="color:#66d9ef">string?</span>, <span style="color:#66d9ef">string?</span>&gt;(
</span></span><span style="display:flex;"><span>                            property, 
</span></span><span style="display:flex;"><span>                            oldValue?.ToString(), 
</span></span><span style="display:flex;"><span>                            newValue?.ToString()
</span></span><span style="display:flex;"><span>                        ));
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        StringBuilder sb = <span style="color:#66d9ef">new</span> StringBuilder();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">foreach</span> (<span style="color:#66d9ef">var</span> change <span style="color:#66d9ef">in</span> changes)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (change.Equals(changes.Last()))
</span></span><span style="display:flex;"><span>            {
</span></span><span style="display:flex;"><span>                sb.Append(<span style="color:#e6db74">$&#34;{change.Item1} from &#39;{change.Item2}&#39; to &#39;{change.Item3}&#39;&#34;</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            sb.Append(<span style="color:#e6db74">$&#34;{change.Item1} from &#39;{change.Item2}&#39; to &#39;{change.Item3}&#39; and &#34;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> sb.ToString();
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>When we bring it all together in a test application like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#66d9ef">static</span> <span style="color:#66d9ef">void</span> Main(<span style="color:#66d9ef">string</span>[] args)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    Console.WriteLine(<span style="color:#e6db74">&#34;Hello, World!&#34;</span>);
</span></span><span style="display:flex;"><span>    TestDbContext dbContext = <span style="color:#66d9ef">new</span> TestDbContext();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    List&lt;Person&gt; persons = dbContext.Persons.ToList();
</span></span><span style="display:flex;"><span>    Person person1 = persons.First();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    List&lt;PersonChange&gt; personChanges = dbContext.PersonChanges
</span></span><span style="display:flex;"><span>        .Where(x =&gt; x.Id == person1.Id)
</span></span><span style="display:flex;"><span>        .OrderBy(x =&gt; x.ModifiedAt)
</span></span><span style="display:flex;"><span>        .AsNoTracking()
</span></span><span style="display:flex;"><span>        .ToList();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">foreach</span> (PersonChange change <span style="color:#66d9ef">in</span> personChanges)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        Console.WriteLine(
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">$&#34;[{change.ModifiedAt}] {change.ModifiedBy} &#34;</span> +
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;updated the the properties &#34;</span> + 
</span></span><span style="display:flex;"><span>            FormatChange&lt;PersonChange&gt;(change));
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We get this output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span>[<span style="color:#ae81ff">2024</span><span style="color:#f92672">-</span><span style="color:#ae81ff">02</span><span style="color:#f92672">-</span><span style="color:#ae81ff">03</span> <span style="color:#ae81ff">11</span>:<span style="color:#ae81ff">30</span>:<span style="color:#ae81ff">19</span>] JSC updated the the properties Firstname <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;Jeppe1&#39;</span> <span style="color:#66d9ef">to</span> <span style="color:#e6db74">&#39;Jeppe&#39;</span>
</span></span><span style="display:flex;"><span>[<span style="color:#ae81ff">2024</span><span style="color:#f92672">-</span><span style="color:#ae81ff">02</span><span style="color:#f92672">-</span><span style="color:#ae81ff">03</span> <span style="color:#ae81ff">11</span>:<span style="color:#ae81ff">35</span>:<span style="color:#ae81ff">23</span>] JSC updated the the properties Lastname <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;Spanggaard&#39;</span> <span style="color:#66d9ef">to</span> <span style="color:#e6db74">&#39;Christensen&#39;</span>
</span></span></code></pre></div><h2 id="is-it-then-a-better-solution">Is it then a better solution?</h2>
<p>I can&rsquo;t answer that, but for me, it is - and that&rsquo;s because I believe something like this belongs in a database and not in code. Also, for me, it&rsquo;s much easier to read a trigger than a bunch of reflection code😏.</p>
]]></content:encoded></item><item><title>EF for SharePoint</title><link>https://jeppe-spanggaard.dk/blogs/entity-framework-for-sharepoint/</link><pubDate>Sun, 23 Jul 2023 00:00:00 +0000</pubDate><author>Jeppe</author><guid>https://jeppe-spanggaard.dk/blogs/entity-framework-for-sharepoint/</guid><description>Why and how? Wouldn’t it be great to have the ability to retrieve data from SharePoint in the same manner you are familiar with from Entity Framework? :face_with_monocle: At least that’s what I thought when I began working with SharePoint, having worked with EF for several years.
I thought there must be a way to retrieve data from SharePoint without having to work with dictionaries and create custom parsers to convert it into class models.</description><content:encoded><![CDATA[<h2 id="why-and-how">Why and how?</h2>
<p>Wouldn&rsquo;t it be great to have the ability to retrieve data from SharePoint in the same manner you are familiar with from Entity Framework? :face_with_monocle:
At least that&rsquo;s what I thought when I began working with SharePoint, having worked with EF for several years.</p>
<p>I thought there must be a way to retrieve data from SharePoint without having to work with dictionaries and create custom parsers to convert it into class models.</p>
<p>And then it hit me! Why not attempt to replicate the approach used by Entity Framework? EF is user-friendly, easily extensible, and does not require numerous custom parsers.</p>
<p>So that&rsquo;s why I&rsquo;ve started a Proof of Concept (POC), where I aim to implement the functionality from EF. I have made my repository public, so everyone is welcome to provide input and contribute ideas.</p>
<p><a href="https://github.com/jeppesc11/ListItemDynamicMapper">See GitHub repository</a></p>
<h2 id="how-does-it-work">How does it work?</h2>
<p>To make it work, you need to add a number of custom attributes to its class models.</p>
<ul>
<li>SPList</li>
<li>SPField</li>
</ul>
<p><strong>Example class</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#a6e22e">[SPList(listGuid: &#34;128b4e17-691a-4e28-9934-7bd399ba907a&#34;, listTitle: &#34;EmployeeTasks&#34;)]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">internal</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">EmployeeTaskModel</span> {
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">  [SPField(internalName: &#34;ID&#34;)]</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> Id { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">  [SPField(internalName: &#34;Title&#34;)]</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string?</span> Title { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">  [SPField(internalName: &#34;EmployeeTask_Done&#34;)]</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">bool?</span> Done { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">  [SPField(internalName: &#34;EmployeeTask_Employeer&#34;)]</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">public</span> FieldLookupValue? Employeer { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; }
</span></span><span style="display:flex;"><span>} 
</span></span></code></pre></div><p><strong>SPList</strong> - It should be thought of as similar to the Table-attribute (e.g [Table(&ldquo;StudentMaster&rdquo;)]) from EF, but it accepts both a GUID and a title. First, the code will attempt to find the list using the GUID, and if not found, it will try to find it using the title.</p>
<p><strong>SPField</strong> - It should be regarded as the Column-attribute from EF, and similarly, it accepts the internal name of the list field. This will be expanded significantly in the future. One of the ideas I have is to allow you to define whether it should be converted to an enum as well.</p>
<h2 id="how-to-use-it">How to use it</h2>
<p>To use this, it doesn&rsquo;t take much effort as it is neatly packed away. Therefore, only a few methods are exposed/displayed.
However, it must be noted that it is still only a POC, and therefore, not everything is thoroughly tested or handled for errors very gracefully.</p>
<p>A prerequisite for using this is to have the <a href="https://pnp.github.io/pnpcore/">PnP Core SDK</a> installed, as all extensions, etc., are built on PnP&rsquo;s web context and models.</p>
<h3 id="get-list-items">Get list items</h3>
<p>Normally, data would need to be extracted in the following way.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> list = context.Web.Lists.GetById(<span style="color:#66d9ef">new</span> Guid(...), p =&gt; p.Title, p =&gt; p.Items, p =&gt; p.Fields);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> listItems = list.Items.AsRequested();
</span></span></code></pre></div><p>Now, it is neatly packed away, so data is extracted in this manner. Unlike the previous method, this one parses the data to the specified class model.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span>IEnumerable&lt;EmployeeTaskModel&gt; items = context.Web.GetItems&lt;EmployeeTaskModel&gt;(p =&gt; ...);
</span></span></code></pre></div><h3 id="update-a-list-item">Update a list item</h3>
<p>Updating a list item can be done incredibly easily and quickly. Just take a look at this method:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#66d9ef">internal</span> EmployeeTaskModel UpdateEmployeeTask(EmployeeTaskModel employeeTask){
</span></span><span style="display:flex;"><span>  EmployeeTaskModel updatedTask = context.Web.UpdateItem(employeeTask);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> updatedTask;
</span></span><span style="display:flex;"><span>} 
</span></span></code></pre></div><h2 id="to-summarize">To summarize</h2>
<p>This POC was created to determine if it was feasible to extract data from SharePoint in an easy and efficient manner, similar to what we are accustomed to with EF.</p>
<p>I will continue working on this POC with the hope that one day it will evolve beyond just a proof of concept. Perhaps, in the future, it could become a part of the PnP Core SDK? :star_struck:</p>
]]></content:encoded></item></channel></rss>