-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Temporarily add CESMII lib to project until new NuGet is released and…
… format serialized output of type and structure fields for new type-released REST interface methods.
- Loading branch information
Showing
6 changed files
with
602 additions
and
10 deletions.
There are no files selected for viewing
31 changes: 31 additions & 0 deletions
31
CESMII.OpcUa.NodeSetModel.EF/CESMII.OpcUa.NodeSetModel.EF.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
Oops, something went wrong.