SubSonic 2.1: Need explanation of SqlQuery.ExecuteJoinedDataSet() - c#

I have a SqlQuery that looks like this:
SqlQuery query =
DB.Select(
Order.Schema.TableName + ".*",
OrderDetail.Schema.TableName + ".*")
.From<Order>()
.InnerJoin<OrderDetail>()
.Where(Order.IdColumn).IsEqualTo(1);
Now I would expect the Method SqlQuery.ExecuteJoindDataSet() to generate a DataSet for me, that contains 2 DataTables (one for Orders, one for OrderDetails) and put a DataRelation into the DataSet, so I don't have to do this all by hand.
But ExecuteJoinedDataSet() only generates one Table containing all the data from Order but not from OrderDetail:
// Order = 104 Columns
// OrderDetail = 74 Columns
query.ExecuteJoinedDataSet().Tables.Count => 1
query.ExecuteJoinedDataSet().Tables[0].Columns.Count => 104
query.ExecuteDataSet().Tables[0].Columns.Count => 177
I think I am on the right way, but can someone please tell me, what I am doing wrong?
The purpose of this is that the Printing Component I use in my project does not accept generic objects, but DataSet's as a DataSource.

ExecuteJoinedDataSet actually uses all the table columns from the first table and replaces the value in any column that has a foreign key with the first non-forgeign-key value from the corresponding row in the foreign table. It does inner joins for non-null foreign-key columns, and left joins for nullable ones.
So for this schema
create table tblBaseType
(
id int not null primary key identity(1,1),
name not null varchar(100) unique
)
create table tblBaseLocation
(
id int not null primary key identity(1,1),
name not null varchar(100) unique
)
create table tblBase
(
id int not null primary key identity(1,1),
name varchar(100) not null unique,
baseTypeID int not null references tblBaseType(id),
baseLocationID int null references tblBaseLocation(id)
)
and a SqlQuery like
SqlQuery q = new Select().From(TblBase.Schema).Where(TblBase.IdColumn).IsEqualTo(1);
DataSet ds = q.ExecuteJoinedDataSet();
this approximate sql would be generated:
select tblBase.Id,
tblBase.Name,
tblBaseType.Name as baseTypeId,
tblBaseLocation.name as baseLocationId
from tblBase
inner join tblBaseType on tblBase.baseTypeID = tblBaseType.id
left join tblBaseLocation on tblBase.baseLocationID = tblBaseLocation.id
The actual sql is fully qualified, this is just a rough from-scratch approximation.

Related

Using LINQ to insert data into a table that uses a sequence as primary key generator

