Forcefully Preventing EF from Inserting an Already Existing Object - c#

We have Users, and Roles with a Many-to-Many relationship defined.
The Fluent API that defines Role:
public RoleMap()
{
// Primary Key
this.HasKey(t => t.RoleString);
// Properties
this.Property(t => t.RoleString)
.IsRequired()
.HasMaxLength(50);
// Table & Column Mappings
this.ToTable("Roles");
this.Property(t => t.RoleString).HasColumnName("Role");
// Relationships
this.HasMany(t => t.Users)
.WithMany(t => t.Roles)
.Map(m =>
{
m.ToTable("UserRoles");
m.MapLeftKey("Role");
m.MapRightKey("UserName");
});
}
the following code (as expected) wants to insert a new Role in Roles, then a matching record in UserRoles for the user "MrAdmin".
var user = db.Users.Find("MrAdmin");
user.Roles.Add(new Role("Administrator"));
db.SaveChanges();
I very specifically DO NOT want to lookup the already existing "Administrator" role from the Roles table. The actual application has an enum of Roles that get passed in, so the user does not need to be aware of the Roles table or any of its content at all.
In other words, I know the following code works, but I am looking to manipulate EF at a lower level:
var adminRole = db.Roles.Find("Administrator");
var user = db.Users.Find("MrAdmin");
user.Roles.Add(adminRole);
db.SaveChanges();
What I'm trying to do is force EF to avoid adding any -- or already existing -- records to the Roles table.
I have attempted two approaches:
1) Create an INSTEAD OF INSERT TRIGGER on the DB side, to simply accept the insert request and not let EF worry about it. But EF still knew something was fishy and threw an exception.
Trigger:
CREATE TRIGGER [dbo].[tr_DontThrowOnInsert] ON [dbo].[Roles]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
DECLARE #ROLE as varchar(50)
SELECT #ROLE = [Role] from inserted
IF EXISTS(SELECT [Role] FROM dbo.Roles WHERE [Role] = #ROLE)
UPDATE dbo.Roles
SET [Updated] = GetUtCDate()
output inserted.[Role]
WHERE [Role] = #Role
ELSE
INSERT INTO dbo.Roles
output inserted.[Role]
SELECT * FROM inserted
WHERE [Role] not in (SELECT [Role] FROM dbo.Roles)
END
2) override the SaveChanges() in DBContext, and mark the suspect entries as Unchanged:
public override int SaveChanges()
{
this.ChangeTracker.Entries<Role>().ForEach(r => r.State = EntityState.Unchanged);
return base.SaveChanges();
}
Exception:
Result Message: System.InvalidOperationException : Saving or accepting changes failed because more than one entity of type
'Manufacturing.Domain.LookupRole' have the same primary key value.
Ensure that explicitly set primary key values are unique. Ensure that
database-generated primary keys are configured correctly in the
database and in the Entity Framework model. Use the Entity Designer
for Database First/Model First configuration. Use the
'HasDatabaseGeneratedOption" fluent API or
'DatabaseGeneratedAttribute' for Code First configuration.

I have decided to use EF 6 Interceptors. IDbCommandTreeInterceptor and IDbCommandInterceptor
...
http://msdn.microsoft.com/en-us/data/dn469464.aspx#BuildingBlocks/

Related

Column, parameter, or variable #1: Cannot find data type Guid - Entity Framework Core

I have a table with the following structure:
CREATE TABLE [dbo].[Wing]
(
[Id] [UNIQUEIDENTIFIER] NOT NULL
CONSTRAINT [PK_Wing] PRIMARY KEY CLUSTERED ([Id])
CONSTRAINT [DF_Wing__Id] DEFAULT (newid()),
[Name] [NVARCHAR](50) NULL
)
I have set up the the model builder in Entity Framework as:
modelBuilder.Entity<Wing>(
entity =>
{
entity.HasKey(e => e.Id);
//Start of Id
entity.Property(e => e.Id)
.HasColumnName("Id")
.HasColumnType(typeof(Guid).Name);
//End of Id
entity.Property(e => e.Name)
.HasColumnName("Name")
.HasColumnType(typeof(string).Name);
});
Now, when I try to add a new Wing, I set up the value like:
Wing wing = new Wing() {Name = "Wing 1"};
And I add it to the database using
await dbContext.Set<Wing>().AddAsync(wing);
await dbContext.SaveChangesAsync();
When I add a single Wing this way, it work as expected without errors.
When I add multiple wings using the following code:
List<Wing> wings = new List<Wing>();
wings.Add(new Wing() {Name = "Wing 1"});
wings.Add(new Wing() {Name = "Wing 2"});
await dbContext.Set<Wing>().AddRangeAsync(wings);
await dbContext.SaveChangesAsync();
I get the following error:
Column, parameter, or variable #1: Cannot find data type Guid
However, when I comment out the code for binding "Id" in the model builder, everything works as expected without any exceptions.
Can someone help me with the root cause or a resolution? I am stumped because similar tables work just fine with or without the model builder for the "Id" column.
HasColumnType refers to database types, not .NET types. So it should be “UNIQUEIDENTIFIER”.

