there is a table of messages. scheme:
CREATE TABLE [dbo].[Messages] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[DateCreate] DATETIME2 (7) DEFAULT (getdate()) NOT NULL,
[SenderId] INT NOT NULL,
[RecipientId] INT NOT NULL,
[TextMessage] NVARCHAR (MAX) NULL,
[IsReaded] BIT NOT NULL,
CONSTRAINT [PK_Messages] PRIMARY KEY CLUSTERED ([Id] ASC)
);
I want to get a table:
sender_id [int] - sender id key
all_count_messages [int] - number of messages from the sender
count_unreaded_messages [int] - number of unread messages from the sender
most_unreaded [DateTime] - the date of the most old unread messages
I also need group by [sender_id] and sort the output. Good. I make a simple request:
SELECT
message.SenderId AS sender_id,
COUNT(*) AS all_count_messages,
SUM(CAST(
CASE
WHEN IsReaded = 0
THEN 1
ELSE 0
END AS int)) as count_unreaded_messages,
MIN(message.DateCreate) AS most_unreaded
FROM
Messages AS message
GROUP BY
message.SenderId
ORDER BY
most_unreaded
demo result:
sender_id all_count_messages unreaded_messages most_unreaded
2 3 2 2019-08-15 20:03:59.0000000
1 9 8 2019-08-15 20:04:59.0000000
the answer suits me. how to describe it on EFCore?
try it
var chats = from my_messages in db.Messages
group my_messages by my_messages.SenderId into g
select
new
{
sender_id = g.Key,
all_count_messages = g.Count(),
unreaded_messages = from sub_messages in db.Messages where sub_messages.SenderId == g.Key && !sub_messages.IsReaded group sub_messages by sub_messages.SenderId into sub_g select sub_g.Count(),
most_unreaded = from sub_messages in db.Messages where sub_messages.SenderId == g.Key && !sub_messages.IsReaded group sub_messages by sub_messages.SenderId into sub_g select sub_g.Min(x => x.DateCreate)
};
foreach (var chat in chats) // so, too, has tried: chats.Include(x=>x.unreaded_messages).Include(x => x.most_unreaded)
{
}
get error in foreach (var chat in chats)
An unhandled exception occurred while processing the request.
ArgumentException: must be reducible node
System.Linq.Expressions.Expression.ReduceAndCheck()
I tried otherwise:
var chats = db.Messages.AsNoTracking().FromSql(
"SELECT" +
" message.SenderId AS sender_id," +
" COUNT(*) AS all_count_messages," +
" SUM(CAST(" +
" CASE" +
" WHEN IsReaded = 0" +
" THEN 1" +
" ELSE 0" +
" END AS int)) as count_unreaded_messages," +
" MIN(message.DateCreate) AS most_unreaded " +
"FROM " +
" Messages AS message " +
"GROUP BY " +
" message.SenderId " +
"ORDER BY" +
" most_unreaded ");
foreach (var chat in chats)
{
}
get error in foreach (var chat in chats)
InvalidOperationException: The required column 'Id' was not present in the results of a 'FromSql' operation.
Microsoft.EntityFrameworkCore.Query.Sql.Internal.FromSqlNonComposedQuerySqlGenerator.CreateValueBufferFactory(IRelationalValueBufferFactoryFactory relationalValueBufferFactoryFactory, DbDataReader dataReader)
You need to add Id column in the select statement to your query. It should be like
"SELECT" +
" message.Id AS Id," +
" message.SenderId AS sender_id," +
...
thank #ilkerkaran for the link https://stackoverflow.com/a/28627991/2630427 there I found and slightly altered method of dynamic request
a little finished realisation .net asp core 2.2:
public class AppDbContext : DbContext
{
public DbSet<Message> Messages { get; set; }
IOptions<AppConfig> app_config;
public AppDbContext(DbContextOptions<AppDbContext> options, IOptions<AppConfig> _app_config)
: base(options)
{
app_config = _app_config;
Database.EnsureCreated();
}
public IEnumerable<dynamic> DynamicListFromSql(string Sql, Dictionary<string, object> Params = null)
{
using (var cmd = Database.GetDbConnection().CreateCommand())
{
cmd.CommandText = Sql;
if (cmd.Connection.State != ConnectionState.Open) { cmd.Connection.Open(); }
if (Params != null)
foreach (KeyValuePair<string, object> p in Params)
{
DbParameter dbParameter = cmd.CreateParameter();
dbParameter.ParameterName = p.Key;
dbParameter.Value = p.Value;
cmd.Parameters.Add(dbParameter);
}
using (var dataReader = cmd.ExecuteReader())
{
while (dataReader.Read())
{
var row = new ExpandoObject() as IDictionary<string, object>;
for (var fieldCount = 0; fieldCount < dataReader.FieldCount; fieldCount++)
{
row.Add(dataReader.GetName(fieldCount), dataReader[fieldCount]);
}
yield return row;
}
}
}
}
}
string query =
"SELECT" +
" message.SenderId AS sender_id," +
" COUNT(*) AS all_count_messages," +
" (SELECT COUNT(*) FROM Messages AS sub_message WHERE sub_message.SenderId = message.SenderId AND sub_message.IsReaded = 0) AS unreaded_messages," +
" (SELECT MIN(sub_message.DateCreate) FROM Messages AS sub_message WHERE sub_message.SenderId = message.SenderId) AS most_unreaded " +
"FROM " +
" Messages AS message " +
"GROUP BY " +
" message.SenderId " +
"ORDER BY" +
" most_unreaded ";
//"OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY", offset, fetch);
foreach (var chat in db.DynamicListFromSql(query))
{
}
Related
please can anyone point me in the right direction. I want to check if the column exists, if so do this select else do another select
select case
when exists (
SELECT *
FROM Sys.columns c
WHERE c.[object_id] = OBJECT_ID('dbo.Municipality')
AND c.name = 'AmountTypeLabel'
)
then 1
else 0
end
This Checks if column exists and then return a 0 or a 1 but there is no column name so i can't check it in C#
This is what i have tried in C# but as i said before there is no column name
DataTable DT;
string SQL = "";
try
{
SQL = "select case " +
" when exists( " +
" SELECT 1 " +
" FROM Sys.columns c " +
" WHERE c.[object_id] = OBJECT_ID('dbo.Municipality')" +
" AND c.name = 'AmountTypeLabel'" +
")" +
" then 1 " +
" else 0 " +
" end ";
DT = Conn.ExecuteDT(SQL);
}
catch (Exception ex)
{
throw new Exception("Unable to get Tables", ex);
}
return DT;
}
With existing code you can fetch the value by using the below and use it in another function:
if(DT.Rows[0][0].ToString() == "1")
//Do Something
OR you could to use SQL Alias in your SQL query like below :
" end AS ColumnExists";
and then you can refer this in your other function. Sample snippet below -
if(DT.Rows[0]["ColumnExists"].ToString() == "1")
//Do Something
On a side note if the requirement is to fetch only the 1 or 0 from SQL server then use the ExecuteScalar as mentioned by Matteo1010 in comments.
var columnExists = cmd.ExecuteScalar();
if(columnExists.ToString() == "1")
//Do Something
I am trying to map two entities using Dapper. It's like this:
You have table Movie:
MovieID,
Runtime,
Description,
Title,
Director,
Genres
And table Projection:
ProjectionID,
Time,
Price,
Hall,
MovieID
Movie has a list of Projections (fore each movie one or more projections)
After connection the result list for movies with their projections is just the FIRST movie and ALL the records from projection.
Basically, Movie1 has two Projections Movie2 has three Projections it returns list of five movies and all of them are the first movie and all of them have five projections.
Can someone help me where do I make a mistake?
public List<Movie> ReturnMovieProjections()
{
var list = new List<Movie>();
var sql = "SELECT " +
"m.MovieID, " +
"m.Runtime, " +
"m.Description, " +
"m.Title, " +
"m.Director, " +
"m.Genres, " +
"p.MovieID, " +
"p.ProjectionID, " +
"p.Time, " +
"p.Price, " +
"p.Hall " +
"FROM Movie AS m INNER JOIN Projection AS p " +
"ON m.MovieID = p.MovieID " +
"WHERE p.MovieID = m.MovieID";
using (var connection = new OleDbConnection(GetConnectionString("CinemaDB")))
{
var movieDictionary = new Dictionary<int, Movie>();
list = connection.Query<Movie, Projection, Movie>(
sql, (movie, projection) =>
{
Movie movieEntry;
if (!movieDictionary.TryGetValue(movie.MovieID, out movieEntry))
{
movieEntry = movie;
movieEntry.Projections = new List<Projection>();
movieDictionary.Add(movieEntry.MovieID, movieEntry);
}
movieEntry.Projections.Add(projection);
return movieEntry;
},
splitOn: "p.MovieID").AsList();
}
return list;
}
You should return the dictionary values and not the IEnumerable obtained by Dapper.
That value will always contain all the 5 records produced by the query
Also there is no need to add a WHERE condition equal to the JOIN statement
var sql = "SELECT " +
"m.MovieID, " +
"m.Runtime, " +
"m.Description, " +
"m.Title, " +
"m.Director, " +
"m.Genres, " +
"p.MovieID, " +
"p.ProjectionID, " +
"p.Time, " +
"p.Price, " +
"p.Hall " +
"FROM Movie AS m INNER JOIN Projection AS p " +
"ON m.MovieID = p.MovieID ";
using (var connection = new OleDbConnection(GetConnectionString("CinemaDB")))
{
var movieDictionary = new Dictionary<int, Movie>();
list = connection.Query<Movie, Projection, Movie>(sql, (movie, projection) =>
{
Movie movieEntry = null;
if (!movieDictionary.TryGetValue(movie.MovieID, out movieEntry))
{
movieEntry = movie;
movieEntry.Projections = new List<Projection>();
movieDictionary.Add(movieEntry.MovieID, movieEntry);
}
movieEntry.Projections.Add(projection);
return movie; // return the same instance passed by Dapper.
},splitOn: "p.MovieID");
}
return movieDictionary.Values.ToList();
Why the ExecuteNonQuery catch exception {"validation error for column \"ORGTABLE\".\"FIKEYID\", value \"* null *\""}
string stValuesPlaceHolder = "#p0";
for (int iii = 1; iii < liststFieldValuesNoKeyId.Count; iii++)
stValuesPlaceHolder += ", #p" + (iii).ToString();
FbTransaction fbTransaction = fbConn.BeginTransaction();
FbCommand fbCmd = new FbCommand("INSERT INTO " + stTableName + "(" + stFieldNamesNoKeyId + ") VALUES ( " + stValuesPlaceHolder + " )", fbConn, fbTransaction);
for (int iii = 0; iii < liststFieldValuesNoKeyId.Count; iii++) {
string stPlaceHolder = "#p" + (iii).ToString();
string stValue = liststFieldValuesNoKeyId[iii];
fbCmd.Parameters.AddWithValue(stPlaceHolder, stValue);
}
fbCmd.ExecuteNonQuery();
fbTransaction.Commit();
The stTableName is OrgTable.
The fields names are:
fstPriority, fstInfo, fstDateCreated, fstDateModified, fiKeyID.
The field definitions are:
fstPriority VARCHAR(30), fstInfo VARCHAR(100), fstDateCreated VARCHAR(30), fstDateModified VARCHAR(30), fiKeyID INTEGER PRIMARY KEY
In this section of the code:
stFieldNamesNoKeyId = "fstPriority, fstInfo, fstDateCreated, fstDateModified".
stValuesPlaceHolder = "#p0, #p1, #p2, #p3"
Four
fbCmd.Parameters.AddWithValue:
stPlaceHolder = "#p0" ... stValue = "1st value";
stPlaceHolder = "#p1" ... stValue = "2nd value";
stPlaceHolder = "#p2" ... stValue = "3rd value";
stPlaceHolder = "#p3" ... stValue = "4th value";
I did not add a value for fiKeyID as it as the PRIMARY KEY.
I did not add a value for fiKeyID as it as the PRIMARY KEY.
So you try to insert a NULL primary key. This is not allowed.
http://www.firebirdsql.org/manual/nullguide-keys.html
NULLs are never allowed in primary keys. A column can only be (part of) a PK if it has been defined as NOT NULL, either in the column definition or in a domain definition.
Then, you might want to ask server for auto-generating IDs. There are few ways of doing it.
Firebird 3 comes with auto-inc column type, for example. Which is a syntactic sugar over tools that were explicitly used by database developer before.
Firebird 2 and prior versions used GENERATORS (aka SQL SEQUENCE) to achieve it.
You have to make a BEFORE-INSERT (or BEFORE-INSERT-OR-UPDATE) trigger on the table, that would fill the ID field from the generator, if the ID field was NULL. http://www.firebirdfaq.org/faq29/
CREATE GENERATOR gen_t1_id;
SET GENERATOR gen_t1_id TO 0;
set term !! ;
CREATE TRIGGER T1_BI FOR T1
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
if (NEW.ID is NULL) then NEW.ID = GEN_ID(GEN_T1_ID, 1);
END!!
set term ; !!
There it boils down to your SQL access library.
Because typically after you inserted the row - you have to know its ID.
If you do not care about that ID of newborn row, you may skip the rest.
But if you want to both insert the row and know its ID then it boils down to another choice.
Low-tech SQL-only libraries would force you to take a doubletrip:
SELECT GEN_ID(GEN_T1_ID, 1) FROM RDB$DATABASE or SELECT NEXT VALUE FOR GEN_T1_ID FROM RDB$DATABASE would reserve you a free token, then you would explicitly assign your ID PK-column to that value and insert it together with data columns, bypassing the trigger.
Or with advanced SQL libraries you may ask Firebird to auto-calculate value and report it to you: INSERT INTO tablename(data1,data2,dataq3) VALUES (1,2,3) RETURNING id. See https://en.wikipedia.org/wiki/Insert_(SQL)#Retrieving_the_key
Whether you need to learn the inserted ID or not, and whether your SQL library supports INSERT-RETURNING command or not - it is up to you to decide.
However when I do Google search ( it is www.google.com ) it comes with many links about C# Firebird Insert Returniung for many different C# SQL libraries, and again only you can tell which one you use. For few examples from different libs:
http://www.ibprovider.com/eng/documentation/firebird_21_adonet.html
http://www.sql.ru/forum/actualutils.aspx?action=gotomsg&tid=720869&msg=8075816
Retrieve last id from Firebird db table
Firebird insert...returning asp.net
https://social.msdn.microsoft.com/Forums/en-US/e7099cfb-7809-460a-aae9-79a2bd703454/how-i-can-return-the-id-after-insert-a-record-into-a-database-firebird-25-with-linq?forum=adodotnetentityframework
et cetera
Definitions:
public const string stMAIN_TABLE_NAME = " OrgTable ";
public const string stDELETED_TABLE_NAME = " BackupTable ";
public const string stFIELD_DEFINITIONS = " fstPriority VARCHAR(30)" +
", fstInfo VARCHAR(100)" +
", fstDateCreated VARCHAR(30)" +
", fstDateModified VARCHAR(30)" +
", fiKeyID INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY ";
public const string stFIELD_NAMES_NO_KEY_ID = " fstPriority" +
", fstInfo" +
", fstDateCreated" +
", fstDateModified ";
public const string stFIELD_NAMES_KEY_ID = " fiKeyID ";
public const string stFIELD_NAMES = stFIELD_NAMES_NO_KEY_ID + ", " + stFIELD_NAMES_KEY_ID;
Code:
//------------------------------
static private bool boCreateDatabaseTables(string stPathFilename,
string stUserID,
string stPassword,
List<string> liststTableNames,
List<string> liststFieldDefinitions)
{
bool boErrorFlag = false;
int iTablesCount = liststTableNames.Count();
string stOpenConn = new FbConnectionStringBuilder {
Database = stPathFilename,
UserID = stUserID,
Password = stPassword,
ServerType = FbServerType.Embedded,
ClientLibrary = stCLIENT_LIBRARY
}.ToString();
using (FbConnection fbConn = new FbConnection(stOpenConn)) {
try {
fbConn.Open();
FbTransaction fbTransaction = fbConn.BeginTransaction();
for (int ii = 0; ii < iTablesCount; ii++) {
string stSql = "CREATE TABLE " + liststTableNames[ii] + "( " + liststFieldDefinitions[ii] + ")";
FbCommand fbCmd = new FbCommand(stSql, fbConn, fbTransaction);
fbCmd.ExecuteNonQuery();
}
fbTransaction.Commit();
}
catch (Exception ex) {
boErrorFlag = true;
MessageBox.Show("catch ... GlobalsFirebird ... boCreateDatabaseTables ... " + ex.Message);
}
}
return boErrorFlag;
}//boCreateDatabaseTables
//------------------------------
//------------------------------
static public bool boAddRow(string stPathFilename,
string stUserID,
string stPassword,
string stTableName,
string stFieldNamesNoKeyId,
string stFieldNamesKeyId,
List<string> liststFieldValuesNoKeyId)
{
bool boErrorFlag = false;
string stOpenConn = new FbConnectionStringBuilder {
Database = stPathFilename,
UserID = stUserID,
Password = stPassword,
ServerType = FbServerType.Embedded,
ClientLibrary = stCLIENT_LIBRARY
}.ToString();
using(FbConnection fbConn = new FbConnection(stOpenConn)) {
fbConn.Open();
try {
string stValuesPlaceHolder = "#p0";
for (int iii = 1; iii < liststFieldValuesNoKeyId.Count; iii++)
stValuesPlaceHolder += ", #p" + (iii).ToString();
FbTransaction fbTransaction = fbConn.BeginTransaction();
string stCmd = "INSERT INTO " + stTableName + "(" + stFieldNamesNoKeyId + ") VALUES ( " + stValuesPlaceHolder + " ) RETURNING " + stFieldNamesKeyId;
FbCommand fbCmd = new FbCommand(stCmd, fbConn, fbTransaction);
for (int iii = 0; iii < liststFieldValuesNoKeyId.Count; iii++) {
string stPlaceHolder = "#p" + (iii).ToString();
string stValue = liststFieldValuesNoKeyId[iii];
fbCmd.Parameters.AddWithValue(stPlaceHolder, stValue);
}
fbCmd.Parameters.Add(new FbParameter() { Direction = System.Data.ParameterDirection.Output });
fbCmd.ExecuteNonQuery();
fbTransaction.Commit();
}
catch (Exception ex) {
boErrorFlag = true;
MessageBox.Show("catch ... GlobalsFirebird ... boAddRow ... " + ex.Message);
}
}
return boErrorFlag;
}//boAddRow
//------------------------------
My issue is this.
I have an ever changing set of columns due to pivoting on a list of items in a table. Basically each column that you see in the above picture is a line in the database table. I want to display a group of lines as one line on the front end. How do I return this dynamic type from a controller or api?
I have been attempting to use code such as this.
List<dynamic> items = repository.GetAll();
foreach(var item in items){
var properties = item.GetType().GetProperties(BindingFlags.Public);
foreach (var property in properties) {
var PropertyName = property.Name;
var PropertyValue = item.GetType().GetProperty(property.Name).GetValue(item, null);
model.Add(PropertyName, PropertyValue);
}
}
But GetProperties() returns nothing from the dynamic "item". I've also tried using item.GetType().GetFields() to no avail.
How can i go about getting the column name and value from a dynamic data type in c# when i don't know the name of that column? Or just returning the dynamic object from the api? Thanks.
Here's the repository code.
public IEnumerable<dynamic> GetAll()
{
var items = context.Database.SqlQuery<dynamic>(
"DECLARE #cols NVARCHAR(MAX), #sql NVARCHAR(MAX)" +
"SET #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(FormDetails.Name)" +
" from Value" +
" left join ValueDetails" +
" on Value.Id = ValueDetails.ValueId" +
" left" +
" join FormDetails" +
" on ValueDetails.FormDetailsId = FormDetails.Id" +
" ORDER BY 1" +
" FOR XML PATH(''), TYPE" +
" ).value('.', 'NVARCHAR(MAX)'), 1, 1, '')" +
" SET #sql = 'SELECT Id, FormId, CreatedDateTime, ' + #cols + '" +
" FROM" +
" (" +
" select Value.Id, Value.FormId, FormDetails.Name, ValueDetails.Value, Value.CreatedDateTime" +
" from Value" +
" left join ValueDetails" +
" on Value.Id = ValueDetails.ValueId" +
" left" +
" join FormDetails" +
" on ValueDetails.FormDetailsId = FormDetails.Id" +
" ) s" +
" PIVOT" +
"(" +
"MAX(Value) FOR Name IN(' + #cols + ')" +
") p order by CreatedDateTime desc '" +
" EXECUTE(#sql)").ToList();
return items;
}
Here's a bit more info. Basically my repository returns the data from the first image at the top of this page. But when i return that data to the front end this is what it looks like. The object is there but no properties...
The problem with your code is not in getting an empty list of properties, because they are genuinely not there. SqlQuery<T> will not populate a property if it's not there on type T; you are passing dynamic, which translates to System.Object.
One trick to solving this is to retrieve column names dynamically, and build an ExpandoObject out of it. The technique is described here.
static IList<dynamic> Read(DbDataReader reader) {
var res= new List<Dictionary<string,object>>();
foreach (var item in reader) {
IDictionary<string,object> expando = new ExpandoObject();
foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(item)) {
var obj = propertyDescriptor.GetValue(item);
expando.Add(propertyDescriptor.Name, obj);
}
res.Add(expando);
}
return res;
}
With this method in place, call
using (var cmd = ctx.Database.Connection.CreateCommand()) {
ctx.Database.Connection.Open();
cmd.CommandText = ...; // Your big SQL goes here
using (var reader = cmd.ExecuteReader()) {
return Read(reader).ToList();
}
}
Once your repository start returning ExpandoObjects, your code for retrieving properties can be replaced with code querying IDictionary<string,object>, an interface implemented by ExpandoObject:
IDictionary<string,object> properties = item as IDictionary<string,object>;
if (properties != null) {
foreach (var p in properties) {
model.Add(p.Key, p.Value);
}
}
I am using the following code for performing a delete operation via EF code first using a inline query internally
void IRepository<T>.Delete(params Guid[] ids)
{
var sql = string.Format("UPDATE {0} SET [IsDeleted] = 1 WHERE [Id] IN (#ids) ", GetTableName());
string sep = String.Join(", ", ids.Select(x => "'" + x + "'"));
var sqlParams = new Object[]
{
new SqlParameter("ids", string.Join(",",sep)),
};
DataContext.Database.ExecuteSqlCommand(sql, sqlParams);
}
Now when I execute the command it gives me
conversion failed when converting from a character string to uniqueidentifier
error.
Hiwever when I run the query in sql say.
UPDATE [dbo].[Table] SET [IsDeleted] = 1 WHERE [Id] IN ('20Cr0BCA-6EBB-E411-A04B-BC305BA8C713','506c79c1-6ebb-e411-a04b-bc305ba8c733')
it works fine.
Is this possible to do this way ?Or what am I doing wrong ?
I would do something like this
void IRepository<T>.Delete(params Guid[] ids)
{
if (ids == null || !ids.Any())
return;
var idParams = ids.Select((x, cnt)=> new { ParamName ="#ids"+ cnt, Param = x});
var sql = string.Format("UPDATE {0} SET [IsDeleted] = 1 WHERE [Id] IN ("+ String.Join(", ",idParams.Select(x => x.ParamName)) + ") ", "Table");
var sqlParams = idParams.Select(x=> new SqlParameter(x.ParamName, x.Param)).ToArray();
DataContext.Database.ExecuteSqlCommand(sql, sqlParams);
}