Can I dynamically reference multiple databases in a LINQPad script? - linqpad

I have a database with a table describing multiple entities, with one column being the name of another database holding data for that entity. All entity databases are on the same SQL Server as the one listing them and all have an identical schema.
I know that I can use Ctrl-drag to add the additional databases to my script but what I actually want is to do this dynamically from the database name. Something like this.
var entities = ParentDatabase.EntityList
.Where(e => ??)
.Select(e => new { e.Id, e.DatabaseName });
var results = new List<ResultCarrier>();
foreach (var entity in entities)
{
results.AddRange(
GetDataContextFor(entity.DatabaseName).SomeTable
.Select(t => new ResultCarrier()
{
EntityId = e.Id,
Column1 = t.Column1,
Column2 = t.Column2,
...
}));
}
// further process combined results
Is this possible?
I see that the type of one of these databases is LINQPad.User.DatabaseNameTypes.TypedDataContext and wondered whether, as each database has the same schema, there might be a base class that I could use in some way to achieve this.

TypedDataContext is your base class, and you can just create a new instance of this and pass it the sql connection string.
You can find your current connection string using
this.Connection.ConnectionString.Dump();
For example, I use Integrated Security and I have a little routine that goes through all the database in my server and dumps out a table, so I use the following routine.
var databases = ExecuteQuery<String>("SELECT name FROM sys.databases").ToList();
foreach(var r in databases)
{
switch (r)
{
case "master" :
case "tempdb" :
case "model" :
case "msdb" :
break;
default:
try
{
string newConnectionString = String.Format("Data Source={0};Integrated Security=SSPI;Initial Catalog={1};app=LINQPad", this.Connection.DataSource, r);
var dc = new TypedDataContext(newConnectionString);
dc.Table.Dump(r);
}
catch (Exception ex)
{
ex.Message.Dump(r);
}
break;
}
}

