Asp.net MVC, validation of unique rows with weak entities - c#

So I have an entity EmployeeRegion
EmployeeRegion is a weak entity with a composite key from the tables Region and Employee.
I have an employee profile where they can add themselves to a region. They get a drop down with the regions
I'm doing a pass where I get everything in the model compliant with the data validation stuff that's built in to Asp.net MVC. This is great because the validation (using annotations) shows really great error messages to the end user.
How, using annotations, can I validate that a composite key is unique, and if not, show an error message?

Basically, you just need:
public class EmployeeRegion
{
[Key, Column(Order = 1)]
public int EmployeeId { get; set; }
public virtual Employee Employee { get; set; }
[Key, Column(Order = 2)]
public int RegionId { get; set; }
public virtual Region Region { get; set; }
}
In other words, the part you're missing is [Key, Column(Order = N)]. This makes the ids an actual composite key, which out of the box won't allow duplicates.
However, all this does is make the API more difficult for working with this M2M relationship. If the only data in the table is the keys and there's no payload of additional data needed for the relationship, then you should get rid of this entity and simply let EF handle the relationship:
public class Employee
{
...
public virtual ICollection<Region> Regions { get; set; }
}
public class Region
{
...
public virtual ICollection<Employee> Employees { get; set; }
}
Behind the scenes, EF will create a table similar to what you have with EmployeeRegion, but you won't be responsible for managing that, and things like ensuring unique relationships will be baked in. This also buys you a much easier API to work with. For example. To get all the employees for a region, currently, you'd have to do something like:
dbo.EmployeeRegions.Where(m => m.Region.Id == regionId).Select(m => m.Employee)
Whereas, by allowing EF to handle it, you can just do:
region.Employees

Related

Code-First Many to Many table not using provided id columns

I'm using EF6.2.0.
I need to create a many-many relationship between two POCO classes, but I need to add a custom property against the link.
So, I created this class to serve as my many-many link POCO:
class SampleGroupSpecies
{
[Key, Column(Order = 0)]
public int GroupId { get; set; }
[Key, Column(Order = 1)]
public int SpeciesId { get; set; }
public bool IsContaminated { get; set; }
[Required]
public virtual Group Group { get; set; }
[Required]
public virtual Species Species { get; set; }
}
however, when EF generates the table, I get this:
dbo.SampleGroupSpecies
Columns
GroupId (PK, FK, int, not null)
SpeciesId (PK, int, not null)
IsContaminated (bit, not null)
Species_Id (FK, int, not null)
As you can see, it's creating its own "Species_Id" column and using that as the foreign key, but that's not what I want.
I can obviously add a GroupSpeciesId column, set that as the only [key] and it'll all work as if by magic, but that's not good design.
I'm trying to avoid using fluent and just use annotations if possible.
Someone tell me where I've gone wrong :)
This was caused by me setting the navigation property incorrectly on the Species POCO - It was not set as a collection of SampleGroupSpecies, but as a single - meaning EF was correctly trying to create a one-many relationship, but I was trying to force it to create a many-to-many when it couldn't :)
Solution was to check both related POCOs to make sure their navigation properties are set as a collection, rather than a single instance.

Duplicate entities when setting state within a collection in Entity Framework

I have a solution which uses Entity Framework to insert invoices to a database table. These invoices reference an order, which in turn also references an order item collection.
In this instance I am trying to add an order to the database, however the code is inside a new DbContext and so I need to attach the order and order items to the context, as these already exist in the database and shouldn't be re-added.
I've cut down the model properties for the sake of demonstration:
public class Invoice {
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int InvoiceId { get; set; }
public string OrderNumber { get; set; }
...
public virtual List<InvoiceLineItem> LineItems { get; set; }
}
public class InvoiceLineItem {
[Key]
public int Id { get; set; }
...
public ShopifyOrderItem { get; set; }
}
public class ShopifyOrder {
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
public int OrderNumber { get; set; }
...
public OrderInvoiceStatus InvoiceStatus { get; set; }
public virtual List<ShopifyOrderItem> OrderItems { get; set; }
}
public class ShopifyOrderItem {
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
...
[Required]
public virtual ShopifyOrder ShopifyOrder { get; set; }
}
In the invoice engine, I'm running the following code for each invoice to add it to the database:
ShopifyOrder order = await db.ShopifyOrders.SingleOrDefaultAsync(x => x.OrderNumber.ToString() == inv.OrderNumber);
if (order != null) {
// Attach marketplace entity to the invoice to avoid duplicate primary key exceptions
db.Marketplaces.Attach(inv.Marketplace);
db.Invoices.Add(inv);
order.InvoiceStatus = OrderInvoiceStatus.InProgress;
}
I've tried a number of methods to try and attach the states, however they all throw errors.
inv.LineItems.ForEach(li => {
db.Entry(li).State = EntityState.Unchanged;
db.Entry(li.ShopifyOrderItem).State = EntityState.Unchanged;
db.Entry(li.ShopifyOrderItem.ShopifyOrder).State = EntityState.Modified;
});
The above code returns the following error on save:
EntityFramework: Saving or accepting changes failed because more than one entity of type 'TorroModels.ShopifyOrder' 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.
What is the best way to attach the LineItems/ShopifyOrderItems without trying to attach the ShopifyOrder connected property multiple times?
Sorry to say but it seems that you need to follow the best practice first when constructing a relationship. You may follow this link :
http://www.entityframeworktutorial.net/entity-relationships.aspx
In short :
Avoid using only "Id" in every entity, or you can use attributes to map between the physical name and the property name
It seems that you have circular references here, so maybe you could simplify it first
Next, you can read this link :
http://www.entityframeworktutorial.net/EntityFramework5/attach-disconnected-entity-graph.aspx
if you need to know more about what's the best practice of attaching entities, but in my opinion, just don't abuse this feature, because using normal CRUD should be sufficient most of the time.
I'm sorry I cannot help you more than this, because of lack of information I may need, and with my reputation I still cannot comment directly in your post to ask for it.

