Entity Framework: Return GUID from code-first stored procedure insert? - entity-framework

I have an InsertPerson stored procedure and i need it to return a GUID (Uniqueidentifier) into my person object that is being created. Is this even possible with entity framework codefirst? I've tried everything and entity framework is ignoring the guid im trying to return using an output parameter. Does anyone have an example if this is possible?
Person.cs:
public class Person
{
public Guid Id { get; set; }
public string FirstName { get; set; }
}
Stored Procedure:
CREATE PROCEDURE [dbo].[InsertPerson]
#KeyPlayerId UNIQUEIDENTIFIER ,
#FirstNameNVARCHAR(255)
AS
-- Perform Insert
insert into [dbo.].[Person]....
-- Return GUID
select #Id as [Id];
END;

I was able to accomplish it by telling Entity Framework that the Id is a Database Generated value. I mapped the Insert procedure to InsertPerson in the PersonMap class and use that when the model is created in the OnModelCreating method. In the stored procedure I generate a new Id and pass this back to Entity Framework. Hope this helps!
PersonMap.cs
public class PersonMap : EntityTypeConfiguration<Person>
{
public PersonMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Tell Entity Framework the database will generate the key.
this.Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// Properties
this.Property(t => t.FirstName)
.IsRequired()
.HasMaxLength(255);
//Map to Stored Procedure
this.MapToStoredProcedures(s => s.Insert(i => i.HasName("InsertPerson")));
}
}
OnModelCreating
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new PersonMap());
}
InsertPerson Stored Procedure
CREATE PROCEDURE InsertPerson
-- Since Id is marked as Database Generated we only need
-- a parameter for First Name
#FirstName nvarchar(255) = 0
AS
-- Variable to hold new Id
DECLARE #Id uniqueidentifier
-- Generate a new Id using NEWID function that returns a unique identifier
SET #Id = NEWID()
-- Perform Insert
INSERT INTO [dbo].[Person] VALUES (#Id, #FirstName)
-- Return the Id to Entity Framework
SELECT #Id AS 'Id'

I think this may help you:
var sqlConnection1 = new SqlConnection("Your Connection String");
var cmd = new SqlCommand();
var reader;
cmd.CommandText = "StoredProcedureName";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = sqlConnection1;
sqlConnection1.Open();
reader = cmd.ExecuteReader();
rdr = cmd.ExecuteReader();
var outputValue = Guid.Parse(cmd.Parameters["#Response"].Value.ToString());
sqlConnection1.Close();

Related

Stop empty strings at the database level with EF code first

Consider the following POCO entity for Entity Framework Code First:
public class Foo
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
}
Which will generate the following table:
CREATE TABLE [dbo].[Foo] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (100) NOT NULL,
CONSTRAINT [PK_dbo.Foo] PRIMARY KEY CLUSTERED ([Id] ASC)
);
Now, I understand that the default behavior of EF is to convert empty strings to null. So even if I explicitly feed it an empty string I will get a validation exception, which is perfect. The following code will throw a DbEntityValidationException:
var f = new Foo { Name = "" };
context.Foos.Add(f);
context.SaveChanges();
But, the problem is if I have an external application which accesses the database directly, I can perform the following query and it succeeds:
insert into dbo.Foo(Name)
values ('')
The best solution is arguably to not allow anyone to connect directly to the database and force them through a business layer. In reality however this may not always be possible. Especially if, say, I myself am importing external data via an SSIS package.
My best understanding says that applications should be set up to reject as much bad data at the lowest level possible. In this case this would mean the at database level. So if were creating the database the old fashioned way, I would add a constraint to check (Name <> '') and stop dirty data from ever being inserted in the first place.
Is there a way to get EF Code First to generate this constraint for me, or some other way to get it to enforce a non-empty-string (minimum length 1) at the database level - preferably using an attribute? Or is my only recourse to add the constraint manually in a migration?
There is MinLength attribute but it does not enforce the constraint on database level, you should add this constraint using migration I think.
public partial class test : DbMigration
{
public override void Up()
{
Sql("ALTER TABLE [dbo].[YOUR_TABLE] ADD CONSTRAINT " +
"[MinLengthConstraint] CHECK (DATALENGTH([your_column]) > 0)");
}
public override void Down()
{
Sql("ALTER TABLE [dbo].[YOUR_TABLE] DROP CONSTRAINT [MinLengthConstraint]");
}
}
You can add sql code generators for EF to generate these codes for MinLength attribute, I'll give you a simplified hint here:
First mark properties with MinLength
public class Test
{
public int Id { get; set; }
[MinLength(1)]
public string Name { get; set; }
}
Add MinLenghtAttribute to conventions and provide the value, which is the Length :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<MinLengthAttribute, int>(
"MinLength",
(property, attributes) => attributes.Single().Length));
}
the generated code for migration will be:
CreateTable(
"dbo.Tests",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(
annotations: new Dictionary<string, AnnotationValues>
{
{
"MinLength",
new AnnotationValues(oldValue: null, newValue: "1")
},
}),
})
.PrimaryKey(t => t.Id);
Override the SqlServerMigrationSqlGenerator to use this convention in order to generate the constraint sql code:
public class ExtendedSqlGenerator : SqlServerMigrationSqlGenerator
{
protected override void Generate(AddColumnOperation addColumnOperation)
{
base.Generate(addColumnOperation);
AddConstraint(addColumnOperation.Column, addColumnOperation.Table);
}
protected override void Generate(CreateTableOperation createTableOperation)
{
base.Generate(createTableOperation);
foreach (var col in createTableOperation.Columns)
AddConstraint(col, createTableOperation.Name);
}
private void AddConstraint(ColumnModel column, string tableName)
{
AnnotationValues values;
if (column.Annotations.TryGetValue("MinLength", out values))
{
var sql = string.Format("ALTER TABLE {0} ADD CONSTRAINT " +
"[MinLengthConstraint] CHECK (DATALENGTH([{1}]) >= {2})"
,tableName, column.Name, values.NewValue);
Generate(new SqlOperation(sql));
}
}
}
the code above contains generation for AddColumn and CreateTable operations you must add codes for AlterColumn, DropTable and DropColumns as well.
Register the new code generator:
internal sealed class Configuration : DbMigrationsConfiguration<TestContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
SetSqlGenerator("System.Data.SqlClient", new ExtendedSqlGenerator());
}
}

