I don't seem to figure it out how to use enum values in an ASP.NET MVC 3 model (using code first approach) so that they are stored in a database and are preserved.
My model code looks like:
public class TestNews
{
public int Id { get; set; }
[Required]
public DateTime Date { get; set; }
[Required]
//[EnumDataType(typeof(TestNewsType))]?
public TestNewsType Type { get; set; }
[Required]
public string Title { get; set; }
}
public enum TestNewsType : short
{
Draft = 0,
PublishedPublicly = 1,
PublishedInternally = 2,
Removed = 3
}
After the database gets recreated it contains the table TestNews, but it contains only the columns:
Id
Date
Title
How to make it also store the Type in the database? And also should the EnumDataType annotation be used?
I would later like to use it in a controller action like:
public ActionResult Latest(int count=5)
{
var model = db.TestNews
.Where(n => n.Type == TestNewsType.PublishedPublicly)
.OrderByDescending(n => n.Date)
.Take(count)
.ToList();
return PartialView(model);
}
The June CTP of Entity Framework introduced Enum support.
As an alternative to using the CTP, at the MSDN blogs is an article on how to fake enums with Entity Framework.
Related
Note: These classes are related, but not part of the same Aggregate (like PurchaseOrder and OrderLine) - so I do not have a navigation property from "One" to "Many".
=== Entities ===
public class One
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Many
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid One { get; set; }
}
=== Contracts ===
public class OneWithMany
{
public Guid Id { get; set; }
public string Name { get; set; }
public IEnumerable<Many>? ManyRelatedObjects { get; set; }
}
I want to select all One objects and any related Many objects from DbSet/DbSet into OneWithMany.
To ensure I don't miss properties added in future I am using ProjectTo in AutoMapper - but I can't work out how to fit it into the equation.
Unfortunately, it seems Entity Framework does not support GroupJoin.
The solution is to do the projection and as much filtering as possible as two separate queries, and then combine them into a result in memory.
If you find EF related answers on the web related to GroupJoin make sure you check the example code to see if they are actually showing code working on arrays instead of DbSet.
I created an Entity Framework app using NetCore 2.2. It's just a simple app that has models and controllers that were scaffolded from my database.
In my database, if I run this query, I will get the expected 4 rows of data back.
select * from BookSeries where mainBookID = 'akfj36hf'
In my controller, I'm trying to mimic the SQL above, with this code:
[HttpGet("BookSeriesByMain/{id}")]
public async Task<List<BookSeries>> GetBookSeries(string id)
{
return await _context.BookSeries.Where(n => n.MainBookId == id).ToListAsync();
}
When I hit the controller URL, with the same id of 'akfj36hf', it is returning EVERY row in the database for that table(BookSeries). Not just the expected 4 rows.
Here is my model:
public partial class BookSeries
{
public string BookLinkId { get; set; }
public string MainBookId { get; set; }
public string ChildBookId { get; set; }
public int? OrderId { get; set; }
public virtual BookList MainBook { get; set; }
}
I'm kind of at a loss as to what I did wrong.
Does anyone see anything that I messed up?
THanks!
I'm developing a small app in order to better understand how MVC3 anda Razor works. I'm using MVC3, all code was generated automatically (dbContext via T4, Controller via Add Controller, Databese from EDMX model...).
In my model I have this simple model:
http://i.stack.imgur.com/nyqu4.png
public partial class Application
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ApplicationType ApplicationType { get; set; }
}
public partial class ApplicationType
{
public int Id { get; set; }
public string Type { get; set; }
}
As you can see, ApplicationType is basically an enum (shame that EF 4 has no support for enums). So, in my ApplicationController I have this:
public ActionResult Create()
{
ViewBag.AppTypes = new SelectList(db.ApplicationTypes.OrderBy(c => c.Type), "Id", "Type");
return View();
}
[HttpPost]
public ActionResult Create(Application application)
{
if (ModelState.IsValid)
{
db.Applications.Add(application);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(application);
}
And in my view:
#Html.DropDownListFor(model => model.ApplicationType.Id, (SelectList)ViewBag.AppTypes, "Choose...")
Now I'm facing two problems:
1) ApplicationType not being populated:
As #Html.DropDownListFor renders only a simple select, it fills the ID, but does not fill Type property as you can see below (sorry, I can't post images as I'm new here):
http://i.stack.imgur.com/96IR1.png
In the picture you can see that the ID is ok, but Type is empty.
What I'm doing wrong?
2) Duplicated Data
The second problem is that if I fill the Type property manually during debug (simulating a correct workflow scenario), ApplicationType is being duplicated in the database, instead of only referring to an old registry.
So, how can I make #Html.DropDownListFor refer to a previous existing item instead of creating a new one?
Thanks for your help!
I believe the mistake you're making is using your domain models in the view and assuming that on post the entire model should be completely binded and ready to store in the database. While it is possible to use domain models in the view, it's better practice to create separate View Models.
For example :
public class ApplicationViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public SelectList ApplicationTypeList { get; set; }
public string ApplicationTypeId { get; set; }
}
In your view:
#Html.DropDownListFor(model => model.ApplicationTypeId, Model.ApplicationTypeList , "Choose...")
In your controller
[HttpPost]
public ActionResult Create(ApplicationViewModel model)
{
if (ModelState.IsValid)
{
Application application = new Application()
{
Id = model.Id,
Name = model.Name,
ApplicationType = db.ApplicationTypes
.First(a => a.Id == model.ApplicationTypeId);
};
db.Applications.Add(application);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
You can then make verifying that your View Model's ApplicationTypeId corresponds to a real application type part of your modelstate's verification. You can use AutoMapper to speed up the process of converting view models to domain models.
Have you tried:
#Html.DropDownListFor(model => model.ApplicationType.Id, m => m.ApplicationType.Type, "Choose...")
Note the second parameter change.
Here is view models
public class ArticleViewModel
{
public string ID { get; set; }
public string Title{ get; set; }
public string Body { get; set; }
public List<BETag> TagsList { get; set; }
}
public class BETag
{
public string ID { get; set; }
public string Name { get; set; }
}
An action
[HttpPost, AuthorizeEx]
public ActionResult AddArticle(ArticleViewModel articleViewModel)
{
//articleViewModel.Tags.Count == 0
//Request.Form["TagsList"] == "tag1, tag2..."
}
and a part of AddArticle.cshtml
#Html.TextAreaFor(m => m.TagsList )
My question is why articleViewModel.Tags.Count is equal 0, but
Request.Form["TagsList"] is equal "tag1, tag2..."? How to bind ArticleViewModel properly?
Lists don't work that way in MVC. You need to use something like EditorFor(m => m.TagsList) and then you need to create a BETag EditorTemplate. But that's only part of the problem, and really won't work for you either.
What you really want is just a simple string that takes your list of tags, such as
public string TagListString {get;set;}
Then, in your controller, you parse the string and extract all your tags, then add them to the TagsList.
var tags = TagListString.Split(' '); // assumes only single space between tags,
// you should add more filtering to make sure
foreach(var tag in tags) {
TagList.Add(new BETag() { Name = tag });
}
MVC works with single items, not complex types. There is some built-in processing to breakdown complex types in some cases, and to automatically iterate over collections, but those don't work in your case because you want to edit all the items in a single field. So your only option is to parse the field in the post method and put the data where you want it.
I am not sure if this is possible but here is my situation.
Say I have a model like this:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
My View model looks like this:
public class ProductModel
{
public int Id { get; set; }
public string Name { get; set; }
public string CustomViewProperty { get; set; }
}
I am using my ProductModel to post back to a form and I don't care or need the Custom View Property. This mapping works fine as automapper drops the unknown properties.
What I would like to do is map my custom properties in only one direction. i.e.
Mapper.CreateMap<Product, ProductModel>()
.ForMember(dest => dest.CustomViewProperty //???This is where I am stuck
What ends up happening is when I call "ToModel", automapper dumps my unknown properties and nothing comes over the wire.
Like this.
var product = _productService.GetProduct();
var model = product.ToModel;
model.CustomViewProperty = "Hello World"; //This doesn't go over the wire
return View(model);
Is this possible? Thanks.
You should ignore unmapped properties:
Mapper.CreateMap<Product, ProductModel>()
.ForMember(dest => dest.CustomViewProperty, opt=>opt.Ignore());
or map them:
Mapper.CreateMap<Product, ProductModel>()
.ForMember(dest => dest.CustomViewProperty, opt=>opt.MapFrom(product=>"Hello world"));