Consider the following situation:
Now, I have a C# application that parses a file in order to get details (tables, columns etc) and starts a new SQL Connection in order to execute a SQL Command to create those tables in the database.
What I want is to create a SQL Project in which I will manually create those tables, and from the C# application I want to programatically publish the SQL project to a certain server and database.
Is this possible ?
If you are using a sqlproj based project in .NET 4 and above, you can build and publish it programatically fairly easily using classes in the Microsoft.Build namespace. Taken from my answer here:
using Microsoft.Build.Framework;
using Microsoft.Build.Execution;
public void UpdateSchema() {
var props = new Dictionary<string, string> {
{ "UpdateDatabase", "True" },
{ "PublishScriptFileName", "schema-update.sql" },
{ "SqlPublishProfilePath", "path/to/publish.xml") }
};
var projPath = "path/to/database.sqlproj";
var result = BuildManager.DefaultBuildManager.Build(
new BuildParameters { Loggers = new[] { new ConsoleLogger() } },
new BuildRequestData(new ProjectInstance(projPath, props, null), new[] { "Publish" }));
if (result.OverallResult == BuildResultCode.Success) {
Console.WriteLine("Schema update succeeded!");
}
else {
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Schema update failed!");
Console.ResetColor();
}
}
private class ConsoleLogger : ILogger
{
public void Initialize(IEventSource eventSource) {
eventSource.ErrorRaised += (sender, e) => {
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(e.Message);
Console.ResetColor();
};
eventSource.MessageRaised += (sender, e) => {
if (e.Importance != MessageImportance.Low)
Console.WriteLine(e.Message);
};
}
public void Shutdown() { }
public LoggerVerbosity Verbosity { get; set; }
public string Parameters { get; set; }
}
This is for .NET 4 and above. Be sure and include assembly references to Microsoft.Build and Microsoft.Build.Framework.
There are a number of ways you could accomplish this. In our app (~ 7 large databases) we manage them all with Database Projects from SQL Server Data Tools. This has allowed us to version control easily as well as do some awesome comparison tools at deploy time and a plethora of other options. We did expand ours to deal with some nuances in our environment but for most people that shouldn't be an issue.
Part of that toolset includes DAC (Data Tier Applications) which allow you to transplant a database that is in your project to various environments pretty easily. This would support a great majority of projects in existence today.
If you wanted to go pure programatic you could use Code First (and Code First Migrations which is pretty slick) which is kind of build as you go and MS will figure out the rest for you (mainly by convention but flexibility to go beyond that). It's really friendly when it comes to upgrading versions. Again IMHO.
Database Projects exist as well but tend to require a little more insight/work to get them tweaked the way you want (but also offer a familar SQL Explorer type layout).
Related
I have a site that uses Angular on the frontend and C# on the backend. I have a c# class that obtains data from a database. I have a new field (spanish translation of the value)I want to add, and am not sure which of the following 3 scenarios would be optimal.
This is what the code currently looks like:
public class CustomerIssueManager
{
static public List<GenericRecordDTO> GetCustomerIssues(MyStore db)
{
List<GenericRecordDTO> responseDTO = new List<GenericRecordDTO>();
try
{
var customerIssues = db.CustomerIssues.Where(m => m.IsActive && m.Name.ToLower() != "none").OrderBy(o => o.Name).ToList();
foreach (var c in customerIssues)
{
GenericRecordDTO issue = new GenericRecordDTO();
issue.ID = c.ID;
issue.Value = c.Name;
responseDTO.Add(issue);
}
}
}
}
public class GenericRecordDTO
{
public int ID { get; set; }
public string Value {get; set; } = "";
}
These are the scenarios I am considering:
Scenario 1:
Is there any significant difference between adding the following to GenericRecordDTO:
public string SpanishValue { get; set; } = "";
Ramifications: There are many classes that use GenericRecordDTO that won't need SpanishValue
Scenario 2:
As opposed to creating another class:
public class GenericRecordTransDTO
and adding SpanishValue to it?
Solves the problem from scenario 1
Scenario 3:
As opposed to keeping GenericRecordDTO.cs as is, but in CustomerIssueManager.cs setting a conditional for spanish site:
if(englishSite) { issue.Value = c.Name; }
else { issue.Value = c.SpanishName; }
Not sure if I can check for Spanish site at this point or if I need to do it later? And are there any drawbacks to checking spanish site here vs later in the TypeScript/Angular?
My question would be, why do this on the front end at all? This requires you to handle localization at two levels, while also sending extra data back to the browser that in many cases you do not need.
For me, I would decide the language server side and load content appropriately. If it is a Spanish client requesting the data, then the Name field would be the Spanish name, English user then an English name, etc ... This has the benefit of handling all of the localization on the server side, at least as far as strings are concerned.
However, it is important to understand, localization is more than just text, so you may still need client side formatting for numbers, dates, etc ... For this, I would recommend using a formatting library that takes locale information, then deliver this information from the server via a configuration payload.
As far as detection of the language server side, this could be done quite a few ways. For example:
You could send an explicit header or variable with your requests instructing it which language to use.
You could use the Accept-Language header sent by the browser to decide which language you would like to use.
You could make it a configuration option that the user can set in their profile, and check a database record for the active user to decide which language you would like to use.
I have a console application with some arguments and options so I would like to use a free third-party library.
I have found two libraries for this purpose: NDesk.Options and Command Line Parser Library
Finally I have decided to use Command Line Parser Library because it is clearer using properties so I have downloaded it and added a reference to it.
The problem is that when adding the reference to my .NET Framework 3.5 project I get a warning icon. From the above page where I have downloaded it, it says that compatibility is .NET Framework 3.5+ so I understand 3.5 is compatible, am I right? If not which previous version of it is compatible with .NET Framework 3.5?
You can also use the new Microsoft CommandLineUtils library.
The nuget package is here, but only for .NET Core or Framrwork 4.5.2.
But you can download the source code (only 7 files) and include in your projet. For the Framework 3.5, you have only 2 compilation errors to solve: remove an extra method (using Tasks) and remove one line (in HandleUnexpectedArg).
Nuget: https://www.nuget.org/packages/Microsoft.Extensions.CommandLineUtils
Source: https://github.com/aspnet/Common/tree/dev/shared/Microsoft.Extensions.CommandLineUtils.Sources
To use this library, find here a first sample:
static void Main(string[] args)
{
var cmd = new CommandLineApplication();
var argAdd = cmd.Option("-a | --add <value>", "Add a new item", CommandOptionType.SingleValue);
cmd.OnExecute(() =>
{
Console.WriteLine(argAdd.Value());
return 0;
});
cmd.HelpOption("-? | -h | --help");
cmd.Execute(args);
}
I recommend FluentArgs (see: https://github.com/kutoga/FluentArgs). I think it is very easy to use:
namespace Example
{
using System;
using System.Threading.Tasks;
using FluentArgs;
public static class Program
{
public static Task Main(string[] args)
{
return FluentArgsBuilder.New()
.DefaultConfigsWithAppDescription("An app to convert png files to jpg files.")
.Parameter("-i", "--input")
.WithDescription("Input png file")
.WithExamples("input.png")
.IsRequired()
.Parameter("-o", "--output")
.WithDescription("Output jpg file")
.WithExamples("output.jpg")
.IsRequired()
.Parameter<ushort>("-q", "--quality")
.WithDescription("Quality of the conversion")
.WithValidation(n => n >= 0 && n <= 100)
.IsOptionalWithDefault(50)
.Call(quality => outputFile => inputFile =>
{
/* ... */
Console.WriteLine($"Convert {inputFile} to {outputFile} with quality {quality}...");
/* ... */
return Task.CompletedTask;
})
.ParseAsync(args);
}
}
}
There are many other examples on the github page.
McMaster.Extensions.CommandLineUtils is the best command line parser for c# that I've used. I especially like that it supports subcommands well.
Source code is here: https://github.com/natemcmaster/CommandLineUtils
dotnet add package McMaster.Extensions.CommandLineUtils
This is a simple example of how to use it using attributes:
using System;
using McMaster.Extensions.CommandLineUtils;
public class Program
{
public static int Main(string[] args)
=> CommandLineApplication.Execute<Program>(args);
[Option(Description = "The subject")]
public string Subject { get; } = "world";
[Option(ShortName = "n")]
public int Count { get; } = 1;
private void OnExecute()
{
for (var i = 0; i < Count; i++)
{
Console.WriteLine($"Hello {Subject}!");
}
}
}
Or you could use a builder:
using System;
using McMaster.Extensions.CommandLineUtils;
var app = new CommandLineApplication();
app.HelpOption();
var subject = app.Option("-s|--subject <SUBJECT>", "The subject", CommandOptionType.SingleValue);
subject.DefaultValue = "world";
var repeat = app.Option<int>("-n|--count <N>", "Repeat", CommandOptionType.SingleValue);
repeat.DefaultValue = 1;
app.OnExecute(() =>
{
for (var i = 0; i < repeat.ParsedValue; i++)
{
Console.WriteLine($"Hello {subject.Value()}!");
}
});
return app.Execute(args);
Microsoft have also been working on a command line parser: https://github.com/dotnet/command-line-api but it's been in preview for ages.
System.CommandLine might do the trick. Though as of November 2022 it is still in beta.
I suppose the .NET team is going to include it in some upcoming .NET framework release.
https://github.com/dotnet/runtime/issues/68578
https://www.nuget.org/packages/System.CommandLine
If you're looking for a third-party library to help you parse command-line arguments and options in C#, you might want to check out the TreeBasedCli library. It is a C# library designed to simplify the process of creating command-line interfaces (CLIs) with nested subcommands, and offers a number of benefits for both developers and users.
One of the key features of TreeBasedCli is its modular structure, which allows you to easily organize and structure your CLI's functionality using leaf and branch commands. Leaf commands represent specific actions that can be performed, and are implemented as individual classes with their own command definition, input parser, and asynchronous handler. Branch commands, on the other hand, represent a group of subcommands and do not have an associated action. This allows you to easily create complex CLIs with multiple levels of nesting.
Another benefit of TreeBasedCli is its support for asynchronous command execution. It also includes a lightweight Dependency Injection (DI) interface, allowing you to use your preferred method of DI type resolution.
public class CreateCatCommand :
LeafCommand<
CreateCatCommand.Arguments,
CreateCatCommand.Parser,
CreateCatCommand.Handler>
{
private const string NameLabel = "--name";
public CreateCatCommand() : base(
label: "create-cat",
description: new[]
{
"Prints out a cat."
},
options: new[]
{
new CommandOption(
label: NameLabel,
description: new[]
{
"Required. The name of the cat to print."
}
),
})
{ }
public record Arguments(string CatName) : IParsedCommandArguments;
public class Parser : ICommandArgumentParser<Arguments>
{
public IParseResult<Arguments> Parse(CommandArguments arguments)
{
string name = arguments.GetArgument(NameLabel).ExpectedAsSingleValue();
var result = new Arguments(
CatName: name
);
return new SuccessfulParseResult<Arguments>(result);
}
}
public class Handler : ILeafCommandHandler<Arguments>
{
private readonly IUserInterface userInterface;
public Handler(IUserInterface userInterface)
{
this.userInterface = userInterface;
}
public Task HandleAsync(Arguments arguments, LeafCommand _)
{
this.userInterface.WriteLine($"I am a cat 😸 with the name {arguments.CatName}!");
return Task.CompletedTask;
}
}
}
How does one get the results of a "Saved Search" of Type "Deleted Record" in NetSuite? Other search types are obvious(CustomerSearchAdvanced, ItemSearchAdvanced, etc...) but this one seems to have no reference online, just documentation around deleting records, not running saved searches on them.
Update 1
I should clarify a little bit more what I'm trying to do. In NetSuite you can run(and Save) Saved Search's on the record type "Deleted Record", I believe you are able to access at least 5 columns(excluding user defined ones) through this process from the web interface:
Date Deleted
Deleted By
Context
Record Type
Name
You are also able to setup search criteria as part of the "Saved Search". I would like to access a series of these "Saved Search's" already present in my system utilizing their already setup search criteria and retrieving data from all 5 of their displayed columns.
The Deleted Record record isn't supported in SuiteTalk as of version 2016_2 which means you can't run a Saved Search and pull down the results.
This is not uncommon when integrating with NetSuite. :(
What I've always done in these situations is create a RESTlet (NetSuite's wannabe RESTful API framework) SuiteScript that will run the search (or do whatever is possible with SuiteScript and not possible with SuiteTalk) and return the results.
From the documentation:
You can deploy server-side scripts that interact with NetSuite data
following RESTful principles. RESTlets extend the SuiteScript API to
allow custom integrations with NetSuite. Some benefits of using
RESTlets include the ability to:
Find opportunities to enhance usability and performance, by
implementing a RESTful integration that is more lightweight and
flexible than SOAP-based web services. Support stateless communication
between client and server. Control client and server implementation.
Use built-in authentication based on token or user credentials in the
HTTP header. Develop mobile clients on platforms such as iPhone and
Android. Integrate external Web-based applications such as Gmail or
Google Apps. Create backends for Suitelet-based user interfaces.
RESTlets offer ease of adoption for developers familiar with
SuiteScript and support more behaviors than NetSuite's SOAP-based web
services, which are limited to those defined as SuiteTalk operations.
RESTlets are also more secure than Suitelets, which are made available
to users without login. For a more detailed comparison, see RESTlets
vs. Other NetSuite Integration Options.
In your case this would be a near trivial script to create, it would gather the results and return JSON encoded (easiest) or whatever format you need.
You will likely spend more time getting the Token Based Authentication (TBA) working than you will writing the script.
[Update] Adding some code samples related to what I mentioned in the comments below:
Note that the SuiteTalk proxy object model is frustrating in that it
lacks inheritance that it could make such good use of. So you end with
code like your SafeTypeCastName(). Reflection is one of the best tools
in my toolbox when it comes to working with SuiteTalk proxies. For
example, all *RecordRef types have common fields/props so reflection
saves you type checking all over the place to work with the object you
suspect you have.
public static TType GetProperty<TType>(object record, string propertyID)
{
PropertyInfo pi = record.GetType().GetProperty(propertyID);
return (TType)pi.GetValue(record, null);
}
public static string GetInternalID(Record record)
{
return GetProperty<string>(record, "internalId");
}
public static string GetInternalID(BaseRef recordRef)
{
PropertyInfo pi = recordRef.GetType().GetProperty("internalId");
return (string)pi.GetValue(recordRef, null);
}
public static CustomFieldRef[] GetCustomFieldList(Record record)
{
return GetProperty<CustomFieldRef[]>(record, CustomFieldPropertyName);
}
Credit to #SteveK for both his revised and final answer. I think long term I'm going to have to implement what is suggested, short term I tried implementing his first solution("getDeleted") and I'd like to add some more detail on this in case anyone needs to use this method in the future:
//private NetSuiteService nsService = new DataCenterAwareNetSuiteService("login");
//private TokenPassport createTokenPassport() { ... }
private IEnumerable<DeletedRecord> DeletedRecordSearch()
{
List<DeletedRecord> results = new List<DeletedRecord>();
int totalPages = Int32.MaxValue;
int currentPage = 1;
while (currentPage <= totalPages)
{
//You may need to reauthenticate here
nsService.tokenPassport = createTokenPassport();
var queryResults = nsService.getDeleted(new GetDeletedFilter
{
//Add any filters here...
//Example
/*
deletedDate = new SearchDateField()
{
#operator = SearchDateFieldOperator.after,
operatorSpecified = true,
searchValue = DateTime.Now.AddDays(-49),
searchValueSpecified = true,
predefinedSearchValueSpecified = false,
searchValue2Specified = false
}
*/
}, currentPage);
currentPage++;
totalPages = queryResults.totalPages;
results.AddRange(queryResults.deletedRecordList);
}
return results;
}
private Tuple<string, string> SafeTypeCastName(
Dictionary<string, string> customList,
BaseRef input)
{
if (input.GetType() == typeof(RecordRef)) {
return new Tuple<string, string>(((RecordRef)input).name,
((RecordRef)input).type.ToString());
}
//Not sure why "Last Sales Activity Record" doesn't return a type...
else if (input.GetType() == typeof(CustomRecordRef)) {
return new Tuple<string, string>(((CustomRecordRef)input).name,
customList.ContainsKey(((CustomRecordRef)input).internalId) ?
customList[((CustomRecordRef)input).internalId] :
"Last Sales Activity Record"));
}
else {
return new Tuple<string, string>("", "");
}
}
public Dictionary<string, string> GetListCustomTypeName()
{
//You may need to reauthenticate here
nsService.tokenPassport = createTokenPassport();
return
nsService.search(new CustomListSearch())
.recordList.Select(a => (CustomList)a)
.ToDictionary(a => a.internalId, a => a.name);
}
//Main code starts here
var results = DeletedRecordSearch();
var customList = GetListCustomTypeName();
var demoResults = results.Select(a => new
{
DeletedDate = a.deletedDate,
Type = SafeTypeCastName(customList, a.record).Item2,
Name = SafeTypeCastName(customList, a.record).Item1
}).ToList();
I have to apply all the filters API side, and this only returns three columns:
Date Deleted
Record Type(Not formatted in the same way as the Web UI)
Name
I am new here and I hope that i will find a solution for my problem. The background of the problem is as follows:
I am trying to build an expert system that constitute a C# front-end which is interacting with Swi-prolog.
I have downloaded SwiPlCs.dll (A CSharp class library to connect .NET languages with Swi-Prolog)
And added a reference to it in a Visual Studio project(Win form app) that I have created to test if I can query prolog from c# (I followed the example used in the documentation found here).
It worked fine.
Then, in a more complicated scenario, I have built a WCF service that will act as an intermediary layer between Swi-Prolog and C# client application (it consumes the service).
The service is hosted in IIS 7.0.
For the sake of simplicity, lets say my service contains three methods.
The first method initializes the prolog engine, consults prolog source file then queries the file.
The second method performs another query.
The third method calls PlCleanup().
Method#1:
public void LaunchAssessment()
{
Dictionary<string, string> questions = new Dictionary<string, string>();
#region : Querying prolog using SwiPlCs
try
{
if (!PlEngine.IsInitialized)
{
String[] param = { "-q" };
PlEngine.Initialize(param);
PlQuery.PlCall("consult('D:/My FYP Work/initialAssessment')");
using (var q = new PlQuery("go(X, Y)"))
{
foreach (PlQueryVariables v in q.SolutionVariables)
{
questions.Add("name", v["X"].ToString());
questions.Add("age", v["Y"].ToString());
}
}
}
}
catch (SbsSW.SwiPlCs.Exceptions.PlException exp)
{
throw new FaultException<PrologFault>(new PrologFault(exp.Source), exp.MessagePl);
}
#endregion
Callback.PoseQuestion(questions, ResponseType.None);
}
Method#2:
public void DetermineAgeGroup(int age)
{
//Determine age group
string age_group = string.Empty;
try
{
using (var query = new PlQuery("age_group(" + age + ", G)"))
{
foreach (PlQueryVariables v in query.SolutionVariables)
age_group += v["G"].ToString();
}
}
catch (SbsSW.SwiPlCs.Exceptions.PlException exp)
{
throw new FaultException<PrologFault>(new PrologFault(exp.Source), exp.MessagePl);
}
//Check whether age_group is found or not
if (string.IsNullOrEmpty(age_group))
{
throw new FaultException<NoSolutionFoundFault>(new NoSolutionFoundFault("No solution found"), "Age specified exceeds the diagnosis range!");
}
else
{
Callback.RespondToUser(age_group, ResponseType.Age);
}
}
Method#3:
public void QuitProlog()
{
if (PlEngine.IsInitialized)
{
PlEngine.PlCleanup();
}
}
The client invokes the first method just fine and a result of the first query is successfully returned. When client tries to call the second method an exception is thrown with message (attempted to read or write protected memory) which causes the application to freeze. I checked the event viewer and this is what I get:
Application: w3wp.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.AccessViolationException
Stack:
at SbsSW.SwiPlCs.SafeNativeMethods.PL_new_term_ref()
at SbsSW.SwiPlCs.PlQuery..ctor(System.String, System.String)
at SbsSW.SwiPlCs.PlQuery..ctor(System.String)
at PrologQueryService.PrologQueryService.DetermineAgeGroup(Int32)
I also tried to use the interface for a .NET project.
Looking in the official repository of the CSharp interface to SWI-Prolog I noticed that the project is very old and the latest updates do not seem included in the binaries available in the download page of the official website.
Then I did the following steps:
The contrib repository dedicated to .NET indicates that the compatible SWI-Prolog version (at the time of writing) is "8.0.3-1" (look in the README file).
-> Then I uninstalled from my computer the latest stable and installed the indicated one. I got it from the full list of downloads of the old versions at this link.
I cloned the SWI-Prolog/contrib-swiplcs repository, unloaded the incompatible projects from the solution, in my case, since I don't use Visual Studio.
-> I set the target framework to Net Framework 4.8 and recompiled it (you can also do this with standard NET). Beware of some pragma directives defined in the old project file (For example I re-defined _PL_X64 variable via code.
I brought the main unit test methods into a new project with xUnit wiht the appropriate changes.
I set the target to x64, recompiled and rebuilt the tests and the "hello world" example.
It worked!
I was able to use SWI-Prolog both for Net 4.8 and in other Net Core applications (if you make the needed changes in order to target the Net Standard). You should not have any problem in both cases).
This is my fork as a preliminary example.
Finally, I can load a *.pl Prolog file with a program in my C# application and use it to evaluate some business logic rules (example with boolean answer [Permitted/Not-Permitted]):
[Fact]
public void ShouldLoadAProgramAndUseIt()
{
var pathValues = Environment.GetEnvironmentVariable("PATH");
pathValues += #";C:\Program Files\swipl\bin";
Environment.SetEnvironmentVariable("PATH", pathValues);
// Positioning to project folder
var currentDirectory = Directory.GetCurrentDirectory().Split('\\').ToList();
currentDirectory.RemoveAll(r => currentDirectory.ToArray().Reverse().Take(3).Contains(r));
var basePath = currentDirectory.Aggregate((c1, c2) => $"{c1}\\{c2}");
var filePath = $"{basePath}\\prolog_examples\\exec_checker.pl";
String[] param = { "-q", "-f", filePath };
PlEngine.Initialize(param);
try
{
var query = "exutable('2020-08-15',[('monthly', ['2019-12-30', '2020-03-10'])])";
_testOutputHelper.WriteLine($"Query: {query}");
using (var q = new PlQuery(query))
{
var booleanAnswer = q.NextSolution();
_testOutputHelper.WriteLine($"Answer: {booleanAnswer}");
Assert.True(booleanAnswer);
}
query = "exutable('2020-08-15',[('daily', ['2019-12-30', '2020-08-15'])])";
_testOutputHelper.WriteLine($"Query: {query}");
using (var q = new PlQuery(query))
{
var booleanAnswer = q.NextSolution();
_testOutputHelper.WriteLine($"Answer: {booleanAnswer}");
Assert.False(booleanAnswer);
}
}
finally
{
PlEngine.PlCleanup();
}
}
Try to close engine in the end of the first method and initialize it in the second again.
You can check this as the answer to the question unless you object.
I have created this interface for storing data in a file:
interface IFileStore {
bool Import(string);
string Export();
}
...and a generic class that implements that interface:
class DBTextFile<T> where T : IFileStore, new ()
{
public List<T> db = new List<T>();
public void SaveFile(string filename)
{
foreach(T d in db) File.WriteToFile(d.Export(), filename);
}
public void RestoreFile(string filename)
{
db = new List<T>();
string buffer;
while(buffer = File.Read(filename) != null)
{
T temp = new T();
if(temp.Import(buffer)) db.Add(temp);
}
}
}
This approach has been working for me for a while. However, I'm finding that I'm having to manage too many files. I think it might be more appropriate to use a database where each of my files would become a table in that database.
I would like to use a database that does not require the user to install any software on the user's system. (I want to keep things simple for the user.) Basically, I just want a database that is easy for me, the developer, to use and deploy.
Would MySQL be a good choice? Or would a different database be a better fit for my needs?
You can use different one file databases.
The one I'm using and am happy with is : SQLite.
You could also use access as Monika suggested, or browse google and see what else you can find ...