I have a table which generates its primary key from a sequence (that just counts up from 0):
CREATE TABLE [dbo].[testTable](
[id] [int] NOT NULL,
[a] [int] NOT NULL,
CONSTRAINT [PK_testTable] PRIMARY KEY CLUSTERED ([id] ASC))
ALTER TABLE [dbo].[tblTestTable] ADD CONSTRAINT [DF_tblTestTable_id] DEFAULT (NEXT VALUE FOR [seq_PK_tblTestTable]) FOR [id]
I've used Visual Studio's O/R Designer to create the mapping files for the table; the id field is defined as:
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_id", DbType="Int NOT NULL", IsPrimaryKey=true)]
public int id {…}
and now I'm trying to insert data via LINQ.
var testTableRecord = new testTable()
{
a = 1,
};
db.Connection.Open();
db.testTables.InsertOnSubmit(testTableRecord);
db.SubmitChanges();
Console.WriteLine($"id: {testTableRecord.id}");
The problem I'm encountering is, that LINQ seems unable to handle the id generation via sequence as it sets the id implicitly to 0 when inserting.
When I set the id to CanBeNull, the insert fails because it tries to insert NULL into a non-nullable field.
When I set the id to IsDbGenerated, the insert works but it expects an IDENTITY field and tries to load the generated id with SELECT CONVERT(Int,SCOPE_IDENTITY()) AS [value]',N'#p0 int',#p0=1 and than sets the id in the object to null because SCOPE_IDENTITY() returns null…
I've been thinking about just using IsDbGenerated, destroying the LINQ object and querying the DB for the id, but I don't have anything unique to search for.
Unfortunately changing the id creation mechanism to IDENTITY is not an option.
Do I have to explicitly query the DB for the next sequence value and set the id manually?
Whats the best way to handle these inserts via LINQ?
PS: I need the id because I have to insert more data that uses the id as FK.
Looking at solutions from the raw sql perspective:
1.
INSERT INTO [dbo].[testTable] VALUES (NEXT VALUE FOR [dbo].[seq_PK_tblTestTable], 1)
Simply can't be done in LINQ to SQL as far as I can tell
2.
INSERT INTO [dbo].[testTable] (a) VALUES (1)
This can be achieved in LINQ to SQL by excluding the id property from the testTable entity.
If you need to retrieve ids from the table, you could create separate entities for inserting and querying:
public class testTableInsert {
[ColumnAttribute(...)]
public int a
}
public class testTableResult {
[ColumnAttribute(...)]
public int id
[ColumnAttribute(...)]
public int a
}
3.
DECLARE #nextId INT;
SELECT #nextId = NEXT VALUE FOR [dbo].[seq_PK_tblTestTable];
INSERT INTO [dbo].[testTable] VALUES (#nextId, 1)
As you mentioned, this can be essentially achieved by manually requesting the next id before each insert. If you go this route there are multiple ways to achieve it in your code, you can consider stored procedures or use the LINQ data context to manually execute the sql to retrieve the next sequence value.
Here's a code sample demonstrating how to extend the generated DataContext using partial methods.
public partial class MyDataContext : System.Data.Linq.DataContext
{
partial void InsertTestTable(TestTable instance)
{
using (var cmd = Connection.CreateCommand())
{
cmd.CommandText = "SELECT NEXT VALUE FOR [dbo].[seq_PK_TestTable] as NextId";
cmd.Transaction = Transaction;
int nextId = (int) cmd.ExecuteScalar();
instance.id = nextId;
ExecuteDynamicInsert(instance);
}
}
}
Once the above is implemented, you can safely insert entities like this, and they will generate the correct sequence id.
TestTable entity = new TestTable { a = 2 };
dataContext.TestTables.InsertOnSubmit(entity);
dataContext.SubmitChanges();
Your only hope is a pretty profound refactoring and use a stored procedure to insert records. The stored procedure can be mapped to the class's Insert method in the data context designer.
Using your table definition, the stored is nothing but this:
CREATE PROCEDURE InsertTestTable
(
#id int OUTPUT,
#a AS int
)
AS
BEGIN
INSERT dbo.testTable (a) VALUES (#a);
SET #id = (SELECT CONVERT(int, current_value)
FROM sys.sequences WHERE name = 'seq_PK_tblTestTable')
END
You can import this stored procedure into the context by dragging it from the Sql Object Explorer onto the designer surface, which will then look like this:
The next step is to click the testTable class and click the ellipses button for the Insert method (which got enabled by adding the stored procedure to the context):
And customize it as follows:
That's all. Now LINQ-to-SQL will generate a stored procedure call to insert a record, for example:
declare #p3 int
set #p3=8
declare #p5 int
set #p5=0
exec sp_executesql N'EXEC #RETURN_VALUE = [dbo].[InsertTestTable] #id = #p0 OUTPUT,
#a = #p1',N'#p0 int output,#p1 int,#RETURN_VALUE int output',
#p0=#p3 output,#p1=123,#RETURN_VALUE=#p5 output
select #p3, #p5
Of course you may have to wonder how long you're going to hang on to LINQ-to-SQL. Entity Framework Core has sequence support out of the box. And much more.

Entity Framework : Table with Composite Key issue : Violation of PRIMARY KEY constraint. Cannot insert duplicate key in object

I am using Entity Framework with edmx & sql database.
I have 3 Tables: 1)Product 2)Language 3)ProductDesc
Now ProductID of Product table & LangID of Language table are used as foreign key in ProductDesc and "ProductID + LangID" is composite key for ProductDesc table, ProductDesc table has 4 other desc columns & all are nvarchar.
now when I insert/update Product data, I am checking in
code if ProductDesc exist with ProductID P1 and LangID L1, it returns null and next lines of code tries to insert data in ProductDesc but gives error
Violation of PRIMARY KEY constraint 'PK_ProductDesc'. Cannot insert duplicate key in object 'dbo.ProductDesc', whereas there is no record with that combination of ProductID and LangID in table ProductDesc.
var pDesc = db.ProductDescs.FirstOrDefault(x => x.ProductID == ProductID && x.LangID == LangID);
if (pDesc == null)
{
pDesc = new ProductDesc();
pDesc.ProductID = ProductID;
pDesc.LangID = LangID;
db.ProductDescs.Add(pDesc);
}
db.SaveChanges();
This works fine when I am doing Insert/Update data from UI for one product.
But when I am using same function for bulk data which are inserted from windows service it causes this error.
My windows service reads data from azure queue and call same function to insert data for each product.
And from windows service around 100 records inserted successfully and then this issue occurred
You Will get the Same Error of Primary Key Violation when there are duplicates exists in the Data That you are inserting.
Suppose I have a table Like this
CREATE TABLE TableA
(
Id INT PRIMARY KEY,
Name VARCHAR(25)
)
I'm inserting a record with Id 1
INSERT INTO TableA
VALUES(1,'ABC')
It Will be inserted But Instead of the above, If I try to Insert 2 Records with the Same ID from the same Statement Like this
INSERT INTO TableA
VALUES(1,'ABC'),(1,'XYZ')
It Will Give me the Primary Key Violation error and Won't show any existing records on the Table.
So Make sure there are no Duplicates in your input Data

