Мы используем платформа без кода, где она похожа на дизайнер рабочих процессов Elsa — у вас есть различные узлы, представляющие определенные функции, и вы соединяете узлы вместе, чтобы заставить их выполнять определенные действия, что может быть так же просто, как взять файл и поместить его в другое место. Он также похож на Power Automate от Microsoft.
Платформа написана на C#, и каждый узел рабочего процесса представляет собой сборку класса, упакованную в NuGet. Этот процесс выглядит так: мы, разработчики, создаем класс C# с определенной функциональностью, собираем и упаковываем код в пакет NuGet, а затем загружаем пакет в частный репозиторий NuGet платформы без кода. Пользователи, которые позже захотят использовать нашу функциональность, просто вставляют узел, и платформа загружает NuGet, устанавливает зависимости, запускает Initialize() для класса, а затем остается открытой, слушая триггерные сигналы от других узлов.
Узел, который у меня сейчас есть, отвечает за получение информации из API и использование sqlite для кэширования результатов (назовем его №1). Я могу использовать один или два таких узла в одном рабочем процессе (процессе), и это работает хорошо. Проблема начала возникать, когда я загрузил другой узел (# 2), он работает так же, но вызывает другой API. Что платформа делает с зависимостями, так это то, что для каждого типа узла она устанавливает зависимости один раз, а затем повторно использует их для других узлов того же типа. Это то же самое, что загрузить класс с отражением один раз, а затем вызвать его много раз. Поскольку узел №2 был введен в рабочий процесс, зависимости внедряются дважды (у меня нет доступа к коду платформы, поэтому все, что я пишу о том, как работает платформа, основано на тщательных наблюдениях), во время «загрузки» Microsoft.Data.SQLite< /code> зависимость. Я вижу следующую ошибку:
Невозможно загрузить DLL «e_sqlite3» или одну из ее зависимостей: указанный модуль не найден. . (0x8007007E)
Метод Initialize(), о котором я упоминал ранее, запускается для каждого узла (подумайте об узле как об объекте, который НЕ static), я не заметил проблем, когда он вызывается дважды (это происходит, когда есть два узла №1).
Платформа и узлы работают в NET 6.
Код: Выделить всё
Microsoft.Data.SQLiteЯ подозреваю, что проблема может заключаться в том, что во время загрузки пакета происходит взаимодействие с неуправляемым кодом (бинарные файлы SQLitePCL.raw?), который не может обработать этот случай. , но я не уверен.
Ниже приведен код одного из узлов, для второго узла вызов API существенно отличается. Некоторые ненужные части вырезаны.
Код: Выделить всё
[Export(typeof(ICustomProcessAdapter))]
public class Startup : CustomProcessAdapter
{
private bool _isDisposed;
private HttpClient _httpClient;
private SqliteConnection _sqliteConnection;
private bool _useCache;
private string _authKey;
public override async Task InitializeAsync()
{
if (!IsInitialized)
{
NodeParams p = NodeParams.Extract(NodeParamList); // User defined settings
// Validate URL
if (string.IsNullOrWhiteSpace(p.EndpointUrl) || !Uri.TryCreate(p.EndpointUrl, UriKind.Absolute, out var _))
{
throw new ArgumentException("Invalid Endpoint URL.");
}
// Validate Authentication key
if (string.IsNullOrWhiteSpace(p.AuthenticationKey))
{
throw new ArgumentException("Invalid Authentication key.");
}
_authKey = XmlEncodeValue(p.AuthenticationKey);
_httpClient = new HttpClient()
{
DefaultRequestHeaders =
{
{ "Accept", "application/xml" }
}
};
if (!string.IsNullOrWhiteSpace(p.CachePath))
{
if (string.Equals(p.CachePath, ":memory:", StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("In-memory database is not supported.");
var filePathSanitized = System.IO.Path.GetFullPath(p.CachePath);
// Initialize SQLite table if it does not exist
SQLitePCL.Batteries_V2.Init();
_sqliteConnection = new($"Data Source={filePathSanitized}");
bool dbFileExists = System.IO.File.Exists(filePathSanitized);
await _sqliteConnection.OpenAsync();
await using (var cmd = _sqliteConnection.CreateCommand())
{
cmd.CommandText = "CREATE TABLE IF NOT EXISTS cache (location_name TEXT PRIMARY KEY, location_signature TEXT, expires_at TEXT)";
await cmd.ExecuteNonQueryAsync();
}
_useCache = true;
if (p.WriteAheadLogging && !dbFileExists)
{
await using var cmd = _sqliteConnection.CreateCommand();
cmd.CommandText = "PRAGMA journal_mode=WAL";
await cmd.ExecuteNonQueryAsync();
}
if (p.InvalidateCache)
{
await using var cmd = _sqliteConnection.CreateCommand();
cmd.CommandText = "DELETE FROM cache";
await cmd.ExecuteNonQueryAsync();
}
}
}
return await base.InitializeAsync();
}
protected override void Dispose(bool disposing)
{
if (_isDisposed)
{
return;
}
if (disposing)
{
_httpClient?.Dispose();
_sqliteConnection?.Dispose();
}
base.Dispose(disposing);
_isDisposed = true;
}
public override async Task[*]> ProcessMessageAsync(AdapterMessage message)
{
try
{
Tracer.WriteBegin();
// ...
// Check cache
bool cacheExpired = false;
string expiredCacheValue = string.Empty;
if (_useCache)
{
// Reconnect if connection broke
if (_sqliteConnection.State == System.Data.ConnectionState.Closed)
{
await _sqliteConnection.OpenAsync();
}
await using var cmd = _sqliteConnection.CreateCommand();
cmd.CommandText = "SELECT location_signature, expires_at FROM cache WHERE location_name = @location_name";
cmd.Parameters.AddWithValue("@location_name", p.LocationName);
await using var reader = await cmd.ExecuteReaderAsync();
if (await reader.ReadAsync())
{
if (!await reader.IsDBNullAsync(1))
{
var expiresAt = DateTime.ParseExact(reader.GetString(1), "yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);
if (expiresAt > DateTime.UtcNow)
{
// ...
}
else
{
cacheExpired = true;
if (p.ReuseExpiredCache && !await reader.IsDBNullAsync(0))
expiredCacheValue = reader.GetString(0);
}
}
else
{
/ ...
}
}
}
// Call API
// ...
// Parse Location signature
// Use XmlReader.
await using var xmlStream = await response.Content.ReadAsStreamAsync();
using var xmlReader = XmlReader.Create(xmlStream, new() { Async = true });
var locationSignature = string.Empty;
if (xmlReader.ReadToFollowing("LocationSignature"))
locationSignature = await xmlReader.ReadElementContentAsStringAsync();
// Cache asset number
if (_useCache)
{
await using var cmd = _sqliteConnection.CreateCommand();
cmd.CommandText = "INSERT OR REPLACE INTO cache (location_name, location_signature, expires_at) VALUES (@location_name, @location_signature, @expires_at)";
cmd.Parameters.AddWithValue("@location_name", p.LocationName);
cmd.Parameters.AddWithValue("@location_signature", string.IsNullOrEmpty(locationSignature) ? DBNull.Value : locationSignature);
var expiresAt = string.IsNullOrEmpty(locationSignature) ? DateTime.UtcNow.AddMinutes(p.CacheNullTime) : DateTime.UtcNow.AddMinutes(p.CacheTime);
cmd.Parameters.AddWithValue("@expires_at", expiresAt.ToString("yyyy-MM-ddTHH:mm:ssZ"));
await cmd.ExecuteNonQueryAsync();
}
// ...
}
catch (Exception ex)
{
Tracer.WriteException(ex);
throw;
}
finally
{
Tracer.WriteEnd();
}
}
private static async Task CreateAPIException(HttpResponseMessage response)
{
return new APIException(
$@"API call failed
Status code: {response.StatusCode}
Reason: {response.ReasonPhrase},
Response content: {await response.Content.ReadAsStringAsync()}");
}
private static string XmlEncodeValue(string value)
{
return System.Security.SecurityElement.Escape(value);
}
}
- Укажите
- Укажите
- Вызовите SQLitePCL.Batteries.Init()
- Вызовите SQLitePCL.Batteries_V2. Init()
- Проверено, совпадают ли версии зависимостей.
- Пыталась сослаться на последнюю версию пакета sqlite3.
< li>Пытался упаковать «отсутствующий» .dll в NuGet.
Подробнее здесь: https://stackoverflow.com/questions/790 ... e-platform
Мобильная версия