LinqToSql DataContext with extracted interface doesn't compose SQL for functions - linq-to-sql

I have setup a test database and console app to confirm the following:
Given a SQL Database with the following function:
CREATE FUNCTION ufn_GTFO
(
#Guid as uniqueidentifier
)
RETURNS VARCHAR(100)
AS
BEGIN
-- Declare the return variable here
DECLARE #Result as VARCHAR(100)
-- Add the T-SQL statements to compute the return value here
SELECT #Result = 'This is a test'
-- Return the result of the function
RETURN #Result
END
GO
And the following table:
CREATE TABLE [dbo].[Test](
[PKey] [int] IDENTITY(1,1) NOT NULL,
[WFT] [uniqueidentifier] NULL,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED
(
[PKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
The Scalar valued function ufn_GTFO is normally composable, such that the following C#:
static void ConcreteTest()
{
DataClasses1DataContext db = new DataClasses1DataContext();
var q = from t in db.Tests
select new { t.PKey, GTFO = db.ufn_GTFO(t.WFT) };
var p = q.ToArray();
}
Is translated into the following SQL:
SELECT [t0].[PKey], [dbo].[ufn_GTFO]([t0].[WFT]) AS [GTFO] FROM [dbo].[Test] AS [t0]
However, if I use the refactor -> extract interface on the DataContext, and use an instance of that:
static void InterfaceTest()
{
IDataClasses1DataContext db = new DataClasses1DataContext();
var q = from t in db.Tests
select new { t.PKey, GTFO = db.ufn_GTFO(t.WFT) };
var p = q.ToArray();
}
I get the following SQL, and calls to ufn_GTFO occur once for each record as .ToArray() enumerates the results.
SELECT [t0].[PKey], [t0].[WFT] AS [guid]
FROM [dbo].[Test] AS [t0]
So, my question is why does this happen and what can I do to prevent it while still using the interface?
Update 1: I've compared the IL generated for the concrete method versus the interface method, and they differ only in the reference to the interface and a compiler generated display class that doesn't seem to have any bearing on the result.

linq to sql relies heavily on attributes to map class (datacontext) members to database members. Your interfaces likely does not have the FunctionAttribute over the ufn_GRFO method. without that attribute the link between the C# world and the SQL function is severed; however. your interface is also not decorated with the DatabaseAttribute as that attribute is only valid on classes and not interfaces. without that attribute you have severed the link between C# and the whole database. by default Linq uses the AttributeMappingSource to map type members in the DataContext. Sense the interface is the type in question and that type has severed the link to that database, due to the fact that you can't apply the database attribute to it, your default mapping source will not map the function ufn_GRFO to the database function, rather linq will treat it as a .NET function to call with the data from the WFT field.
I suspect the way around this issue is to provide your datacontext with a custom MappingSource implementation and that ignores the DatabaseAttribute and only considers the attributes on the properties.
http://msdn.microsoft.com/en-us/library/system.data.linq.mapping.functionattribute.aspx
http://msdn.microsoft.com/en-us/library/system.data.linq.mapping.databaseattribute.aspx
http://msdn.microsoft.com/en-us/library/system.data.linq.mapping.attributemappingsource.aspx
http://msdn.microsoft.com/en-us/library/system.data.linq.mapping.mappingsource.aspx

Related

Stored Procedure for multiple value with single parameter in SQL Server

I have this form in C# with a listbox where I selected 4 items. Now I want to make single stored procedure using which I can find data from single table for all this selected item with single parameter.
As I am a beginner when it comes to SQL Server, I completely don't know this type of procedure
Thanks, but this is not my question's answer
I want a Single Stored Procedure for all Items which are selected in ListBox
Create Procedure procedureName
(
#ItemName varchar(50),
)
AS
BEGIN
(
Select * from item_master where item_name = #ItemName
)
END
by this Query i can find data for one ItemName, but i want for all selected Items in Listbox, even I don't know the C# code also,
so plz help me....
This is a very simple example that does what you want. You would not want to use hard-coded connection strings, especially in-line, and you would want error-handling, but I am going for as much clarity as possible. You would also probably want to make the column length greater than 50 characters, but I made it match your column definition.
Also, I would recommend a generic approach, passing keys (column names) and values, so as to be able to use it for any sort of criteria, but you asked that I keep it to exactly what you require, so I trimmed it down to the essential.
This example returns all the Employees with FirstName matching any in the list passed to the stored procedure (as a user-defined table type).
First, create a user-defined table type (to hold the values you want to pass to the stored procedure) in your SQL Server database as follows:
CREATE TYPE [dbo].[FilterValues] AS TABLE(
[Value] [varchar](50) NOT NULL,
PRIMARY KEY CLUSTERED
(
[Value] ASC
)
)
The stored procedure to return the Employees looks as follows (note that it has the user-defined table type as the type of the single parameter passed in):
CREATE PROCEDURE [dbo].[GetEmployees] (
#FirstNameFilterValues dbo.FilterValues READONLY
)
AS
BEGIN
SELECT * FROM Employees
INNER JOIN #FirstNameFilterValues fv ON fv.Value = Employees.FirstName;
END
That's the SQL Server side done. To call it from C#, you can create a DataTable with a single column matching the column name and populate it with the values you want. In this simple example, I populate it with two names, but it could be as many as you want.
var filterValuesDataTable = new DataTable();
filterValuesDataTable.Columns.Add(new DataColumn("Value", typeof(string)) { AllowDBNull = false });
filterValuesDataTable.Rows.Add("Frodo");
filterValuesDataTable.Rows.Add("Sam");
using (var connection = new SqlConnection("server=.;Initial Catalog=Test;Integrated Security=True;"))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = "GetEmployees";
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#FirstNameFilterValues", filterValuesDataTable);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine("{0} {1}", reader["FirstName"], reader["LastName"]);
}
reader.Close();
}
}
connection.Close();
}

