Skip to content

Commit

Permalink
Temporarily add CESMII lib to project until new NuGet is released and…
Browse files Browse the repository at this point in the history
… format serialized output of type and structure fields for new type-released REST interface methods.
  • Loading branch information
barnstee committed Mar 5, 2025
1 parent a59357c commit 3645095
Show file tree
Hide file tree
Showing 6 changed files with 602 additions and 10 deletions.
31 changes: 31 additions & 0 deletions CESMII.OpcUa.NodeSetModel.EF/CESMII.OpcUa.NodeSetModel.EF.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net9.0</TargetFrameworks>
<Configurations>Debug;Release;Staging</Configurations>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageIcon>cesmii.png</PackageIcon>
<Version>2.1.2</Version>
<Authors>Markus Horstmann</Authors>
<Company>CESMII/C-Labs</Company>
<Product></Product>
<NeutralLanguage>en</NeutralLanguage>
<Description>OPC UA Node Set Model mapping for Entity Framework</Description>
<Copyright>Copyright © 2022-2024 CESMII. All rights reserved.</Copyright>
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CESMII.OpcUa.NodeSetModel" Version="2.1.2" />
<PackageReference Include="CESMII.OpcUa.NodeSetModel.Factory.Opc" Version="2.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.2" />
</ItemGroup>

<ItemGroup>
<PackageReference Update="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageReference Update="Nerdbank.GitVersioning" Version="3.7.115" />
</ItemGroup>