How to avoid adding duplicate records in the database with Entity Framework Code First and Web API?

My web application is built with MVC and Entity Framework Code First. To explain my question, I'm describing it with a simplified Album - Song example:
public abstract class MusicStoreEntity
{
[Display(AutoGenerateField = false), Key, Required, Column(Order = 0)]
public Guid Id { get; set; }
}
[Table(TableName), DataContract]
public class Album : MusicStoreEntity
{
public const string TableName = "Albums";
public virtual Collection<AlbumSong> Songs { get; set; }
}
[Table(TableName), DataContract]
public class Song : MusicStoreEntity
{
public const string TableName = "Songs";
public virtual Collection<AlbumSong> Albums { get; set; }
}
The two entities are connected many-to-many with a separate entity that contains two foreign keys, as well as an unique identifier:
[Table(TableName), DataContract]
public class AlbumSong : MusicStoreEntity
{
public const string TableName = "AlbumSongs";
[DataMember, Required]
public Guid AlbumId{ get; set; }
public virtual Album Album { get; set; }
[DataMember, Required]
public Guid SongId { get; set; }
public virtual Song Song { get; set; }
}
With an incoming API call, you can create a new entity if the two entities aren't connected yet:
public void SetAlbumSong(Guid albumId, Guid songId) {
var albumSong = DBContext.Set<AlbumSong>().SingleOrDefault(a => a.AlbumId == albumId && a.SongId == songId);
if(albumSong == null) {
var albumSong = new AlbumSong {
AlbumId = albumId,
SongId = songId
}
DBContext.Set<AlbumSong>().Add(albumSong);
DBContext.SaveChanges();
} else {
// update existing albumSong
}
}
But, when two API calls come in at approximately the same time with the same entity id's, there is a window that enables adding two AlbumSong entities between the same album and song.
One solution is making a Composite Key that consists of both foreign id's in the AlbumSong entity. This way no two duplicate AlbumSongs can be made and an exception will be thrown.
A similar solution is adding an extra property to AlbumSong that combines the two id's and requiring the column to be unique.
However, I wondered if there are other (better, cleaner) solutions to this problem, as above solutions bring unwanted changes for my specific application.
(My web application is built with MVC 4 and Entity Framework 5.)
A composite key is a good idea, and can be viewed as better database design, because the AlbumID and SongID are what truly identify the record as unique.
EF provides concurrency checking, and wouldn't require many changes other than updating your model and adding a try/catch to your else statement. This article can take you through the process, but will add a field to your database. This article uses concurrency checking, but does not add a field to the database.

Is there a way to model an optional many-to-many relationship in Entity Framework?