How can I use Dapper with a SELECT stored procedure containing an INNER JOIN between two tables?

I am experimenting with Dapper for the first time. I have two tables: Films and Ratings.
CREATE TABLE [dbo].[Films]
(
[Id] INT NOT NULL PRIMARY KEY,
[Title] VARCHAR(250) NOT NULL,
[Genre] VARCHAR(50) NOT NULL,
[RatingId] INT NOT NULL,
CONSTRAINT [FK_Films_To_Ratings] FOREIGN KEY (RatingId) REFERENCES Ratings(Id)
)
CREATE TABLE [dbo].[Ratings]
(
[Id] INT NOT NULL PRIMARY KEY,
[Name] VARCHAR(50) NOT NULL
)
I have written a stored procedure that will return all films in the Films table and joins with the Ratings table. Dapper works easily when I have the table structured using the same name between the FK and PK of the tables.
CREATE PROCEDURE [dbo].[GetFilms]
AS
BEGIN
SELECT
F.Id,
F.Title,
F.Genre,
F.RatingId,
R.Name
FROM
dbo.Films as F
INNER JOIN
dbo.Ratings as R
ON
F.RatingId = R.Id;
END
When my query runs, the film object becomes instantiated correctly but the RatingId was set to 0 (since int defaults to 0). The rating property contains the name, but the Id was also set to 0.
return this._db.Query<Film, Rating, Film>(
"dbo.GetFilms",
(f, r) =>
{
f.Rating = r;
return f;
},
splitOn: "RatingId",
commandType: CommandType.StoredProcedure
).ToList();
How can I successfully run my Dapper query and get the exact results I need when the column names are not identical like in my example? Table structure looks cleaner to me when I have columns named Ratings.Id instead of Ratings.RatingId.
Dapper will try and map property for property at the point you start splitting. You want your query to look like the following, and you can ditch the splitOn since you are splitting on Id.:
SELECT
F.Id,
F.Title,
F.Genre,
--Rating object starts here
R.Id,
R.Name
FROM
dbo.Films as F
INNER JOIN
dbo.Ratings as R
ON
F.RatingId = R.Id;

Insert Unique Primary Key into Database using Entity-Framework LINQ