How do I update a table that only has foreign keys

I have a database with a table with only foreign keys. I am able to manually add data into this table, and then remove it with code. However, I am unable to add data to it. This may be because I don't actually know how to access my table, or I'm doing it wrong. Help is appreciated.
My foreign key only table:
CREATE TABLE UserCategory
(
CatID INT FOREIGN KEY REFERENCES Category(ID),
UserID NVARCHAR(128) FOREIGN KEY REFERENCES [User](ID),
CONSTRAINT PK_UserCategory PRIMARY KEY (CatID, UserID)
);
The code that links it in the model:
modelBuilder.Entity<Category>()
.HasMany(e => e.Users)
.WithMany(e => e.Categories)
.Map(m => m.ToTable("UserCategory").MapLeftKey("CatID").MapRightKey("UserID"));
As you can see, I have a table called what I created, and with the values expected.
However, when I try to access this table through code, I cannot access those values directly. I can delete values like this:
var userCats = thisUser.Categories.ToList();
foreach (var cat in userCats)
{
thisUser.Categories.Remove(cat);
}
But when I try and access the UserCategories properties, I get this:
What can I do to add new UserCategories to my database? If I just create a new Category, and add that the same way I delete one (but with Add, obviously) I am required to fill out Name. Then when I fill out Name, it tells me there's a key conflict, because it appears to be adding it to the Category table, not the UserCategory.
Here are the other two tables in question:
CREATE TABLE [User]
(
ID NVARCHAR(128) PRIMARY KEY,
FirstName VARCHAR(255) NOT NULL,
LastName VARCHAR(255),
JoinDate DATETIME,
ZipCode VARCHAR(25),
SearchRadius INT,
LoginToBusinessSide BIT
);
/*The categories businesses can fall under. */
CREATE TABLE Category
(
ID INT IDENTITY(1,1) PRIMARY KEY,
[Name] VARCHAR(255) UNIQUE NOT NULL,
);
Am I trying to access it wrong? How can I access it correctly? Thank you.
I'm not totally sure why this works, but I was able to finally save entries into my UserCategory table. This is my code:
for(var i = 0; i < categories.Length; ++i)
{
var user = db.Users.Find(thisUser.ID);
var cat = db.Categories.Find(categories[i]);
user.Categories.Add(cat);
}
I have to an active connection to both tables that the foreign keys are linked to, and then when I add one to the Categories table, it goes to the correct place.
If you understand exactly why this works, or if there's a better way, please do comment.

Cassandra C# driver mapper

