Pipelined function as entity in EF/MVC - c#

I have a .Net MVC app using entity framework, and normally I'd use a table or a view in a data entity... eg.
[Table("company_details", Shema = "abd")]
public class CompanyDetails
{
[Key]
[Column("cd_id_pk")]
public int CompanyDetailsId { get; set; }
etc ...
etc ...
...where company_details is an oracle table.
However I need to try to utilise a pipelined function.... eg the sql would be:
SELECT * FROM TABLE(abd.company_pck.f_single_rprt('1A122F', '01-Feb-2020','Y'));
This had been used in a report used in Oracle forms, but now it's to be included in an .Net MVC app.
How can I include a pipelined function in my entity?
thanks in advance

I just tried this and it seems to work. First create a class as you would to be able to map the return from your DbContext. In your case you just call the Pipelined table function from Oracle. I used a TVF in SQL to demonstrate. The TVF returned 3 columns of data, 2 INT and 1 NVarChar.
public class ReturnThreeColumnTableFunction
{
public int ColumnOne { get; set; }
public int ColumnTwo { get; set; }
public string ColumnThree { get; set; }
}
Then based on your Oracle Pipelined function, (see my MSSQL TVF below)
/* SQL TableValuedFunction */
ALTER FUNCTION [dbo].[ReturnThreeColumnTableFunction]
(
#ColumnOne INT,
#ColumnTwo INT,
#ColumnThree NVARCHAR(10)
)
RETURNS TABLE
AS
RETURN
(
SELECT #ColumnOne AS ColumnOne, #ColumnTwo AS ColumnTwo, #ColumnThree AS ColumnThree
)
Then in your DbContext class you setup your CodeFirst entities, be sure to add the complex type in the OnModelCreating method.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.ComplexType<ReturnThreeColumnTableFunction>();
modelBuilder.ComplexType<ReturnThreeColumnTableFunction>().Property(x => x.ColumnOne).HasColumnName("ColumnOne");
modelBuilder.ComplexType<ReturnThreeColumnTableFunction>().Property(x => x.ColumnTwo).HasColumnName("ColumnTwo");
modelBuilder.ComplexType<ReturnThreeColumnTableFunction>().Property(x => x.ColumnThree).HasColumnName("ColumnThree");
}
Then you return this easily using the SqlQuery
var items = context.Database.SqlQuery<ReturnThreeColumnTableFunction>("SELECT * FROM dbo.ReturnThreeColumnTableFunction(1,2,'3')")

Related

Seeding huge amount of data (EF Core)

Here is the script which I use manually after the database creating to generate useless data for testing:
DECLARE #index BIGINT
SET #index = 0
SET IDENTITY_INSERT Persons ON
WHILE #index < 50000
BEGIN
INSERT INTO Persons
(Id, [Name], Code)
VALUES
(NEWID(), CONCAT('Person', #index), #index)
SET #index = #index + 1
END
How can I run it using EF core in the moment of the database initialization or somehow using data seeding methods? All the answers around are about small amounts of data, but in my case, I work with ~ 50000 records.
If you are using EF Core 2.1 and higher, then making use of HasData method is an ideal way to add seed data.
We can call it using ModelBuilder object in 'OnModelCreating' method to add the data as part of code first migrations.The data then gets seeded the very first time when a database is scaffold or initialized and migrations are applied.
You can also combine it with Bogus to generate fake data for entities.
Inline is a scenario for creating 50000 objects as stated in question. The program executed successfully without any issues. In fact ef core is intelligent enough to split the data into batch queries of 700-800 objects per batch and push it to database.
Entity:
public class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
public string LastName { get; set; }
}
OnModelCreating
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
int id = 1;
var fakePersons = new Faker<Person>().StrictMode(true)
.RuleFor(o => o.Id, f => id++)
.RuleFor(u => u.FirstName, (f, u) => f.Name.FirstName())
.RuleFor(u => u.LastName, (f, u) => f.Name.LastName());
var persons = fakePersons.Generate(50000);
modelBuilder.Entity<Person>().HasData(persons);
}

How can I write my own ef core function that operatoes on the database entitites

