I am using Visual Studio 2017 with a asp.net mvc 5 and entity framework 6. I am following an online course and the steps the author is doing, is creating 2 classes with properties, then marking properties as [Required] and assign [StringLength(255)] to some other string properties. The modifications work on one class but not on the other.
This is the code for the 2 classes:
The one that's working:
public class Genre
{
public byte Id { get; set; }
[Required]
[StringLength(255)]
public string Name { get; set; }
}
The one that's not working:
public class Gig
{
public int Id { get; set; }
[Required]
public ApplicationUser Artist { get; set; }
public DateTime DateTime { get; set; }
[Required]
[StringLength(255)]
public string Venue { get; set; }
[Required]
public Genre Genre { get; set; }
}
I ran migration first time before adding the attributes, and the CreateTables.cs migration looks like this:
public override void Up()
{
CreateTable(
"dbo.Genres",
c => new
{
Id = c.Byte(nullable: false),
Name = c.String(),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.Gigs",
c => new
{
Id = c.Int(nullable: false, identity: true),
DateTime = c.DateTime(nullable: false),
Venue = c.String(),
Artist_Id = c.String(maxLength: 128),
Genre_Id = c.Byte(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.AspNetUsers", t => t.Artist_Id)
.ForeignKey("dbo.Genres", t => t.Genre_Id)
.Index(t => t.Artist_Id)
.Index(t => t.Genre_Id);
}
Then I added the attributes to my code, and added a new migration, When I run add migration, the generated code in the migration is like this:
public override void Up()
{
DropForeignKey("dbo.Gigs", "Artist_Id", "dbo.AspNetUsers");
DropForeignKey("dbo.Gigs", "Genre_Id", "dbo.Genres");
DropIndex("dbo.Gigs", new[] { "Artist_Id" });
DropIndex("dbo.Gigs", new[] { "Genre_Id" });
AlterColumn("dbo.Genres", "Name", c => c.String(nullable: false, maxLength: 255));
AlterColumn("dbo.Gigs", "Venue", c => c.String(nullable: false, maxLength: 255));
AlterColumn("dbo.Gigs", "Artist_Id", c => c.String(nullable: false, maxLength: 128));
AlterColumn("dbo.Gigs", "Genre_Id", c => c.Byte(nullable: false));
CreateIndex("dbo.Gigs", "Artist_Id");
CreateIndex("dbo.Gigs", "Genre_Id");
AddForeignKey("dbo.Gigs", "Artist_Id", "dbo.AspNetUsers", "Id", cascadeDelete: true);
AddForeignKey("dbo.Gigs", "Genre_Id", "dbo.Genres", "Id", cascadeDelete: true);
}
However, the generated T-SQL code to create the table is like this:
CREATE TABLE [dbo].[Gigs] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[DateTime] DATETIME NOT NULL,
[Venue] NVARCHAR (MAX) NULL,
[Artist_Id] NVARCHAR (128) NULL,
[Genre_Id] TINYINT NULL,
CONSTRAINT [PK_dbo.Gigs] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_dbo.Gigs_dbo.AspNetUsers_Artist_Id] FOREIGN KEY ([Artist_Id]) REFERENCES [dbo].[AspNetUsers] ([Id]),
CONSTRAINT [FK_dbo.Gigs_dbo.Genres_Genre_Id] FOREIGN KEY ([Genre_Id]) REFERENCES [dbo].[Genres] ([Id])
);
However, the generated TSQL for the other table, was right:
CREATE TABLE [dbo].[Genres] (
[Id] TINYINT NOT NULL,
[Name] NVARCHAR (255) NOT NULL,
CONSTRAINT [PK_dbo.Genres] PRIMARY KEY CLUSTERED ([Id] ASC)
);
Why the T-SQL is not reflecting the modifications by the migration for the 1st table? Knowing that I added the migration and executed update-database, and could see the changes for one table (Genre), but not the other.
Any help is appreciated.
I've faced the same problem, neither StringLength nor MaxLength Data Annotations didn't helped me much. It seems I'm trying to use same online course, which initially for MSVS2015, but I'm using latest MSVS2017.
What I've tried so far:
Setting length as is and the change only it in migration
Setting specific migration version with Update-Database -TargetMigration Migration
Setting all the migrations from scratch with Update-Database -TargetMigration 0 | Update-Database -Force
Recreating database (Deleting database in SQL Server Object Explorer)
And after all SQL definition still contains initial MAX constraint, as well as table definition.
Part of migration
AlterColumn("dbo.Genres", "Name", c => c.String(nullable: false, maxLength: 255));
AlterColumn("dbo.Gigs", "Venue", c => c.String(nullable: false, maxLength: 255));
AlterColumn("dbo.Gigs", "Artist_Id", c => c.String(nullable: false, maxLength: 128));
Database definition after all migrations
[Name] NVARCHAR (MAX) NULL,
...
[Venue] NVARCHAR (MAX) NULL,
[Artist_Id] NVARCHAR (128) NULL,
Related
I've been messing around with EF Core 3.1 trying to setup a relationship between two classes. The trick is the composite key on the one class which the other one is only related to through one part.
Consider the following:
public class A
{
public string Foo { get; set; }
public long Id { get; set; }
public IEnumerable<B> B { get; set; }
}
public class B
{
[Key]
public int Id { get; set; }
public string Bar { get; set; }
}
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<A>(b =>
{
b.HasKey(x => new {x.Id, x.Foo});
b.HasMany(x => x.B)
.WithOne()
.HasPrincipalKey(x => x.Id);
});
}
Which creates the following migration:
migrationBuilder.CreateTable(
name: "A",
columns: table => new
{
Foo = table.Column<string>(nullable: false),
Id = table.Column<long>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_A", x => new { x.Id, x.Foo });
table.UniqueConstraint("AK_A_Id", x => x.Id);
});
migrationBuilder.CreateTable(
name: "B",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Bar = table.Column<string>(nullable: true),
AId = table.Column<long>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_B", x => x.Id);
table.ForeignKey(
name: "FK_B_A_AId",
column: x => x.AId,
principalTable: "A",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_B_AId",
table: "B",
column: "AId");
Which is almost what I want, except for the UniqueConstraint on table A! Is there a way around this?
This is not possible. And not because of EF Core, but the definition of the relationship multiplicity (cardinality).
The direct FK relationship from dependent to principal is to (has multiplicity) one (1) (or zero-or-one 0..1 in case of optional relationship), i.e. the FK must identify exactly one principal record. That's why the referenced principal key must be unique, which is satisfied by primary or unique key constraint.
What you are asking allows dependent record referencing more than one (i.e. many) matching principal records. In other words, to many cardinality. Which cannot be achieved with FK in the dependent table, but would require the typical for many-to-many relationship intermediate link (join) entity/table.
I am trying to bind my data using entity framework v6.1.3 but I am getting this error message EntityType has no key defined. Define the key for this EntityType. (I am having a composite key)
I've tried the following approaches:
public class CommunicationCollection
{
[Key, Column(Order = 0)]
[ForeignKey("FK_CommunicationCollection_Communication")]
public Guid CommunicationId;
[Key, Column(Order = 1)]
[ForeignKey("FK_CommunicationCollection_Collection")]
public Guid CollectionId;
}
and this
public class CommunicationCollection
{
[Key, Column(Order = 0)]
[ForeignKey("FK_CommunicationCollection_Communication")]
public Guid CommunicationId;
[Key, Column(Order = 1)]
[ForeignKey("FK_CommunicationCollection_Collection")]
public Guid CollectionId;
public virtual Communication Communication { get; set; }
public virtual Collection Collection { get; set; }
}
and also this
public class CommunicationCollection
{
[Key, Column(Order = 0)]
public Guid CommunicationId;
[Key, Column(Order = 1)]
public Guid CollectionId;
}
and in the DB I have
CREATE TABLE [CommunicationCollection](
[CommunicationId] [uniqueidentifier] NOT NULL,
[CollectionId] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_CommunicationCollection] PRIMARY KEY CLUSTERED
(
[CommunicationId] ASC,
[CollectionId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [CommunicationCollection] WITH CHECK ADD CONSTRAINT [FK_CommunicationCollection_Collection] FOREIGN KEY([CollectionId])
REFERENCES [Collection] ([CollectionId])
GO
ALTER TABLE [CommunicationCollection] CHECK CONSTRAINT [FK_CommunicationCollection_Collection]
GO
ALTER TABLE [CommunicationCollection] WITH CHECK ADD CONSTRAINT [FK_CommunicationCollection_Communication] FOREIGN KEY([CommunicationId])
REFERENCES [Communication] ([CommunicationId])
GO
ALTER TABLE [CommunicationCollection] CHECK CONSTRAINT [FK_CommunicationCollection_Communication]
GO
Any idea what am I missing?
Thanks a lot!
With EF, everything needs to be properties and not just 'plain' variables. This is needed so EF can hook into those methods.
So like this:
public Guid CommunicationId { get; set; }
public Guid CollectionId { get; set; }
Forgetting to do this causes all kinds of problems that can be hard to trace back to the actual cause, as you have just encountered.
In ASP.NET I'm using Entity Framework with a code-first approach and I want based on my model to generate the DB table. One of the attributes is Enum value, and it is not showing as a column in the generated table.
So this is my enum:
public enum MyEnum
{
val_one = 1,
val_two = 2,
val_three = 3
}
And this is my model:
public class MyModel
{
public int Id { get; set; }
public string attrString { get; set; }
public double attrDouble { get; set; }
public MyEnum attrEnum { get; set; }
}
So with the code-first approach I'm having generated table with the following columns:
CREATE TABLE [dbo].[MyModel]
(
[Id] INT IDENTITY (1, 1) NOT NULL,
[attrString] NVARCHAR (MAX) NOT NULL,
[attrDouble] FLOAT (53) NOT NULL,
[attrEnum] INT NOT NULL,
CONSTRAINT [PK_dbo.MyModel] PRIMARY KEY CLUSTERED ([Id] ASC)
);
And later when generating forms (controller and views) based on the model, I'm having a forms where attrEnum param is missing.
So how to proceed in order to have this enum attribute (in format of dropdown list) in the forms (still using code-first approach). I guess there may be needed generation of another DB table that will contains enums, but I'm really not sure how to do that in Entity Framework since I'm beginner in it.
You could take an easier approach...
In the controller, for example:
public ActionResult Edit(int? id)
{
List<SelectListItem> myOptions = new List<SelectListItem>();
myOptions.Add(new SelectListItem() { Text="1", Value = "1" });
myOptions.Add(new SelectListItem() { Text = "2", Value = "2" });
myOptions.Add(new SelectListItem() { Text = "3", Value = "3" });
ViewData["OptionList"] = new SelectList(myOptions, "Value", "Text");
Then in the Edit.cshtml:
#Html.DropDownListFor(model => model.Status, (SelectList)ViewData["OptionList"], new { #class = "form-control dropdown" })
You get a nice styled dropdown with options you're looking for.
I am attempting to save user preferences into a table but am getting a null exception and I do not understand why. This is an MVC 4 application and this is my action result where I am getting the error.
public ActionResult Go(string path, string name)
{
RaterContext r = new RaterContext();
UserData u = new UserData();
var userid = u.GetCurrentUserData().UserId;
var info = r.RatersInfo.Where(w => w.RaterName.Equals(name)).FirstOrDefault();
var pref = r.RatersPreferences.Where(w => w.RaterId.Equals(info.RaterId) && w.UserId.Equals(userid)).FirstOrDefault();
if (pref != null && pref.Count > 0)
{
pref.Count++;
r.SaveChanges();
}
else
{
pref = new RaterPreferences();
pref.UserId = userid;
pref.RaterId = info.RaterId;
pref.Count = 1;
r.RatersPreferences.Add(pref);
r.SaveChanges();
}
return Redirect(path);
}
There is nothing saved in the preferences table yet so it is hitting the else block and throwing a null exception on r.SaveChanges();. The exception is
Cannot insert the value NULL into column 'UserId', table
'WebSiteNew.dbo.RaterPreferences'; column does not allow nulls. INSERT
fails.\r\nThe statement has been terminated.
The reason this doesn't make sense is because all three properties, including the UserId have data when I step through. These are the only fields in the table. UserId = 1, RaterId = 6 and Count is clearly set to 1. They are all set as non-nullable ints and the primary key is a combination of UserId and RaterId. My Model is as follows.
public class RaterContext : DbContext
{
public RaterContext()
: base("DefaultConnection")
{
}
public DbSet<RaterInfo> RatersInfo { get; set; }
public DbSet<RaterPreferences> RatersPreferences { get; set; }
}
[Table("RaterInfo")]
public class RaterInfo
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int RaterId { get; set; }
public string RaterName { get; set; }
public string RaterLink { get; set; }
public string Section { get; set; }
public string Department { get; set; }
}
[Table("RaterPreferences")]
public class RaterPreferences
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public int RaterId { get; set; }
public int Count { get; set; }
}
Any help would be greatly appreciated as I am relatively new to MVC and ASP.NET. Let me know if you need more information. Thanks in advance!
I don't know if this helps but I tested to see what would happen on UPDATE by adding data manually so it would catch on the if block and that works. I'm only getting an error on INSERT.
Here is the create statement for the table in question.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[RaterPreferences](
[UserId] [int] NOT NULL,
[RaterId] [int] NOT NULL,
[Count] [int] NOT NULL,
PRIMARY KEY CLUSTERED
(
[UserId] ASC,
[RaterId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[RaterPreferences] WITH CHECK ADD CONSTRAINT [FK_RaterPreferences_RaterInfo] FOREIGN KEY([RaterId])
REFERENCES [dbo].[RaterInfo] ([RaterId])
GO
ALTER TABLE [dbo].[RaterPreferences] CHECK CONSTRAINT [FK_RaterPreferences_RaterInfo]
GO
I have copied your code into a brand new ASP.Net MVC project with the current version of Entity Framework and I am able to run your code with no problems. I escaped the UserData acquisition with code that looks like:
RaterContext r = new RaterContext();
//UserData u = new UserData();
var userid = 1; // u.GetCurrentUserData().UserId;
var info = r.RatersInfo.Where(w => w.RaterName.Equals(name)).FirstOrDefault();
and did not have a problem running the remainder of this code.
I think you may have some problems with your keys and database structure for the RaterPreferences table. I don't know your full data-model, but I don't understand how this fits in, and it is not keyed in your code the way that you describe.
Edit:
I've modified my database tables to reflect the design you've described. You have a difference between your EntityFramework code-first implementation and your database. It looks like your database existed first, and I would remove your EntityFramework classes and rebuild them with Database First techniques.
I trying to map my classes to existing database, but failed in part of foreign keys declaration. Actually, I do not want to have field-foreign key, but only navigation proerty.
Here is models:
public class Template : BaseNamedType
{
//One template may has multiple TemplateSection
public virtual ICollection<TemplateSection> TemplateSection { get; set; }
...........
}
//Custom mapping class. I have field Order, without it mapping is simple
public class TemplateSection : BaseType
{
public virtual int Order { get; set; }
//Here is one-to-many relation, this field is required
public virtual Template Template { get; set; }
//Here is one-to-many relation, this field is required
public virtual Section Section { get; set; }
}
public class Section : BaseNamedType
{
//One section may be referenced by multiple TemplateSection
public virtual ICollection<TemplateSection> SectionTemplates { get; set; }
...........
}
Here is my database creation script:
CREATE TABLE [templates]
(
[id] INT NOT NULL IDENTITY(1, 1),
[name] NVARCHAR(300) NOT NULL,
);
GO;
CREATE TABLE [sections_to_templates]
(
[section_id] INT NOT NULL, //FK to [sections]
[template_id] INT NOT NULL, //FK to [templates]
[order] INT NOT NULL DEFAULT(0)
);
GO
CREATE TABLE [sections]
(
[id] INT NOT NULL IDENTITY(1, 1),
[name] NVARCHAR(300) NOT NULL,
);
GO
And here in my models binding code, I am absoulte not sure that it is correct:
modelBuilder.Entity<Template>()
.HasKey(t0 => t0.Id)
.Map(m => m.ToTable("templates"))
.Property(x => x.Id)
.HasColumnName("id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
.IsRequired();
modelBuilder.Entity<Template>()
.HasMany(t0 => t0.TemplateSection)
.WithRequired(t1 => t1.Template)
.Map(??????????????????)
modelBuilder.Entity<TemplateSection>()
.HasKey(t0 => t0.Id)
.Map(m => m.ToTable("sections_to_templates"))
.Property(x => x.Id)
.HasColumnName("id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
.IsRequired();
modelBuilder.Entity<TemplateSection>()
.HasRequired(t0 => t0.Template)
.WithMany(t1 => t1.TemplateSection)
.Map(m => m.MapKey("template_id"));
modelBuilder.Entity<TemplateSection>()
.HasRequired(t0 => t0.Section)
.WithMany(t1 => t1.SectionTemplates)
.Map(m => m.MapKey("section_id"));
//How to describe Section class????
You are almost done in my opinion:
Remove the second mapping block, it is redundant because the relationship is already covered in the fourth mapping block.
You can remove the IsRequired() mappings because non-nullable properties can only be required.
You can remove the HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity) mappings because integer keys are database generated identities by default.
For the string properties Name you can add:
modelBuilder.Entity<Template>()
.Property(x => x.Name)
.HasColumnName("name")
.HasMaxLength(300)
.IsRequired();
// the same for Section
"How to describe Section class????": Exactly the same way as your first mapping block.