JSON Arrays for multiple values in ASP.NET API - sql

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.

Related

Performing search across multiple tables using linq

I'm trying to perform a search action on my page. I have 3 tables on my database with no relation between then. I created a view model called SearchModel and I'm trying to send the query data to the view like below
I'm very new to ASP.NET*
IQueryable<SearchModel> t = Enumerable.Empty<SearchModel>().AsQueryable();
var searchD = from d in db.domains
where d.domainName.Contains(search) //search is the search string
select d;
var searchU = from u in db.users
where u.userName.Contains(search)
select u;
var searchA = from a in db.adminLists
where a.userName.Contains(search)
select a;
t.searchDomain = searchD;
t.searchAdmin = searchA;
t.searchUsers = searchU;
return view(t);
I get an error saying this
IQueryable<SearchModel>' does not contain a definition for 'searchDomain'
and no accessible extension method 'searchDomain' accepting a first argument of type
'IQueryable<SearchModel>' could be found (are you missing a using directive or an assembly reference?)
This is my view model
public class SearchModel
{
public user searchUsers { get; set; }
public adminList searchAdmin { get; set; }
public domain searchDomain { get; set; }
}
Is there something I'm missing and/or is there a better way to implement the search?

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";

Can I dynamically reference multiple databases in a LINQPad script?

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;
}
}

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.

NHibernate - How to write this Query: Select parents & find child for each parent that matches a condition

OK, first my simple Domain Model is 2 classes with a one-to-many relationship, a simple Parent -> child relationship. A 'Tweet' has one or more 'Votes', but each Vote belongs to just one Tweets etc.
public class Tweet
{
public virtual long Id { get; set; }
public virtual string Username { get; set; }
public virtual string Message { get; set; }
public virtual ISet<Vote> Votes { get; set; }
}
public class Vote
{
public virtual long Id { get; set; }
public virtual long TwitterUserId { get; set; }
public virtual DateTime VotedDate { get; set; }
public virtual Tweet Tweet { get; set; }
}
I'm trying to write a query in either HQL, ICriteria or NHibernate LINQ, that selects all Tweets, but also add two columns that selects:
A count of the number of votes, and...
Whether a particular user has voted for that tweet, based on a particular TwitterUserId
With the two extra columns, I'm not expecting to get Tweet domain object back, and would probably need to run a Report query, that's OK. But I'm struggling to figure out how to write this query.
I know how to write this as a Stored Procedure, or using LINQ 2 SQL. If it helps I will express this as a LINQ to SQL query.
long userId = 123;
var tweets = from t in dataContext.Tweets
where t.Application == app
orderby t.PostedDate desc
select new TweetReport()
{
Id = t.Id,
Username = t.Username,
Message = t.Message,
TotalVotes = t.Votes.Count(),
HasVoted = t.Votes.Any(v => v.TwitterUserId == userId)
};
I know this will work in LINQ 2 SQL, and generate reasonably efficient T-SQL, but I can't figure out how to write this in NHibernate.
Update: I tried running the above LINQ query in NHibernate by using the NHibernate LINQ provider built for NHibernate v2, eg:
var tweets = from t in Session.Linq<Tweet>()
where (snip)
But it didn't work. Has LINQ support in Nhibernate 3.0 improved? I'm a bit reluctant to use version 3.0 because it's still alpha, but if this will work, then I might give it a go.
Update 2: Thanks to Diego Mijelshon's suggestion, I upgraded to NHibernate 3.0 alpha 2 and wrote the query in LINQ:
var tweets = from t in Session.Query<Tweet>()
where t.App == app
orderby t.PostedDate descending
select t;
int totalRecords = tweets.Count();
var pagedTweets = (from t in tweets
select new TweetReport()
{
Id = t.Id,
TwitterId = t.TweetId,
Username = t.Username,
ProfileImageUrl = t.ImageUrl,
Message = t.Message,
DatePosted = t.PostedDate,
DeviceName = t.Device.Name,
DeviceUrl = t.Device.Url,
TotalVotes = t.Votes.Count(),
HasVoted = t.Votes.Any(v => v.TwitterUserId == userId)
})
.Skip(startIndex)
.Take(recordsPerPage)
.ToList();
return new PagedList<TweetReport>(pagedTweets,
recordsPerPage, pageNumber, totalRecords);
It's exactly the same with NHibernate 3; just replace dataContext.Tweets with session.Query<Tweet>.
Alternatively, you can create a context class that exposes session.Query<Tweet> as an IQueryable<Tweet> Tweets property, then the code would be 100% unchanged.