I am trying to add a new custom function to ef core that I can use on the database entites.
I need to add my own implementation of count on each entity.
db.xyz.count() does not work for me.
It does not work for me because its too slow owing to the number of rows.
I want to have a custom function which brings the counts from another view.
something like:
db.xyz.customcount();
I tried using ef core extension methods:
namespace xyz.Helpers
{
public static class CustomCountExtension:DbContext
{
public static int CustomCount() => {....};
}
}
But this function , I need it to query against a different view which has the counts of each entity stored.
.i.e
Myview:
table | row |
______________
xyz | n |
when i call xyz.customcount() , I need it to return rowcount where table
= xyz.
is this possible.
Is there a better of implementing the above idea?
I am using:
.net core 2.1
ef core 2.2
You can map an Entity directly to your View and query it like a table.
public class MyView
{
public string Table { get; set; }
public int Row { get; set; }
}
Then define it as a DbQuery instead of DbSet in your DbContext:
public virtual DbQuery<MyView> MyView { get; set; }
Then map it to your View in the OnModelCreating() method using .Query() instead of .Entity():
protected override view OnModelCreating(ModelBuilder modelBulder)
{
//... Other tables
modelBuilder.Query<MyView>(query =>
{
query.ToView("MyView");
}
}

Mapping stored procedure results to a custom class using EF Core FromSql returns instances with default values

I have a stored procedure like this:
CREATE PROCEDURE PLogin
#pin varchar(50), #ipaddress varchar(50)
AS
BEGIN
SET NOCOUNT ON;
//Some code here ...
SELECT * FROM [dbo].[QRelaceUzivatele_Aktivni] WHERE Pin = #pin AND IPAdresa=#ipaddress
END
I need to execute it from ASP MVC using EF Core 2.1.
I have used the approach mentioned here.
I have a custom C# class AktivniRelaceUzivatele that maps the fields of the QRelaceUzivatele_Aktivni view.
public class AktivniRelaceUzivatele
{
int ProvozID { get; set; }
string NazevProvozu { get; set; }
int UzivatelID { get; set; }
string PIN { get; set; }
string Nick { get; set; }
In MVC controller I call:
var qpin = new SqlParameter("p", pin);
var qip = new SqlParameter("ip", ip);
var ar = db.Query<AktivniRelaceUzivatele>()
.FromSql("EXECUTE dbo.PLogin #pin=#p, #ipaddress=#ip", qpin, qip).FirstOrDefault();
In dbContext class I have added:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Query<AktivniRelaceUzivatele>();
I get no compile error the stored procedure gets executed but the result values are not insterted into objects's properties, instead I get instances that contain only default values like null, 0 etc.
Can u try to add public classifiers to class variables? I think I got the similar issues, where I missed both getters and setters and public classifiers.

Entity Framework: RowVersion value is null

I am using Entity Framework 6.2.0 and a local MSSQL (MDF) database.
I have several types that all descend from my main type "Entity" ("Table per Type" strategy is used). Now, I am trying to implement optimistic locking.
In my EDMX file, I added a property RowVersion to Entity (a fixed length byte array of 8 bytes, in SQL-DB : "[RowVersion] binary(8) NOT NULL") and set the Concurrency mode of that proeprty to "Fixed". I flagged the property inside the Entity class with the "Timestamp" attribute:
[System.ComponentModel.DataAnnotations.Schema.Table("EntitySet", Schema = "RightsManager")]
public partial class Entity
{
public int Id { get; set; }
public string Name { get; set; }
public System.DateTime ActiveFrom { get; set; }
public Nullable<System.DateTime> ActiveUntil { get; set; }
[System.ComponentModel.DataAnnotations.Timestamp]
public byte[] RowVersion { get; set; }
}
I also added code to OnModelCreating of my DBContext descendant to indicate RowVersion to be used:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<RightsManagerContext>(null);
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Entity>().Property(p => p.RowVersion).IsRowVersion();
modelBuilder.Entity<Product>().Property(p => p.RowVersion).IsRowVersion();
}
The problem: Upon insert of a new Product, an SQL error is thrown. This is the unit test i am using:
[TestMethod]
public void TestCreateProduct()
{
using (var context = GetContext())
{
var newProduct = new Product
{
Name = "New product",
ActiveFrom = DateTime.Now
};
context.Entry(newProduct).State = System.Data.Entity.EntityState.Added;
var objectsWritten = context.SaveChanges();
Assert.AreNotEqual(0, objectsWritten);
};
}
The innermost exception thrown:
System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'RowVersion', table 'P:\VISUAL STUDIO\PROJECTS\RIGHTSMANAGER\DATABASE\RIGHTSMANAGER.MDF.RightsManager.EntitySet'; column does not allow nulls. INSERT fails.
Obviously, EF is not filling in a value automatically, it's handling the field like any other one. What am i missing here?
I think i misunderstood the IsRowVersion/Timestamp thing to be a database-agnostic one. It seems that this whole mechanism only works if using the MSSQL-specific database field type "rowversion" when creating the table. All other databases such as Oracle, DB2 etc are not in scope.
As I am trying to have DBMS neutrality in my project, I will have to manually implement such a feature with "IsConcurrencyToken".

GUID COMB strategy in EF

Is there any way to implement the Guid COMB identity strategy for objects in the new Entity Framework 4.1 using the CodeFirst design? I thought setting the StoreGeneratedPattern would work, but it still gives me normal GUIDs.
Why worry about defaults for Guid columns in the database at all? Why not just generate the Guid on the client like any other value. That requires you have a method in your client code that will generate COMB-like guids:
public static Guid NewGuid()
{
var guidBinary = new byte[16];
Array.Copy( Guid.NewGuid().ToByteArray(), 0, guidBinary, 0, 8 );
Array.Copy( BitConverter.GetBytes( DateTime.Now.Ticks ), 0, guidBinary, 8, 8 );
return new Guid( guidBinary );
}
One of the advantages of the Guid is specifically that you can generate them on the client without a round trip to the database.
I guess you are using SQL server as your database. This is nice example of inconsistency among different MS tools. SQL server team doesn't recommend using newid() as default value for UNIQUEIDENTIFIER columns and ADO.NET team use it if you specify Guid property as autogenerated in the database. They should use newsequentialid() instead!
If you want sequential Guids generated by database you must modify generated table and it is really complex because you must find autogenerated default constraint, drop it and create new constraint. This all can be done in custom database initializer. Here you have my sample code:
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new CustomInitializer());
using (var context = new Context())
{
context.TestEntities.Add(new TestEntity() { Name = "A" });
context.TestEntities.Add(new TestEntity() { Name = "B" });
context.SaveChanges();
}
}
}
public class CustomInitializer : DropCreateDatabaseAlways<Context>
{
protected override void Seed(Context context)
{
base.Seed(context);
context.Database.ExecuteSqlCommand(#"
DECLARE #Name VARCHAR(100)
SELECT #Name = O.Name FROM sys.objects AS O
INNER JOIN sys.tables AS T ON O.parent_object_id = T.object_id
WHERE O.type_desc LIKE 'DEFAULT_CONSTRAINT'
AND O.Name LIKE 'DF__TestEntities__Id__%'
AND T.Name = 'TestEntities'
DECLARE #Sql NVARCHAR(2000) = 'ALTER TABLE TestEntities DROP Constraint ' + #Name
EXEC sp_executesql #Sql
ALTER TABLE TestEntities
ADD CONSTRAINT IdDef DEFAULT NEWSEQUENTIALID() FOR Id");
}
}
public class TestEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Context : DbContext
{
public DbSet<TestEntity> TestEntities { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<TestEntity>()
.Property(e => e.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
The simplest answer
public class User
{
public User(Guid? id = null, DateTime? created = null)
{
if (id != null)
Id = id;
if (created != null)
Created = created;
}
public User()
{
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime? Created { get; internal set; }
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid? Id { get; internal set; }
}
This assumes you have your database table set with the default of newsequentialid() which in my case is managed by FluentMigrator migrations.
if you use SQL Server, when a GUID property is configured as value generated on add, the provider automatically performs value generation client-side, using an algorithm to generate optimal sequential GUID values. refer to for more.
https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=fluent-api

Categories

Resources