</Project>
178 changes: 178 additions & 0 deletions CESMII.OpcUa.NodeSetModel.EF/DbOpcUaContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/* ========================================================================
* Copyright (c) 2005-2022 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* 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.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CESMII.OpcUa.NodeSetModel;
using CESMII.OpcUa.NodeSetModel.Factory.Opc;
using CESMII.OpcUa.NodeSetModel.Opc.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Export;

namespace CESMII.OpcUa.NodeSetModel.EF
{
public class DbOpcUaContext : DefaultOpcUaContext
{
protected DbContext _dbContext;
protected Func<ModelTableEntry, NodeSetModel> _nodeSetFactory;
protected List<(string ModelUri, DateTime? PublicationDate)> _namespacesInDb;

public DbOpcUaContext(DbContext appDbContext, ILogger logger, Func<ModelTableEntry, NodeSetModel> nodeSetFactory = null)
: base(logger)
{
this._dbContext = appDbContext;
this._nodeSetFactory = nodeSetFactory;
// Get all namespaces with at least one node: used for avoiding DB lookups
this._namespacesInDb = _dbContext.Set<NodeModel>().Select(nm => new { nm.NodeSet.ModelUri, nm.NodeSet.PublicationDate }).Distinct().AsEnumerable().Select(n => (n.ModelUri, n.PublicationDate)).ToList();
}
public DbOpcUaContext(DbContext appDbContext, SystemContext systemContext, NodeStateCollection importedNodes, Dictionary<string, NodeSetModel> nodesetModels, ILogger logger, Func<ModelTableEntry, NodeSetModel> nodeSetFactory = null)
: base(systemContext, importedNodes, nodesetModels, logger)
{
this._dbContext = appDbContext;
this._nodeSetFactory = nodeSetFactory;
}

public override TNodeModel GetModelForNode<TNodeModel>(string nodeId)
{
var model = base.GetModelForNode<TNodeModel>(nodeId);
if (model != null) return model;

var uaNamespace = NodeModelUtils.GetNamespaceFromNodeId(nodeId);
NodeModel nodeModelDb;
if (_nodesetModels.TryGetValue(uaNamespace, out var nodeSet))
{
if (!_namespacesInDb.Contains((nodeSet.ModelUri, nodeSet.PublicationDate)))
{
// namespace was not in DB when the context was created: assume it's being imported
return null;
}
else
{
// Preexisting namespace: find an entity if already in EF cache
int retryCount = 0;
bool lookedUp = false;
do
{
try
{
nodeModelDb = _dbContext.Set<NodeModel>().Local.FirstOrDefault(nm => nm.NodeId == nodeId && nm.NodeSet.ModelUri == nodeSet.ModelUri && nm.NodeSet.PublicationDate == nodeSet.PublicationDate);
lookedUp = true;
}
catch (InvalidOperationException)
{
// re-try in case the NodeSet access caused a database query that modified the local cache
nodeModelDb = null;
}
retryCount++;
} while (!lookedUp && retryCount < 100);
if (nodeModelDb == null)
{
// Not in EF cache: assume it's in the database and attach a proxy with just primary key values
// This avoids a database lookup for each referenced node (or the need to pre-fetch all nodes in the EF cache)
nodeModelDb = _dbContext.CreateProxy<TNodeModel>(nm =>
{
nm.NodeSet = nodeSet;
nm.NodeId = nodeId;
}
);
_dbContext.Attach(nodeModelDb);
}
}
nodeModelDb?.NodeSet.AllNodesByNodeId.Add(nodeModelDb.NodeId, nodeModelDb);
}
else
{
nodeModelDb = _dbContext.Set<NodeModel>().FirstOrDefault(nm => nm.NodeId == nodeId && nm.NodeSet.ModelUri == uaNamespace);
if (nodeModelDb != null)
{
nodeSet = GetOrAddNodesetModel(new ModelTableEntry { ModelUri = nodeModelDb.NodeSet.ModelUri, PublicationDate = nodeModelDb.NodeSet.PublicationDate ?? DateTime.MinValue, PublicationDateSpecified = nodeModelDb.NodeSet.PublicationDate != null });
nodeModelDb?.NodeSet.AllNodesByNodeId.Add(nodeModelDb.NodeId, nodeModelDb);
}
}
if (!(nodeModelDb is TNodeModel))
{
_logger.LogWarning($"Nodemodel {nodeModelDb} is of type {nodeModelDb.GetType()} when type {typeof(TNodeModel)} was requested. Returning null.");
}
return nodeModelDb as TNodeModel;
}

public override NodeSetModel GetOrAddNodesetModel(ModelTableEntry model, bool createNew = true)
{
if (!_nodesetModels.TryGetValue(model.ModelUri, out var nodesetModel))
{
var existingNodeSet = GetMatchingOrHigherNodeSetAsync(model.ModelUri, model.GetNormalizedPublicationDate(), model.Version).Result;
if (existingNodeSet != null)
{
_nodesetModels.Add(existingNodeSet.ModelUri, existingNodeSet);
nodesetModel = existingNodeSet;
}
}
if (nodesetModel == null && createNew)
{
if (_nodeSetFactory == null)
{
nodesetModel = base.GetOrAddNodesetModel(model, createNew);
if (nodesetModel.PublicationDate == null)
{
// Primary Key value can not be null
nodesetModel.PublicationDate = DateTime.MinValue;
}
}
else
{
nodesetModel = _nodeSetFactory.Invoke(model);
if (nodesetModel != null)
{
if (nodesetModel.ModelUri != model.ModelUri)
{
throw new ArgumentException($"Created mismatching nodeset: expected {model.ModelUri} created {nodesetModel.ModelUri}");
}
_nodesetModels.Add(nodesetModel.ModelUri, nodesetModel);
}
}
}
return nodesetModel;
}

public Task<NodeSetModel> GetMatchingOrHigherNodeSetAsync(string modelUri, DateTime? publicationDate, string version)
{
return GetMatchingOrHigherNodeSetAsync(_dbContext, modelUri, publicationDate, version);
}
public static async Task<NodeSetModel> GetMatchingOrHigherNodeSetAsync(DbContext dbContext, string modelUri, DateTime? publicationDate, string version)
{
var matchingNodeSets = await dbContext.Set<NodeSetModel>()
.Where(nsm => nsm.ModelUri == modelUri).ToListAsync();
return NodeSetVersionUtils.GetMatchingOrHigherNodeSet(matchingNodeSets, publicationDate, version);
}
}
}
Loading

0 comments on commit 3645095

Please sign in to comment.