How do I write a SQL Concat in Linq? - c#

What is the best approach to take to write a concat in Linq? I know that the official word is that you can't but I've heard that there are ways you can accomplish it and I need help to do this. I have a SQL Statement that reads as follows:
CONCAT(
[event_organiser] ,
' ', DATEPART(dd,[event_start]),
'/', DATEPART(mm, [event_start]))
AS organiser_info
I need to create this in Linq but I'm not sure how to based on my current setup. Here is my current select statement.
var datacontext = db.tbl_events.AsQueryable();
IQueryable<EventsViewModel> theevent = (
from v in datacontext
where v.event_start == null
select new EventsViewModel
{
event_idx = v.event_idx,
event_name = v.event_name
...concat goes here..
});

Thank you for the feedback on this question your comments and examples led me to the answer based on what you have shown me. Here is what worked for my particular scenario
next_charterer_info = string.Concat(p.fixture_charterer ?? "", " ", p.fixture_start.Value.Day, "/", p.fixture_start.Value.Month),
Thanks again

Just use "+" to join the strings?
from v in datacontext
where v.event_start == null
select new EventsViewModel
{
event_idx = v.event_idx,
event_name = v.event_name
organiser_info = event_organiser + ' ' + ...
});

You can use SqlFunctions (https://msdn.microsoft.com/en-us/library/system.data.objects.sqlclient.sqlfunctions.datepart(v=vs.110).aspx)
IQueryable<EventsViewModel> theevent = (
from v in datacontext
where v.event_start == null
select new EventsViewModel
{
event_idx = v.event_idx,
event_name = v.event_name
organiser_info = v.event_organiser + ' ' + DatePart("dd", v.event_start)
});
Other option is to include all needed columns and extend your EventsViewModel with a getter property:
public class EventsViewModel
{
...
public string organiser_info
{
get
{
return string.format("{0} {1} / {2}",
event_organiser,
event_start.ToString("dd"),
event_end.ToString("MM"))
}
}
}
Using this approach you will not be confronted with any non-translatable code constructs in Linq to Entities

var theevent = (
from v in datacontext
where v.event_start == null
select new EventsViewModel
{
event_idx = v.event_idx,
event_name = v.event_name
organiser_info = string.Format("{0} {1}/{2}", event_organiser ?? "", event_start.Date.ToString("d"), event_start.Date.ToString("M"))
});
UPDATE
String.Concat(string, string, string) is significantly faster than String.Format(). Also you can write that like this:
organiser_info = string.Concat(event_organiser ?? ""," ", event_start.Date.ToString("d"), event_start.Date.ToString("M"))
UPDATE2
Solution for retrive IQueryable odject:
IQueryable<EventsViewModel> theevent = (
from v in datacontext
where v.event_start == null
select new EventsViewModel
{
event_idx = v.event_idx,
event_name = v.event_name,
organiser_info = v.event_organiser ?? "" +
v.event_organiser ? " " : "" +
v.event_end ? SqlFunctions.DatePart("Day", v.event_end) : "" +
v.event_end ? SqlFunctions.DatePart("Month", v.event_end) : ""
}
Dont forget add using System.Data.Objects.SqlClient for SqlFunctions.DatePart.
From MSDN
This function is translated to a corresponding function in the
database. For information about the corresponding SQL Server function,
see DATEPART (Transact-SQL).

Related

Linq SELECT AS and CONCAT

I have been given a SQL query that I need to translate into LINQ, that's not a problem but there are a couple of items that I need help with in this particular query and those are the selecting column name as '' and also the concatination. Here is the query in question:
SELECT vessel_idx,
vessel_name,
spotlist_dp,
spotlist_bhp,
spotlist_deck,
spotlist_bp,
spotlist_oilrec,
spotlist_fifi,
spotlist_rov,
'' AS fixture_charterer,
'' AS fixture_work,
CONCAT( fixture_charterer ,
' ',
mid(fixture_start,9,2),
'/',
mid(fixture_start,6,2)) AS next_charterer_info,
'' AS fixture_location,
'0000-00-00 00:00:00' AS fixture_stop,
'' AS fixture_note
FROM tbl_vessels
WHERE vessel_type='AHTS'
AND current_location_spotlist_id = 2
AND fixture_start > '2016-02-12 08:30:00'
AND vessel_status = 'PPT'
ORDER BY fixture_stop
The usual stuff like AND, WHERE etc I can deal with it's purly these sections I'm struggling with. The selecting column as '':
SELECT '' AS fixture_charterer,
'' AS fixture_work,
and the concatination
CONCAT( fixture_charterer ,
' ',
mid(fixture_start,9,2),
'/',
mid(fixture_start,6,2)) AS next_charterer_info,
'' AS fixture_location,
'0000-00-00 00:00:00' AS fixture_stop,
'' AS fixture_note
Many thanks
SELECT AS is represented as:
var demo = from vessel in db.tbl_vessels
select new {
fixture_charterer = "",
fixture_work = ""
};
CONCAT is:
var demo = from vessel in db.tbl_vessels
select new {
fixture_note = fixture_charterer + " " + next_charterer_info
};
You can select constants like "" easily:
var q = from v in ....
....
select new
{
...,
fixture_charterer = "",
fixture_work = "",
next_charterer_info =
(v.fixture_charterer ?? "") + " "
+ (v.fixture_start == null ? "" : v.fixture_start.Substring(8,2))
};

Regex parse sql string

I have sql string like
select * from dbo.Person where Person = ? AND Name = ? OR (Country = ? OR City = 1)
If it's possible to get string array like below with Regex in C#
result[0] = Person = ?
result[1] = Name = ?
result[2] = (Country = ? OR City = 1)
thanks.
First try looks like this
var s = #"select* from dbo.Person where Person = ? AND Name = ? OR (Country = ? OR City = 1)";
var reg = new Regex("[A-Za-z]+ = [A-Za-z0-9?]+");
var result = reg.Matches(s);
Something like that but is no Regex
var s = #"select* from dbo.Person where Person = ? AND Name = ? OR(Country = ? OR City = 1)";
var s1 = s.Split(new[] { "where" }, StringSplitOptions.None)[1];
var s2 = s1.Split(new[] { "OR", "AND" }, StringSplitOptions.None);
If you need anything more complicated than this, it's going to quickly go beyond what you can easily solve with regex. I have released a free parser on GitHub that will parse out TSQL in a stable way into the pieces, TSQL Parser . You can also use the Microsoft TSQL parser, using the TSqlParser . With either of these, they will break it out a little more granular than you're requesting, which you will then have to piece back together based on parenthesis for example.

Can't Select Records from a database table with grouping using Linq

I'm trying to Select an SQL table and grouping columns using Linq to SQL, Entities, or Object (I don't really know what.) I'm a bit new to Linq and could use some help. The code structure is straight-forward in my view. When I don't add in the GroupBy method, it works fine. JT_Temp is an entity model by the way. When I run my code below, it goes to the exception:
The entity or complex type 'JT_Temp' cannot be constructed in LINQ to Entities query.
I have tried this and various stackoverflow solutions but they don't seem to solve and apply to my case.
Here is my current code:
//Goal:
//SELECT EnvelopeCode, Branch_COA, AQ_COA, AQ_Branch, SUM(Amount), AQ_PostStatus FROM JT_Temp
//GROUP BY EnvelopeCode, Branch_COA, AQ_COA, AQ_Branch, AQ_PostStatus
//var csvFilteredRecord = Context.JT_Temp.SqlQuery("SELECT * FROM JT_Temp").ToList<JT_Temp>();
// GROUP BY -- No go; Manual SELECT -- No go;
try
{
var csvFilteredRecord = (
from c in Context.JT_Temp
group c by new
{
c.EnvelopeCode,
c.Branch_COA,
c.AQ_COA,
c.AQ_Branch,
c.AQ_PostStatus
} into i
select new JT_Temp
{
EnvelopeCode = i.Key.EnvelopeCode,
Branch_COA = i.Key.Branch_COA,
AQ_COA = i.Key.AQ_COA,
AQ_Branch = i.Key.AQ_Branch,
//TO-DO SUM(Amount),
AQ_PostStatus = i.Key.AQ_PostStatus
}).ToList();
foreach (var Record in csvFilteredRecord)
{
Console.WriteLine(
Record.EnvelopeCode
+ Record.Branch_COA
+ Record.AQ_COA
//+ Record.Amount
+ Record.AQ_PostStatus
);
}
}
catch (Exception e)
{
Console.WriteLine("---------- " + e.Message);
Console.ReadLine();
}
You can't project into JT_Temp. Just use an anonymous object. Also, no reason to make it a list, so I removed the .ToList()
Query syntax:
var csvFilteredRecord = (
from c in Context.JT_Temp
group c by new
{
c.EnvelopeCode,
c.Branch_COA,
c.AQ_COA,
c.AQ_Branch,
c.AQ_PostStatus
} into i
select new
{
EnvelopeCode = i.Key.EnvelopeCode,
Branch_COA = i.Key.Branch_COA,
AQ_COA = i.Key.AQ_COA,
AQ_Branch = i.Key.AQ_Branch,
//TO-DO SUM(Amount),
AQ_PostStatus = i.Key.AQ_PostStatus
});
Method syntax:
var csvFilteredRecord = Context.JT_Temp.GroupBy(k=> new
{
c.EnvelopeCode,
c.Branch_COA,
c.AQ_COA,
c.AQ_Branch,
c.AQ_PostStatus
},
v=>v.Amount,
(k,v)=>new {
k.EnvelopeCode,
k.Branch_COA,
k.AQ_COA,
k.AQ_Branch,
k.AQ_PostStatus
Amount=v.Sum()
});
foreach (var Record in csvFilteredRecord)
{
Console.WriteLine(
Record.EnvelopeCode
+ Record.Branch_COA
+ Record.AQ_COA
+ Record.Amount
+ Record.AQ_PostStatus
);
}

Use Dynamic LINQ Library with join

Following is the UI :
And this is code snippet i am using to fire dynamic where clause :
public void bind()
{
string filter = "";
if (!string.IsNullOrEmpty(txtPart.Text))
{
filter = filter + "masterinv.inv_item_id = " + txtPart.Text;
}
if (!string.IsNullOrEmpty(txtDescription.Text))
{
if (!string.IsNullOrEmpty(filter))
{
filter = filter + " || masterinv.description = " + txtDescription.Text;
}
else
{
filter = filter + "masterinv.description = " + txtDescription.Text;
}
}
if (!string.IsNullOrEmpty(txtVendor.Text))
{
if (!string.IsNullOrEmpty(filter))
{
filter = filter + " || vendor.vendor_name = " + txtVendor.Text;
}
else
{
filter = filter + "vendor.vendor_name = " + txtVendor.Text;
}
}
InventoryDataContext dc = new InventoryDataContext(InventoryDBContext.GetConnectionstring());
var searchResult = (from masterinv in dc.OMS_REF_Master_Inventories
join vendor in dc.OMS_REF_Vendors on masterinv.inv_item_id equals vendor.inv_item_id
Where(filter)
select new OMS_REF_Master_Inventory
{
inv_item_id = masterinv.inv_item_id,
description = masterinv.description,
unit_of_measure = masterinv.unit_of_measure,
lot_id = masterinv.lot_id,
serial_id = masterinv.serial_id,
mfg_id = masterinv.mfg_id,
mfg_item_id = masterinv.mfg_item_id,
item_status_current = masterinv.item_status_current,
cm_unit_cost = masterinv.cm_unit_cost,
sync_dte = masterinv.sync_dte
}).ToList();
searchResult;
}
In the above code filter created on the basis of combination of combo box and text field
selection.
Out of these one filter is :
masterinv.inv_item_id = 'A' || masterinv.description = 'F' || vendor.vendor_name = 'V'
it may vary depend upon the combobox value selection. All cases of Combo box present in BuildQueryFilter Method.
PROBLEM :
I am not able to fire where clause in this join. Where i am going wrong ?
I think you cannot use those % with linq queries.
Instead of % you can use Contains()/StartsWith()/EndsWith()
refer this for more info...
How to do SQL Like % in Linq?
How to do a LIKE query with linq?
Or
Use Sql Methods..
where SqlMethods.Like(c.CustomerName, "%/abc/%")

Linq ExecuteCommand doesn't understand nulls

I'm having a problem when passing nulls to a ExecuteCommand() method using linq. My code is similar to the one that follows:
public void InsertCostumer(string name, int age, string address)
{
List<object> myList = new List<object>();
myList.Add(name);
myList.Add(age);
myList.Add(address);
StringBuilder queryInsert = new StringBuilder();
queryInsert.Append("insert into Customers(name, address) values ({0}, {1}, {2})");
this.myDataContext.ExecuteCommand(queryInsert.ToString(), myList.ToArray());
}
But, when a parameter is null (address, for instance), I get the following error: "A query parameter cannot be of type 'System.Object'."
The error doesn't occur if no parameters are null. I know the design in my example is a little poor, I just created a simplified example to focus on the problem. Any suggestions?
This is a known bug and Microsoft does not intend to fix it...
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=305114&wa=wsignin1.0
The work around is to either:
Drop into ADO.NET and execute the SQL Command directly
Format the string you're executing yourself and call ExecuteCommand with an empty object array (new object[0])
The second isn't a good idea as it opens you up to SQL inject attacks, but its a quick hack.
Kevin is right.
an example of his work around #1 in LinqPad. Need this (Object)s??DBNull.Value
string s = null;
//ExecuteCommand("insert into T(C1) values({0})", s); //Exception
SqlCommand cmd= new SqlCommand(){
CommandText = "insert into T(C1) values(#P0)",
Connection = new SqlConnection(this.Connection.ConnectionString),
};
//cmd.Parameters.AddWithValue("#P0", s); //SqlException
cmd.Parameters.AddWithValue("#P0", (Object)s??DBNull.Value);
cmd.Connection.Open();
cmd.ExecuteNonQuery();
cmd.Connection.Close();
Ts.OrderByDescending(t=>t.ID).Take(1).Dump();
have you tried assigning a value to those that are null? Meaning (pseudo):
If address is null then address = ""
or
If age is < 0 then age = 0
then add it to myList
or you could always use a Ternary operator:
name = name.Length < 1 ? "" : name;
age = age < 1 ? Int32.MinValue : age;
then add it to myList
Same issue for me. So stupid of MS not to fix that.
Here's my solution although I did not support all parameter types but ya get the idea. I stuck this in the DataContext class so it looks like it's built in to Linq :) .
public int ExecuteCommandEx(string sCommand, params object[] parameters)
{
object[] newParams = new object[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i] == null)
newParams[i] = "NULL";
else if (parameters[i] is System.Guid || parameters[i] is System.String || parameters[i] is System.DateTime)
newParams[i] = string.Format("'{0}'", parameters[i]);
else if (parameters[i] is System.Int32 || parameters[i] is System.Int16)
newParams[i] = string.Format("{0}", parameters[i]);
else
{
string sNotSupportedMsg = string.Format("Type of param {0} not currently supported.", parameters[i].GetType());
System.Diagnostics.Debug.Assert(false, sNotSupportedMsg);
}
}
return ExecuteCommand(string.Format(sCommand, newParams));
}
I use something like this (note I'm using the SO "IDE" so I can't, guarantee this will compile or work correctly but you'll get the idea)
public void InsertCostumer(string name, int age, string address)
{
List<object> myList = new List<object>();
myList.Add(name);
myList.Add(age);
myList.Add(address);
StringBuilder queryInsert = new StringBuilder();
queryInsert.Append("insert into Customers(name, age, address) values (");
int i = 0;
foreach (var param in myList.ToArray())
{
if (param == null)
{
queryInsert.Append("null, ");
myList.RemoveAt(i);
}
else
{
queryInsert.Append("{" + i + "}, ");
i++;
}
}
queryInsert.Remove(queryInsert.Length - 2, 2);
queryInsert.Append(")");
this.myDataContext.ExecuteCommand(queryInsert.ToString(), myList.ToArray());
}
I made a generic ParamArray Function to pass in the parms I normally would pass into the ExecuteCommand. Then have it pass back the uninterpretted SQL parms and a list of objects actually passed in.
Public Sub CommitRecords(ByVal InItems As List(Of Item)
Dim db As New DataContext(ConnectionString)
Try
For Each oItem In InItems
With oItem
Dim strParms As String = ""
Dim collParms = BuildExecuteCommandParms(strParms, .MapValue1, .MapValue2, .MapValue3, .MapValue4, .MapValue5, .MapValue6)
db.ExecuteCommand("Insert Into ItemTable (Value1, Value2, Value3, Value4, Value5, Value6)" & vbCrLf & _
"Values (" & strParms & ")", _
collParms.ToArray)
End With
Next
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Public Function BuildExecuteCommandParms(ByRef strParms As String, ByVal ParamArray InParms As Object()) As List(Of Object)
Dim i As Integer = 0
Dim collOutParms As New List(Of Object)
For Each oParm In InParms
If i <> 0 Then strParms &= ", "
If oParm Is Nothing Then
strParms &= "NULL"
Else
strParms &= "{" & i & "}"
collOutParms.Add(oParm)
End If
i += 1
Next
Return collOutParms
End Function
I usually use this sort of thing, not ideal but it's gets it done if you're stuck
if (myObject != null)
{
foreach (var p in ct.GetType().GetProperties())
{
if (p.GetValue(myObject , null) == null)
{
if (p.PropertyType == typeof(string))
{
p.SetValue(myObject , "Empty", null);
}
if (p.PropertyType == typeof(int))
{
p.SetValue(myObject , 0, null);
}
if (p.PropertyType == typeof(int?))
{
p.SetValue(myObject , 0, null);
}
}
}
}
This makes sure each value in the object has a value before you use the parameters in ExecuteCommand. Again, not ideal, but it works.
I didn't like using string.format since (as the current selected answer to this question says) you're opening yourself to SQL injection.
So I solved the problem by iterating through the parameters and if the parameter is null, I add NULL as a string to the command text, if it it not null, I add a placeholder that will be replaced (similar to string.format) with values by ExecuteQuery (which does the SQL injection checks).
private static T ExecuteSingle<T>(string connectionString, string sprocName, params object[] sprocParameters)
where T : class
{
var commandText = sprocName;
if (sprocParameters.Length > 0)
{
// http://stackoverflow.com/questions/859985/linq-executecommand-doesnt-understand-nulls
int counter = 0;
var nulledPlaceholders = sprocParameters
.Select(x => x == null ? "NULL" : "{" + counter ++ + "}");
commandText += " " + string.Join(",", nulledPlaceholders);
sprocParameters = sprocParameters.Where(x => x != null).ToArray();
}
var connection = new SqlConnection(connectionString);
var dc = new DataContext(connection);
return dc.ExecuteQuery<T>(commandText, sprocParameters).SingleOrDefault();
}
internal static class DataContextExtensions
{
public static int ExecuteCommandEx(this DataContext context, string command, params object[] parameters)
{
if (context == null)
throw new ArgumentNullException("context");
if (parameters != null && parameters.Length > 0)
parameters = parameters.Select(p => p ?? "NULL").ToArray();
return context.ExecuteCommand(command, parameters);
}
}
why not use nullable values?
public void InsertCostumer(string? name, int? age, string? address)
{
List<object> myList = new List<object>();
myList.Add(name.GetValueOrDefault());
myList.Add(age.GetValueOrDefault());
myList.Add(address.GetValueOrDefault());
StringBuilder queryInsert = new StringBuilder();
queryInsert.Append("insert into Customers(name, address) values ({0}, {1}, {2})");
this.myDataContext.ExecuteCommand(queryInsert.ToString(), myList.ToArray());
}
In .NET, a null/nothing string does not evaluate to an empty string, i.e. "". If you want "", then that has to be the value of the string, or if you want to represent null/nothing in SQL, you have to manually write out "NULL" if your .NET string is in fact null/nothing.
All the execute command does, is execute a SQL query, provide by you, as a String. it doesn't do anything else special in terms of that SQL string.
So, for the Execute Command to work, you have to pass in a valid SQL string, you have to manually construct the string correctly.

Categories

Resources