Entity Framework 5 - T4 generated context class causing 'duplicate parameter name'

I'm using EF5.0 in an ASP.NET MVC app. My Entity Model is named 'DataModel'. Included in the model is a table-valued function that exists in my MSSQL database, named MatchingEntries. It returns a table of integer ids.
I've looked at the DataModel.Context.cs file, that gets generated via the .tt (T4) template file. It has the following code in it:
[EdmFunction("DataEntities", "MatchingEntries")]
public virtual IQueryable<Nullable<int>> MatchingEntries(string term)
{
var termParameter = term != null ?
new ObjectParameter("Term", term) :
new ObjectParameter("Term", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<Nullable<int>>("[DataEntities].[MatchingEntries](#Term)", termParameter);
}
The error I am getting results from using this method twice within the one query, such as:
IQueryable<int> one = db.MatchingEntries("\"one*\"");
IQueryable<int> two = db.MatchingEntries("\"two*\"");
List<int> both = one.Intersect(two).ToList();
The error is:
A parameter named 'Term' already exists in the parameter collection. Parameter names must be unique in the parameter collection.
Parameter name: parameter
Is this a known limitation of the classes generated from an EDMX for table-valued functions? With LINQ2SQL I am able to execute this a a single query to the database (that does a JOIN between the 2 outputs from MatchingEntries) and it replaces the parameter name #Term with #p0 and #p1 for the two different instances of the call. I'd like to make Entity Framework do the same.
So, my question is, how can I get EF to work in the same manner and avoid the 'Duplicate parameter' error?
My fallback is to evaluate each call to db.MatchingEntries separately, by putting ToList() after them. My other idea has been to replace the ObjectParameter name in the T4 generated Context.cs class with something randomly generated each time. These feel like hacks that I should be able to avoid.
This answer is Linq to Entities specific. This doesn't have to be done in Linq to SQL (Linqpad).
Thanks to this question I got a pointer to a viable solution:
extend the autogenerated DBContext class (partial class)
add a method with two parameters in the partial class
at calling, pass an index as second parameter
Detailed Answer:
DataEntitys.my.cs:
[EdmFunction("DataEntities", "MatchingEntries")]
public virtual IQueryable<Nullable<int>> MatchingEntries(string term, int index)
{
string param_name = String.Format("k_{0}", index);
var termParameter = term != null ?
new ObjectParameter(param_name, term) :
new ObjectParameter(param_name, typeof(string));
return ((IObjectContextAdapter)this).
ObjectContext.CreateQuery<Nullable<int>>(
String.Format("[DataEntities].[MatchingEntries](#{0})", param_name),
termParameter);
}
Call the function:
foreach (string teil in such)
{
index++;
if (teil.Trim() != "")
res = res.Join(db.MatchingEntries("\"" + teil + "*\"", index), l => l.ID, s => s.KEY, (l, s) => l);
}

Enable hbm2ddl.keywords=auto-quote in Fluent NHibernate

I have made a tiny software tool that allows me to display or run SQL generated from NHibernate. I made this because hbm2ddl.auto is not recommended for production.
I have one problem: when I generate the SQL I always get the infamous Index column unquoted, because I need .AsList() mappings. This prevents me to run the SQL.
In theory, if I had an XML configuration of NHibernate I could use hbm2ddl.keywords tag, but unfortunately since my tool is designed as a DBA-supporting tool for multiple environments, I must use a programmatic approach.
My approach (redundant) is the following:
private static Configuration BuildNHConfig(string connectionString, DbType dbType, out Dialect requiredDialect)
{
IPersistenceConfigurer persistenceConfigurer;
switch (dbType)
{
case DbType.MySQL:
{
persistenceConfigurer =
MySQLConfiguration
.Standard
.Dialect<MySQL5Dialect>()
.Driver<MySqlDataDriver>()
.FormatSql()
.ShowSql()
.ConnectionString(connectionString);
requiredDialect = new MySQL5Dialect();
break;
}
case DbType.MsSqlAzure:
{
persistenceConfigurer = MsSqlConfiguration.MsSql2008
.Dialect<MsSqlAzure2008Dialect>()
.Driver<SqlClientDriver>()
.FormatSql()
.ShowSql()
.ConnectionString(connectionString);
requiredDialect = new MsSqlAzure2008Dialect();
break;
}
default:
{
throw new NotImplementedException();
}
}
FluentConfiguration fc = Fluently.Configure()
.Database(persistenceConfigurer)
.ExposeConfiguration(
cfg => cfg.SetProperty("hbm2ddl.keywords", "keywords")
.SetProperty("hbm2ddl.auto", "none"))
.Mappings(
m => m.FluentMappings.AddFromAssemblyOf<NHibernateFactory>());
Configuration ret = fc.BuildConfiguration();
SchemaMetadataUpdater.QuoteTableAndColumns(ret);
return ret;
}
...
public static void GenerateSql(MainWindowViewModel viewModel)
{
Dialect requiredDialect;
Configuration cfg = BuildNHConfig(viewModel.ConnectionString, viewModel.DbType.Value, out requiredDialect);
StringBuilder sqlBuilder = new StringBuilder();
foreach (string sqlLine in cfg.GenerateSchemaCreationScript(requiredDialect))
sqlBuilder.AppendLine(sqlLine);
viewModel.Sql = sqlBuilder.ToString();
}
Explanation: when I want to set the ViewModel's SQL to display on a TextBox (yea, this is WPF) I initialize the configuration programmatically with connection string given in ViewModel and choose the dialect/provider accordingly. When I Fluently Configure NHibernate I both set hbm2ddl.keywords (tried both auto-quote and keywords, this being the default) and, following this blog post, I also use the SchemaMetadataUpdater.
The result is that I'm always presented with SQL like
create table `OrderHistoryEvent` (Id BIGINT NOT NULL AUTO_INCREMENT, EventType VARCHAR(255) not null, EventTime DATETIME not null, EntityType VARCHAR(255), Comments VARCHAR(255), Order_id VARCHAR(255), Index INTEGER, primary key (Id))
where the guilty Index column is not quoted.
The question is: given a programmatic and fluent configuration of NHibernate, how do I tell NHibernate to quote any reserved word in the SQL exported by GenerateSchemaCreationScript?
I have found a workaround: when I generate the update script (the one that runs with hbm2ddl.auto=update) the script is correctly quoted.
The infamous Index column has been already discussed and from my findings it's hardcoded in FNH (ToManyBase.cs, method public T AsList()).
Since the update script is a perfectly working creational script on an empty database, changing the code to generate an update script on an empty DB should equal generating a creational script.
This happens only because I want to generate the script on my own. There is probably a bug in NHibernate that only activates when you call GenerateSchemaCreationScript and not when you let your SessionFactory build the DB for you

Getting identity column value using entity framework 4.1

I'm using entity framework 4.1 (VS 2010, SQL Server 2012) for inserting data into a database.
First I create an instance of an object, fill the properties with values and call AddObject(), like this:
VideoData videodata = new VideoData();
videodata.StartCaptureTime = startCaptureTime;
videodata.EndCaptureTime = endCaptureTime;
videodata.CameraID = CameraID;
using (var context = new PercEntities())
{
if (context.VideoDatas.Where(c => c.VideoID == videoID).Count() == 0)
{
var videoData = new VideoData
{
StartCaptureTime = startCaptureTime,
EndCaptureTime = endCaptureTime,
CameraID = CameraID,
};
context.VideoDatas.AddObject(videoData);
context.SaveChanges();
}
}
The thing is, that the table in the database has an identity column:
VideoID int IDENTITY(1,1)
and I need to get the value inserted by the identity function in order to fill additional objects, that have the VideoID as a foreign key. for example:
FrameData frameData = new FrameData();
frameData.VideoID = videodata.VideoID;
frameData.Path = path;
The only thing I could think of was to query for the max identity right after AddObject(videoData), but I'm afraid of race conditions.
I'm new to Entity Framework, so I'd be happy for any guidance on this.
If you have other objects which require VideoID as FK you just need to correctly configure your navigation properties between VideoData and those other types and EF will handle it for you.
Call to AddObject does not insert your data to database and because of that you cannot get the identity value after this call. Only call to SaveChanges will push all your changes to database and during this call EF will handle referential integrity internally (but only if you have your model correctly configured with relations).
After calling SaveChanges your VideoID should be populated automatically if you have everything correctly configured.

Bulk Update with LINQ to SQL

Is there a way to do a bulk update on a collection with LINQ? Currently if I have a List<myObject> and I want to update column1 to equal TEST for every row in the List I would setup a foreach loop and then for each individual object I would set the value and then save it. This works fine but I was just wondering if there was some LINQ method out there where I could do something like myOject.BulkUpdate(columnName, value)?
Your requirement here is entirely possible using Linq expressions and Terry Aney's excellent library on this topic.
Batch Updates and Deletes with LINQ to SQL
An update in the terms of the example you gave would be as follows:
using BTR.Core.Linq;
...
Context.myObjects.UpdateBatch
(
Context.myObjects.Where(x => x.columnName != value),
x => new myObject { columnName = value}
);
Edit (2017-01-20): It's worth nothing this is now available in the form of a NuGet package # https://www.nuget.org/packages/LinqPost/.
Install-Package LinqPost
Sounds like you're using LINQ To SQL, and you've got the basics laid out already.
LINQ To SQL is about abstracting tables into classes, and doesn't really provide the 'silver bullet' or one-liner you are looking for.
The only way to do that is to achieve your one-liner would be to make a stored proc to take that column name and new value, and implement that logic yourself.
db.MassUpdateTableColumn("Customer", "Name", "TEST");
....
CREATE PROC MassUpdateTableColumn
#TableName varchar(100), #ColumnName varchar(100), #NewVal varchar(100)
AS
/*your dynamic SQL to update a table column with a new val. */
Otherwise, it's as you describe:
List<Customer> myCusts = db.Customers.ToList();
foreach(Customer c in myCusts)
{
c.Name = "TEST";
}
db.SubmitChanges();
LINQ to SQL (or EF for that matter), is all about bringing objects into memory, manipulating them, and then updating them with separate database requests for each row.
In cases where you don't need to hydrate the entire object on the client, it is much better to use server side operations (stored procs, TSQL) instead of LINQ. You can use the LINQ providers to issue TSQL against the database. For example, with LINQ to SQL you can use context.ExecuteCommand("Update table set field=value where condition"), just watch out for SQL Injection.
EF Core 7.0 introduces Bulk Update and Bulk Delete.
For example, consider the following LINQ query terminated with a call to ExecuteUpdateAsync:
var priorToDateTime = new DateTime(priorToYear, 1, 1);
await context.Tags
.Where(t => t.Posts.All(e => e.PublishedOn < priorToDateTime))
.ExecuteUpdateAsync(s => s.SetProperty(t => t.Text, t => t.Text + " (old)"));
This generates SQL to immediately update the “Text” column of all tags for posts published before the given year:
UPDATE [t]
SET [t].[Text] = [t].[Text] + N' (old)'
FROM [Tags] AS [t]
WHERE NOT EXISTS (
SELECT 1
FROM [PostTag] AS [p]
INNER JOIN [Posts] AS [p0] ON [p].[PostsId] = [p0].[Id]
WHERE [t].[Id] = [p].[TagsId] AND [p0].[PublishedOn] < #__priorToDateTime_1)