I want to enter unique index value into my database when insert other table data. In this case my table index column ID is unique but not auto-increment.
I also want to check the maximum of index value in target table. If the table is empty then index value starts from 0, OR if it contains 1 row then the index value starts from 2.
Already I can successfully do this operation using a stored procedure. But I want to do this operation using Entity Framework when saving data.
My stored procedure is:
ALTER PROCEDURE [dbo].[CreatePerson]
#Name Nvarchar(50),
#Code Nvarchar(50)
AS
BEGIN
Declare #Id int
SET #Id = ISNULL(((SELECT MAX([id]) FROM dbo.tbl_Person)+1),'1')
Insert Into dbo.tbl_Person([Id], [Name], [Code])
Values (#id, #Name, #Code)
END
Can anyone tell me what the LINQ Command for this index value save process?
A straight translation of your SQL stored procedure into Linq would basically be something like this:
var newId = ctx.Persons.Any() ? ctx.Persons.Select(p => p.Id).Max() + 1 : 1;
var newPerson = new Person { Id = newId, Name = someName, Code = someCode };
ctx.Persons.Add(newPerson);
ctx.SaveChanges();
Note: this solution is certainly NOT recommended for inserting unique id's in a database, consider carefully the suggestions of Sharped and Magnus in the comments below the question to better use auto-increment or random guids to solve your problem.
With an auto-incremented id column your code would look like this:
var newPerson = new Person { Name = someName, Code = someCode };
ctx.Persons.Add(newPerson);
ctx.SaveChanges();
var newId = newPerson.Id;
Note that EF will update the id column of the newly created entity for you in code automagically.

How to handle mututally/recursively related tables?

I'm pretty new to databases and sql. I have a problem where I have two tables which both contain a foreign key to the primary key of the other. My problem is I have a large number of elements which can have multiple names in different languages, but MUST have a single primary name/language.
alt text http://img688.imageshack.us/img688/1121/11768540.png
Firstly, I want to know if this is possible, or should I just give up already? The obvious solution is to have an extra boolean field in the ElementName table to say IsDefaultName, but it adds some extra complexity for querying and updating. If this is the best solution, how do I constrain the ElementName table to not accept any submission if IsDefaultName is set and the table already has an entry with the same ElementId and IsDefaultName set (or would I need to query this manually)?
I'm attempting to use LINQ to SQL here. The code I'm using to attempt to insert new items throws an exception at SubmitChanges with The INSERT statement conflicted with the FOREIGN KEY constraint "FK_ElementName_Element". I can understand why this is, but wondering if there's a fix/better solution.
var db = new MyDBDataContext();
var element = new Element();
var elementName = new ElementName() {
ElementName1 = "MyElement",
Language = "English",
};
element.ElementName = elementName;
db.Elements.InsertOnSubmit(element);
db.ElementNames.InsertOnSubmit(elementName);
db.SubmitChanges();
Solution 1
element
------------------
element_id
~....
element_name
------------------
element_name_id
fk_element_id
name
language_id
is_default_name Default ( 0 )
Trigger:
if ( ( select count ( 1 ) from element_name where is_default_name = 1 ) > 1 )
BEGIN
raisError ( 'only 1 element_name may be marked is_default_name = true.', 16, 1 );
END
Solution 2
element
------------------
element_id ( pk )
~....
element_name
------------------
element_name_id ( pk )
fk_element_id
name
language_id
element_name_default
------------------
fk_element_id
fk_element_name_id
( pk - fk_element_id, fk_element_name_id )
Solution 3
element
------------------
element_id
fk_element_name_id_default NULL
~....
element_name
------------------
element_name_id
fk_element_id
name
language_id
order of code:
* Insert to element_name
* update of element
I would stick with what you had, cause it is just fine, just:
db.Elements.InsertOnSubmit(element);
db.ElementNames.InsertOnSubmit(elementName);
//I don't know this syntax to say
// set the property of element.fk_element_name_id_default
// to the newly inserted elementName from above
db.Elements.?.?
Why not simply use a self-join like so:
Create Table Elements(
ElementId... Not Null Primary Key
, DefaultElementId ... Not Null
References Elements( ElementId )
, Name ...
, Language ...
)
The default name is the one where ElementId = DefaultElementId.
Btw, this is a place where a guid PK is nicer than an identity column. With a guid PK, you could generate both the ElementId and DefaultElementId from the client. If you are using an identity column with this schema, you'll probably have to create a "Unknown" elementId with a known value like zero so that you can do the insert and then turn around and do an update all in a single transaction.
** ADDITION **
Given what you said in comments, it sounds like you are trying to localize the elements data. My inclination would be to recommend adding a non-nullable "Name" column to the Elements table which represents the language neutral or default language name. Your ElementNames table would have a foreign key to the Elements table and would only populate that table when you localized an element name. Your queries would then need to coalesce on the requested language name and the name in the elements table if the requested language did not have a localized name.

Categories

Resources