#sgmoore's answer got me on the right track. I hadn't come across ExecuteQuery in LINQPad and I was able to use it to achieve what I wanted. Below is the code I ended up with. I will now extend it to further retrieve data from a service and join it to databaseLocations to give the final result I'm after.
void Main()
{
var organisations = ExecuteQuery<OrganisationCarrier>(#"
SELECT do.GroupId [Id], o.sOrganisationName [Name], o.sConnectDatabase [Database]
FROM dbo.Organisation o
INNER JOIN dynamix.Organisations do ON o.liOrgID = do.OrganisationID
INNER JOIN dynamix.OrganisationFeatures oft ON do.OrganisationKey = oft.OrganisationKey
INNER JOIN dynamix.Features ft ON oft.FeatureKey = ft.FeatureKey
WHERE ft.FeatureName = 'LightningLocations'").ToList();
var databaseLocations = new List<LocationExtract>();
foreach (var organisation in organisations)
{
this.Connection.ConnectionString = $"Data Source={this.Connection.DataSource};Integrated Security=SSPI;Initial Catalog={organisation.Database};app=LINQPad";
databaseLocations.AddRange(ExecuteQuery<LocationCarrier>(#"
SELECT dml.DmxLocationId [Id], ml.sLocationName [Name], ml.bDeleted [IsDeleted]
FROM dynamix.MapLocations dml
INNER JOIN dbo.MapLocations ml ON dml.FmLocationId = ml.liLocationID")
.Select(l => new LocationExtract(organisation.Id, l.Id, l.Name, l.IsDeleted)));
}
databaseLocations.Dump();
}
class OrganisationCarrier
{
public long Id { get; set; }
public string Name { get; set; }
public string Database { get; set; }
}
class LocationCarrier
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
}
class LocationExtract
{
public long OrganisationId { get; }
public long LocationId { get; }
public string Name { get; }
public bool IsDeleted { get; }
public LocationExtract(long organisationId, long locationId, string name, bool isDeleted)
{
OrganisationId = organisationId;
LocationId = locationId;
Name = name;
IsDeleted = isDeleted;
}
}

Related

Checking Against Multiple Entries in a Table - ASP.NET Core 3.1 and EFCore

I'm trying to figure out the logic when checking against existing fields in a table with a single variable. Here is my entity class:
public class Metadata
{
public int Id { get; set; }
public string MachineName { get; set; }
public string MachineId { get; set; }
public string UserId { get; set; }
}
Lets say for example the user has 3 entries in the table and we check the current machine name with the machine names in the table. If the current machine name does not match the machine names in the table, I want to save a new entry in the table.
At the moment, my code jumps to the else and returns out of the method if the first machine name entry in the table is the same as the current machine name, which is not good as it might not be the case for the 2nd or 3rd entry. Also if the current machine name matches the 2nd entry, it will be ignored and saved since the first entry did not match.
Here is my method:
private void ValidateMetadata(string userId)
{
// get list of entities if they exist based upon the userId
var metaList = _metadata.where(x => x.UserId == userId).ToList();
if(metaList != null)
{
foreach (var m in metaList)
{
// check each machine name with current machine name
if (m.MachineName != GetMachineName())
{
// create new entry
var metadata = new { MachineName = GetMachineName(), MachineId = GetMachineId(), UserId = userId };
// save to db
_metadata.Save(metadata);
}
else
{
return;
}
}
}
else
{
// create new entry if there are none in the table
var metadata = new { MachineName = GetMachineName(), MachineId = GetMachineId(), UserId = userId };
// save to db
_metadata.Save(metadata);
}
}
Use Any
var hasHit = _metadata.Any(x => x.UserId == userId);
Example
var test = new List<string> {"one", "two"};
var result = test.Any(x => x.Equals("one"));
result will have the value true

JSON Arrays for multiple values in ASP.NET API

I've built a simple API for querying a local SQL database (for a database course). I'm using Postman to test my endpoints, but the results I'm getting aren't formatted the way I'd like. For example, in this query I ask my database for data about 1 person but it's returning all the unique sets.
What I would like is something like this (multivalues in a list):
My API is calling a stored procedure that's pretty lengthy, but this is the select statement at the end (a lengthy inner join):
SELECT DISTINCT specializationType, memberSince, teamName, E.meetingsAttended, lifetimeScore
FROM participants AS A
INNER JOIN
ONTEAM AS B
ON A.participantKey = B.participantKey
INNER JOIN
MEMBERSHIP AS C
ON A.participantKey = C.participantKey
INNER JOIN
SPECIALIZATIONPART AS D
ON A.participantKey = D.participantKey
INNER JOIN
MEETINGCOUNT as E
ON A.participantKey = E.participantKey;
The endpoint that's calling this is:
// GET api/InfoSecDB/adminSelectsParticipant
[HttpGet]
[Route("adminSelectsParticipant")]
public ActionResult<IEnumerable<string>> adminSelectsParticipant([FromBody] JObject data)
{
string pName = (string)data["pName"];
List<string> distinctSpecializations = new List<string>();
List<string> allTeams = new List<string>();
List<string> myP = new List<string>();
DatabaseModel dbm = new DatabaseModel();
DataTable dt = dbm.adminSelectsParticipant(pName);
return Ok(dt);
}
I'm not sure if this something that's supposed to be done by the stored procedure or the API endpoint. Any suggestions would be greatly appreciated!
Something you may want to do for this is set up a Model with each of the fields that you want to display. You can then loop through your DataTable and assign it to your model, then return your model in the way you want with your second image above.
Here is something that I found, it should be similar to what you want:
How do I bind a datatable to model and then to a dropdownlist in mvc?
Your model would look something like this:
public class Model
{
public List<string> SpecializationType { get; set; }
public DateTime MemberSince { get; set; }
public List<string> TeamName { get; set; }
public int MeetingsAttended { get; set; }
public int LifetimeScore { get; set; }
}
Then you could do var model = new List(); and assign your DataTable values to it.

How do I execute RAW SQL query and dump it to JSON in ASP.NET MVC 5 (EF 6)?

I want to execute a raw SQL query as shown below:
select material, method, count(*)
from treatment
group by material, method
and return it as a JSON object.
Because EF 6 allows the execution of raw query (i.e., Database.SQLQuery()), I use it to create a JSON Object.
My code looks like this.
// GET: Dummy
public string Dummy()
{
string query = "select material, method, count(*) as cnt from Treatment "
+ " group by material, method";
var result = db.Database.SqlQuery<List<String>>(query);
var jsonResult = JsonConvert.SerializeObject(result);
return jsonResult;
}
However, instead of getting a JSON object with the material and methods, I get an empty json object instead.
[[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]
Is it possible to return the correct JSON object without having the model for the the raw query? If so, how to update my code to return the correct result?
Thanks in advance
You can't resolve that query to a list of strings, you need to resolve it to a (list of) class(es), like this :
public static string Dummy()
{
using (var db = new TestDbEntities())
{
string query = "select 'material' as material , 'method' as method, 1 as cnt ";
var result = db.Database.SqlQuery<MyClass>(query);
var res = result.ToList();
var jsonResult = JsonConvert.SerializeObject(res);
return jsonResult;
}
}
where your class will be something like this :
public class MyClass
{
public string Material { get; set; }
public string Method { get; set; }
public int Cnt { get; set; }
}
You need to capture query result sets with multiple columns using a class with matching property names (make sure they have exactly same name and proper casing) and parameterless constructor:
public string Dummy()
{
string query = "select material as Material, method as Method, count(*) as Cnt from Treatment group by material, method";
var result = db.Database.SqlQuery<ResultSet>(query).ToList();
var jsonResult = JsonConvert.SerializeObject(result);
return jsonResult;
}
public class ResultSet
{
public string Material { get; set; }
public string Method { get; set; }
public int Cnt { get; set; }
}
Note that Database.SqlQuery<T> will return an IEnumerable<T> with type System.Data.Entity.Internal.InternalSqlQuery<T>, so that a List<string> is not fit in this context. If you just return single result set, Database.SqlQuery<string>(...).ToList() should be used instead of Database.SqlQuery<List<string>>.
Similar issue:
Entity framework raw SQL Query
Use FOR JSON AUTO:
string query = "select material, method, count(*) as cnt from Treatment "
+ " group by material, method FOR JSON AUTO";

Overflow or Underflow Exception Querying Cosmos DocumentDB

I'm encountering an unexpected error trying to query a Cosmos DocumentDB. I have run my query in Azure Data Explorer and I am getting the expected results, however, when I attempt to run it using the C# SQL API, I am getting various errors (after different attempts) including "Overflow or Underflow in Arithmetic Operation", or "NullReferenceException". When I use ReadDocumentAsync, I get a document without error, so I know my cosmos client and collection uri are OK.
Could someone please help me understand why I may be having a problem with this.
Here is how I am building and executing my query:
using (var cosmosClient = new DocumentClient(new Uri(cosmosEndPointUrl), cosmosAuthKey))
{
var collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, collectionId);
var sql = #"SELECT TOP 10 p.Id As QuoteKey
,pol.Proposer.Forename
,pol.Proposer.Surname
,pol.Proposer.ResidentialAddress.Postcode
,veh.Vrn
FROM Proposal p
JOIN pol IN p.QuoteRequest.Policies
JOIN veh IN pol.Vehicles
WHERE(pol.Proposer.Forename = #Forename OR #Forename = '')
AND(pol.Proposer.Surname = #Surname OR #Surname = '')
AND(pol.Proposer.ResidentialAddress.Postcode = #Postcode OR #Postcode = '')
AND(veh.Vrn = #Vrn OR #Vrn = '')";
var ps = new SqlParameterCollection(new SqlParameter[]
{
new SqlParameter { Name = "#Forename", Value = criteria.Firstname },
new SqlParameter { Name = "#Surname", Value = criteria.Surname },
new SqlParameter { Name = "#Postcode", Value = criteria.Postcode },
new SqlParameter { Name = "#Vrn", Value = criteria.RegNo },
});
var sqlQuerySpec = new SqlQuerySpec(sql, ps);
var fo = new FeedOptions { MaxItemCount = 10 };
List<QuoteSummary> results = new List<QuoteSummary>();
var query = cosmosClient.CreateDocumentQuery(collectionUri, sqlQuerySpec, fo).AsDocumentQuery();
while (query.HasMoreResults)
{
var demiResults = await query.ExecuteNextAsync<QuoteSummary>();
results.AddRange(demiResults);
}
return results.ToList();
}
Here are the other couple of classes used:
public class QuoteSummary
{
public string QuoteKey { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public string Postcode { get; set; }
public string Vrn { get; set; }
}
public class QuoteSearchCriteria
{
public string Firstname { get; set; }
public string Surname { get; set; }
public string Postcode { get; set; }
public string RegNo { get; set; }
}
Please help. This issue is eating into my time at a Hack I'm attending at Microsoft and no one I've asked so far knows why it isn't working.
When I got this exception, it was environment+dependencies related:
My configuration was Azure App service, which queries azure cosmosdb.
What I was missing is the Nuget package in the Web API level. It obviously existed as a dependency in other class library which was referenced, but wasn't referenced directly at the top level running application (Web API in my case).

Best Practice with MVC4 and EF5 to apply changes

I have a CustomerOrder-view where I would like to change an existing CustomerOrder.
I have a viewmodel that very simpliefied looks something like this:
public class CustomerOrderViewModel
{
public int ID { get; set; }
public Customer Customer { get; set; }
public DateTime Date { get; set; }
public IEnumerable<OrderRow> OrderRows { get; set; }
}
public class OrderRow
{
public int id { get; set; }
public int price { get; set; }
}
public class Customer
{
public string Name { get; set; }
}
I also have a database with mapping tables / fields.
In my GET Action Method I load the Order with the help of Automapper like this:
var customerOrder = using (var ctx = new My_Entities()) {
return ctx.CustomerOrders.
Include("Orderrows").
Include("Customer").
Single(o => o.CustomerOrderID == id);
}
var model= AutoMapper.Mapper.DynamicMap<DataAccessLayer.CustomerOrder, CustomerOrderViewModel>(customerOrder);
In the View I use Knockout to bind to a viewmodel there, where the user can update the CustomerOrder. That includes editing Customer information and adding new orderrows etc.
Then in the post back a map the ViewModel back to the ObjectContext CustomerOrder:
var customerOrderToBeSaved =
AutoMapper.Mapper.DynamicMap<CustomerOrderViewModel, CustomerOrder>(
customerOrderViewModel);
try
{
using (var ctx = new MyEntities())
{
ctx.CustomerOrders.Attach(customerOrderToBeSaved);
ctx.CustomerOrders.ApplyCurrentValues(customerOrderToBeSaved);
...
ctx.SaveChanges();
}
}
I get the error message: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
OK, that I can understand. But how should I go about this? Can I get the existing object and apply Changes to that one, because that is really what I'd like. I've tried to look up the old one and detach it but I haven't got it to wrok.Perhaps I'm doing this in a completely wrong way. Please advice.
You should not attach customerOrderToBeSaved, see MSDN about the argument of ApplyCurrentValues.
The detached object that has property updates to apply to the original object.
So you've got to load the entity from the database into the context and then ApplyCurrentValues with the detached object that has the new values.
You don't have to load the row from the database to update it.
You can do something like this:
var entity = ctx.CustomerOrders.Attach(customerOrderToBeSaved);
ctx.Entry( entity ).State = EntityState.Modified;
ctx.SaveChanges();
This will tell EF to issue an UPDATE SQL statement that overwrites all the columns in the record.
You can select which columns you want to update like this:
var entity = ctx.CustomerOrders.Attach(customerOrderToBeSaved);
var entry = ctx.Entry( entity );
entry.Property( o => o.<ColumnPropertyToUpdate> ).IsModified = true;
entry.Property( o => o.<ColumnPropertyToUpdate> ).IsModified = true;
...
ctx.SaveChanges();
If you do this, EF will only include the columns you've marked as modified in the UPDATE statement.