Is there a way in Entity Framework (and I assume it will be with fluent syntax as data annotations are somewhat limited) to model a many-to-many relationship in which both sides are optional (a 0..M to 0..N relationship)? The use case is this: I would like to allow users to add tags to entities. Tags to entities is a M:N relationship, but neither should be required. That is, a tag can exist that is not applied to any entities and an entity can be untagged. This seems fairly reasonable to me. I can't simply model this using:
public virtual ICollection<Tag> Tags { get; set; }
and
public virtual ICollection<Entity> Entities { get; set; }
because each class has other relationships, and I get a "foreign key constraint may cause cycles or multiple cascade paths." I was hoping maybe I could do something like:
modelBuilder.Entity<Tag>().HasOptional(t => t.Entities);
modelBuilder.Entity<Entity>().HasOptional(t => t.Tags);
but I am warned that EF is "Unable to determine the principal end of the association." From reading, it seems that such relationships HAVE to have a principal end, but in my case, that's undesirable.
I could add a class to represent the bridge table and handle the mapping manually, but I'd prefer not to clutter the code. I was wondering if there is another way to model this in EF.
To fill in a bit more detail, there is also an Author class (which amounts to Users). Authors and tags are 1:M and Authors to Entities are also 1:M. So of course, the problem is that the Entities class occurs twice in the cascade tree. Making the Tag/Entity relationship optional would fix this. I could also fix it if there was a way to get to Tags through Entities, but since Tags can exist without being connected to an entity, I figured that would be impossible.
Here's a summary of the related code:
public class Author
{
public Guid Id { get; set; }
public virtual List<Entity> Entities { get; set; }
public virtual List<Tag> Tags { get; set; }
}
public class Tag
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; }
public virtual Author Author { get; set; }
public virtual ICollection<Entity> Entities { get; set; }
}
public class Entity
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; }
public virtual Author Author { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
EDIT:
Using .HasMany().WithMany() as suggested below gives me this:
CREATE TABLE [dbo].[TagEntities] (
[Tag_Id] [uniqueidentifier] NOT NULL,
[Entity_Id] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_dbo.TagEntities] PRIMARY KEY ([Tag_Id], [Entity_Id])
)
but what I WANT is for Tag_Id and Entity_Id to be nullable on this table. Maybe this model doesn't make as much sense as I thought?? Can you have a bridge table where both sides are nullable?
Use
modelBuilder.Entity<Tag>().HasMany(t => t.Entities)
.WithMany(t => t.Tags);
Instead of
modelBuilder.Entity<Tag>().HasOptional(t => t.Entities);
modelBuilder.Entity<Entity>().HasOptional(t => t.Tags);
I don't know if this is the RIGHT answer, but I solved this by creating a base class called DbEntity that other classes inherited from. So now Author has just:
// Both entities and tags are part of this collection
public virtual List<DbEntity> Entities { get; set; }
Both "Entities" (which has special meaning in my code) and "Tags" subclass DbEntity. This eliminated the multiple cascade paths while preserving the navigation properties, although I do need to do this:
author.Entities.OfType<Tag>();
or
author.Entities.OfType<Entity>();
to get specific sets of entities.

Creating a master table with two child tables linking one-to-zero-or-one with EF 4.1

Using MVC EF4.1, I am trying to link a table (TableMaster) to TableChildOne (relationship one-to-zero-or-one) and also to TableChildTwo (also one-to-zero-or-one).
TableChildOne and TableChildTwo are not directly linked.
TablechildOne and TableChildTwo needs to share the primary key of TableMaster (I read this is not possible, any workarounds?)
I am including an image to make this a bit more clear, not sure if there should be foreign keys added somewhere, this is not an actual model created by the code, but is what i would like. not sure if there should be foreign keys somewhere?
image : http://www.davidsmit.za.net/img/untitled.png
My code below compiles, but when trying to add a controller, I get the error :
"An item with the same key has already been added"
public class TableMaster
{
public int TableMasterID { get; set; }
public DateTime ReportDate { get; set; }
public virtual TableChildOne TableChildOne { get; set; }
public virtual TableChildTwo TableChildTwo { get; set; }
}
public class TableChildOne
{
[Key]
public int TableMasterID { get; set; }
public String Description_1 { get; set; }
public virtual TableMaster TableMaster { get; set; }
}
public class TableChildTwo
{
[Key]
public int TableMasterID { get; set; }
public String Description_2 { get; set; }
public virtual TableMaster TableMaster { get; set; }
}
public class Context : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<TableMaster>()
.HasOptional(p => p.TableChildOne).WithRequired(p => p.TableMaster);
modelBuilder.Entity<TableMaster>()
.HasOptional(p => p.TableChildTwo).WithRequired(p => p.TableMaster);
}
When I remove the second table completely, it works fine.
I used the below link as an example (tables OfficeAssignment and Student), which shows how to link a table one-to-zero-or-one. But I have trouble adding another table with the same linkage:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/creating-a-more-complex-data-model-for-an-asp-net-mvc-application
Any help will be appreciated.
Thanks
appelmeester
Could you give more background about why you want to do this? If you are sharing the primary key across three tables you are partitioning data. What development scenario are you trying to address. It sounds like you might be wanting to map an object inheritance, is that right?
If you truly only have a couple of Descriptions, then this is really just one table.
EDIT:
Cool. Because the business context of this request is a bit vague, I can't quite understand still, sorry. If you have a TableMaster and then some child tables, then this sounds like an inheritance tree. So with EF, you can choose many different strategies to model this (TPH, TPT etc). For this, I would suggest looking into TPT because this might allow you to get the granularity for how you want to clean up the data. Also, you get the benefit that the tables will be created, by default, largely like you have specified. Check this out for reference.

Categories

Resources