Basic rules of Cassandra data modeling recommend us to create tables per query pattern.
In practice, this generally means you will use roughly one table per
query pattern. If you need to support multiple query patterns, you
usually need more than one table.
For example, we can have this 3 tables for Users
CREATE TABLE IF NOT EXISTS users(
id uuid,
username text,
emial text,
role text,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS users_by_role(
role text,
userid uuid,
username text,
emial text,
PRIMARY KEY (role)
);
CREATE TABLE IF NOT EXISTS users_by_email(
email text,
userid uuid,
username text,
role text,
PRIMARY KEY (email)
);
And in C# using Cassandra CSharp driver we map User to the first table:
public class User
{
public Guid Id;
public string UserName;
public string Email;
public string Role;
}
var config = new MappingConfiguration();
config.Define(new Map<User>()
.TableName("users")
.PartitionKey((o) => o.Id)
.Column((u) => u.Id, (cm) => cm.WithName("id"))
.Column((u) => u.UserName, (cm) => cm.WithName("username"))
.Column((u) => u.Email, (cm) => cm.WithName("email"))
.Column((u) => u.Role, (cm) => cm.WithName("role"))
.ExplicitColumns());
UserMapper = new Mapper(Session, config);
and read data using Mapper:
User result = UserMapper.Single<User>("WHERE id=?", guid);
My first question is: How do we map User to the other two tables users_by_role and users_by_email? I don't think that we have to create 2 more CLR types with the same properties. (Besides, we can have different columns in other tables)
And the second question: How can we utilize defined mappers in BatchStatement? For example: to insert a user to first table we'd use:
UserMapper.Insert<User>(usr);
But in our example we have to insert to 3 tables in a batch. What is the best approach?
driver version 3.1.0.1
I guess this is something that cannot be done.
You will have to proceed with mapping the 3 to each of the cassandra models.
Cassandra Table <=> c# Object.
When accessing them, based on the type of search (email, id or role), you'll need to create a level of abstraction, and this will return the user.

EF Mapping Properties of an Entity Type to Multiple Tables with TPH Inheritance

I would like to use Mapping Properties of an Entity Type to Multiple Tables in the Database (Entity Splitting) whilst as the same time using Mapping the Table-Per-Hierarchy (TPH) Inheritance, therefore my model mapping code is as follows:
modelBuilder
.Entity<Person>()
.HasKey(n => n.PersonId)
.Map(map =>
{
map.Properties(p => new { p.Name });
map.ToTable("dbo.Person");
})
.Map<Customer>(map =>
{
map.Requires("PersonType").HasValue("C");
map.Properties(p => new { p.CustomerNumber });
map.ToTable("dbo.Customer");
});
Based upon the following underlying database schema:
create table dbo.Person
(
PersonId int not null identity(1,1) primary key,
PersonType char(1) not null,
Name varchar(50) not null
)
create table dbo.Customer
(
PersonId int not null references dbo.Person (PersonId),
CustomerNumber varchar(10) not null
)
However, when the EF tries to execute my query:
ctx.People.ToList();
The following exception message is thrown:
Invalid column name 'PersonType'.
Running a SQL profile it would appear that its trying to use a predicate on the field PersonType with value C on the dbo.Customer table, rather than on the dbo.Person table where my discriminator really is.
If I use one or the other feature, i.e. only the inheritance or only the additional table mapping then it works but then I forfeit some of my requirements.
Can what I'm doing be done with the EF Fluent API?
Thanks for your time.
This can be achieved by creating a view on all the table schemas involved in the mapping:
create view dbo.vw_PersonExtended
as
select
p.Name, p.PersonId, p.PersonType, c.CustomerNumber
from
dbo.Person p
left join dbo.Customer c on c.PersonId=p.PersonId
And mapping this view to the base class type Person and removing the derived class table mapping as follows:
modelBuilder
.Entity<Person>()
.HasKey(n => n.PersonId)
.Map(map =>
{
map.Properties(p => new { p.Name });
map.ToTable("dbo.vw_PersonExtended");
})
.Map<Customer>(map =>
{
map.Requires("PersonType").HasValue("C");
map.Properties(p => new { p.CustomerNumber });
});
This would fail on inserting new entities as the view has more than one base table, therefore you'd have to use an INSTEAD OF TRIGGER or map the insertion to a stored procedure with Fluent code as:
modelBuilder
.Entity<Customer>()
.MapToStoredProcedures(map => map.Insert(i => i.HasName("usp_InsertCustomer")));
And insert stored procedure example as:
create procedure dbo.usp_InsertCustomer
#Name varchar(50),
#CustomerNumber varchar(50)
as
begin
set nocount on
declare #id int
insert into dbo.Person (Name, PersonType)
values (#Name, 'C')
set #id = scope_identity()
insert into dbo.Customer (PersonId, CustomerNumber)
values (#id, #CustomerNumber)
select #id as PersonId
end
Obviously the drawback with this approach is all of the plumbing work involved with getting this working.

Table with 2 foreign keys entity framework

I have a table which consists of 2 foreign keys. And those are only elements of the table. The table is meant to create association between 2 other tables. For example: The table is Users_Products, and the only 2 columns are UserId and ProductID, both foreign keys. When I generated the EF object from database it didn't create Users_Products object, it only automatically created navigation properties. Now how can I insert data in my Users_Products table using EF?
You can get some user object and add product into its navigation property.
User user = context.Users.Where(u => u.Id == 1);
Product product = context.Products.Where(p => p.Id == 1);
user.Products.Add(product);
context.SaveChanges();
For code examples that show how to work with many-to-many relationships in EF see the Working with Many-to-Many Relationships section in
The Entity Framework 4.0 and ASP.NET – Getting Started Part 5.
That is EF 4.0 / Database First; for an example using the DbContext API, see Adding Course Assignments to the Instructor Edit Page in Updating Related Data with the Entity Framework in an ASP.NET MVC Application (6 of 10).
using ( var ctx = new ...)
{
var user = new User();
var product = new Product();
user.Products.Add(product);
ctx.Users.AddObject(user);
ctx.SaveChanges();
}
If you want to create relation (insert record to User_Products table) you just need to use navigation property either on User or Product:
user.Products.Add(product);
or
product.Users.Add(user);
That means you must have navigation property on at least one side to be able to create the relation. If you have loaded entities from the current contest you can use the approach described by #Pavel.
If you don't have loaded entities or if you don't want to do two queries to the database just to make a relation you can use this workaround:
// Make dummy objects for records existing in your database
var user = new User() { Id = 1 };
var product = new Product() { Id = 1 };
// Ensure that your context knows instances and does not track them as new or modified
context.Users.Attach(user);
context.Products.Attach(product);
// Again make relation and save changes
user.Products.Add(product);
ctx.SaveChanges();

Categories