Entity Framework override DatabaseGeneratedOption.Identity

I have a model with DatabaseGeneratedOption.Identity, but in a specific situation, would like to save an entity with a specific value for primary key;
ie force id = 1
If I just assign the value and save, the id gets overwritten by the auto-generated value.
var model = new User {Id = 1};
dbContext.SaveChanges();
Asser.AreEqual(1, model.Id); // false
you can set identity insert on before save change. This is sample.
var model = new User {Id = 1};
dbContext.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[User] ON");
dbContext.SaveChanges();
dbContext.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[User] OFF");
Asser.AreEqual(1, model.Id);
Replace [dbo].[User] with the name of your user table.
I did it via this method:
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public new int Id { get; set; }
//Rest of the fields...
}
You could also make a class that overrides User.

EF Code first error when seeding with composite key

I have an entity with a composite, primary key (Id and TreeVersion). I have another entity with values that reference this key.
First entity:
class ClassificationType
{
[Key]
[Column(Order = 1)
public int Id {get;set;}
[Key]
[Column(Order = 2, Type="varchar")]
[StringLength("100")]
public string TreeVersion {get;set}
}
Second entity:
class ClassConfig
{
public int Id {get;set;}
public string Name {get;set;}
public int? ClassificationTypeId { get; set; }
[Column(TypeName = "varchar")]
[StringLength(10)]
public string TreeVersion { get; set; }
}
With this in place, everything works fine - I can seed my tables with data and everything is peachy.
However, when I try to add a navigation property (and thereby foreign key from second entity to first entity), seeding fails.
I add this line to second entity:
public virtual ClassificationType ClassificationType {get;set;}
This causes this migration step:
public override void Up()
{
DropPrimaryKey("dbo.ClassConfig");
AlterColumn("dbo.ClassConfig", "Id", c => c.Int(nullable: false));
AddPrimaryKey("dbo.ClassConfig", "Id");
CreateIndex("dbo.ClassConfig", new[] { "Id", "TreeVersion" });
AddForeignKey("dbo.ClassConfig", new[] { "Id", "TreeVersion" }, "dbo.ClassificationTypes", new[] { "Id", "TreeVersion" });
}
Notice how it actually fails in deducting the foreign key columns, so I manually change this to
public override void Up()
{
DropPrimaryKey("dbo.ClassConfig");
AlterColumn("dbo.ClassConfig", "Id", c => c.Int(nullable: false));
AddPrimaryKey("dbo.ClassConfig", "Id");
CreateIndex("dbo.ClassConfig", new[] { "ClassificationTypeId", "TreeVersion" });
AddForeignKey("dbo.ClassConfig", new[] { "ClassificationTypeId", "TreeVersion" }, "dbo.ClassificationTypes", new[] { "Id", "TreeVersion" });
}
This creates the following SQL statement:
ALTER TABLE [dbo].[ClassConfig] DROP CONSTRAINT [PK_dbo.ClassConfig]
ALTER TABLE [dbo].[ClassConfig] ALTER COLUMN [Id] [int] NOT NULL
ALTER TABLE [dbo].[ClassConfig] ADD CONSTRAINT [PK_dbo.ClassConfig] PRIMARY KEY ([Id])
CREATE INDEX [IX_ClassificationTypeId_TreeVersion] ON [dbo].[ClassConfig]([ClassificationTypeId], [TreeVersion])
ALTER TABLE [dbo].[ClassConfigEntries] ADD CONSTRAINT [FK_dbo.ClassConfig_dbo.ClassificationTypes_ClassificationTypeId_TreeVersion] FOREIGN KEY ([ClassificationTypeId], [TreeVersion]) REFERENCES [dbo].[ClassificationTypes] ([Id], [TreeVersion])
Now, when I seed my tables, the script fails. The message is this:
Cannot insert explicit value for identity column in table
'ClassConfigEntries' when IDENTITY_INSERT is set to OFF.
Can anyone tell me why this is happening? The values I am trying to insert into the ClassConfig table are present in the ClassificationType table at point of insertion.
I have about 30 existing tables in out model, some of them with identity specified, and all of them work as they should when seeding and navigation properties are available when getting values from the database. It is only this corner of my model, that causes me head aches.

How to map results of a stored procedure to an existing table in Entity Framework code-first

I have an issue while mapping results of a stored procedure (dbo.sp_Get_User) to an existing UserEntity entity.
I didn't want to user db table column names in my project directly. So I have UserEntity as follows:
public class UserEntity
{
public UserEntity()
{
}
public int UserKey { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
And my mapper maps all the columns of table to this entity as follows:
public class UserEntityMapper : EntityTypeConfiguration<UserEntity>
{
public UserEntityMapper()
{
ToTable("tbl_User");
HasKey(m => m.UserKey);
Property(p => p.UserKey).HasColumnName("User_KEY");
Property(p => p.FirstName).HasColumnName("FIRST_NAME");
Property(p => p.LastName).HasColumnName("LAST_NAME");
}
}
Now I have a condition where I need to use a stored procedure to get the user data (based on some dynamic queries and all, I have to use this stored procedure for sure)
So I am trying to execute the stored procedure as follows:
SqlParameter temp1 = new SqlParameter("#EffecitveDate", DateTime.Parse("01/01/2012"));
SqlParameter temp2 = new SqlParameter("#User_Key", id);
IEnumerable<UserEntity> usrEnt = context.Database.SqlQuery<UserEntity>("sp_Get_User #EffecitveDate, #User_Key", temp1, temp2);
But I am getting following error while executing the above line:
The data reader is incompatible with the specified 'UserEntity'. A member of the type, 'UserKey', does not have a corresponding column in the data reader with the same name.
I am really stuck on this please suggest. Thanks

How can I use my stored procedure in Entity Framework 4?

I have this stored procedure for login
ALTER PROCEDURE usp_Login
(
#username varchar(50),
#password varchar(50)
)
AS
BEGIN
DECLARE #loginflag VARCHAR(1)
IF EXISTS (SELECT dbo.tbl_Login.Id FROM dbo.tbl_Login WHERE (dbo.tbl_Login.LoginId = #username))
BEGIN
IF ((SELECT COUNT(dbo.tbl_Login.Id) FROM dbo.tbl_Login WHERE (dbo.tbl_Login.LoginId = #username) AND (dbo.tbl_Login.Loginpassword= #password))> 0)
BEGIN
WITH LoggedInUser AS
(
SELECT L.LoginId,L.LoginRole,L.LoginPassword,L.UserId,S.FatherName,S.Name,S.RollNo,S.Marks,S.Dob, S.Address,S.PhoneNumber,S.CityId,S.EmailAddress,S.Gender,S.Password, S.SiteUrl,S.StuAvtar,S.StuCv FROM dbo.tbl_Login AS L INNER JOIN dbo.tbl_Student AS S ON L.UserId = S.Id
WHERE L.LoginPassword=#password AND L.LoginId=#username
)
SELECT *FROM LoggedInUser
END
ELSE
SELECT -98 AS Error --User Password Wrong
END
ELSE
SELECT -99 AS Error --USER NOT EXISTS
END
and I would like to use it with Entity Framework with return result. How can I do it? please help me...
here's a link on how to use the stored procedure by Entity Framework Stored Procedures in the Entity Framework
you can write stored procedures in sql and your entity update and then conversion it to function and use it such as function in your program,db-click on the Entity Model and in open page(EntityModel) then i right side page db-click on folder that called StoredproceduresEntity and open then right click on the a StoredProcedures and select option AddFunction... then select type of output go to: http://msdn.microsoft.com/en-us/data/gg699321.aspx
There is no direct mapping support for stored procedures when using Code First
You can simply call your procedure instead of Add method by using db.Database.SqlCommand method.
Try this (This is a sample):
News class:
public class News
{
public int NewsId { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public bool Active { get; set; }
}
Connection String
<connectionStrings>
<add
name="MyDatabaseContext"
connectionString="Data Source=MyServer;Initial Catalog=MyDatabase;Integrated Security=True;"
providerName="System.Data.SqlClient" />
</connectionStrings>
DbContext
public class MyDatabaseContext : DbContext
{
public DbSet<News> Newses { get; set; }
}
Stored Procedure
ALTER PROCEDURE [dbo].[News_Insert]
(
#Title VARCHAR(100),
#Body VARCHAR(MAX),
#NewsStatusId INT
)
AS
BEGIN
INSERT INTO
News
(
Title,
Body,
NewsStatusId
)
VALUES
(
#Title,
#Body,
#NewsStatusId
);
SELECT SCOPE_IDENTITY();
END
How to use SP with above model (code first)
db.Database.SqlCommand("dbo.News_Insert #Title, #Body, #NewsStatusId",
new SqlParameter("Title", news.Title),
new SqlParameter("Body", news.Body),
new SqlParameter("NewsStatusId", news.NewStatus.Id));
Here are some useful links for more details
Nikolaos Blog Post
Beyondrelational Blog Post