I have a code block regards get a list like that in aspnetzero:
public async Task<List<DMNCCThueSuatDto>> GetDSThueSuat()
{
using (_unitOfWorkManager.Current.SetTenantId(null))
{
var lstthueSuat = await _dmThueSuatRepository.Query(t => t.Where(i =>
i.IsDeleted == false)).OrderBy("thuesuat_ma asc").ToListAsync();
return ObjectMapper.Map<List<DMNCCThueSuatDto>>(lstthueSuat);
}
}
I expected a list of DMNCCThueSuatDto is returned but the error
Cannot create a DbSet for 'DM_NCC_ThueSuat' because this type is not
included in the model for the context.
is always displayed.
Also, i had myown a declaration
public virtual DbSet DS_ThueSuat { get; set; }
in my DBContext.
In my mariadb database, i had a table called "vs_dm_ncc_thuesuat"
and i have already declare a class for mapping to the table above
namespace VS.vHoaDon.Domains.DanhMuc.DMNhaCungCap
{
[Table("vs_dm_ncc_thuesuat")]
[MultiTenancySide(MultiTenancySides.Host)]
public class DM_NCC_ThueSuat : FullAuditedEntity
{
public int ThueSuat_Ma { get; set; }
public string ThueSuat_Ten { get; set; }
public int ThueSuat_GiaTri { get; set; }
public bool ThueSuat_HieuLuc { get; set; }
public DateTime? ThueSuat_BatDau { get; set; }
public DateTime? ThueSuat_KetThuc { get; set; }
}
}
I don't know why?
Any helps is appreciated.
Thank you so much.
As I'm developping an asp net core + ef core 2.0 with localized objects in my model, I adapted the solution provided in the following link to localize my objects link.
I'm now trying to find a clean way to update my collection of translation when updated object are received in the controller.
For the moment I have a step model class defined this way :
public class Step
{
//Native properties
public Guid ID { get; set; }
public string Name { get; set; }
public int Order { get; set; }
public string ScriptBlock { get; set; }
//Parent Step Navigation property
public Nullable<Guid> ParentStepID { get; set; }
public virtual Step ParentStep { get; set; }
//Collection of sub steps
public virtual ICollection<Step> SubSteps { get; set; }
//MUI Properties
public TranslationCollection<StepTranslation> Translations { get; set; }
public string Description { get; set; }
//{
// get { return Translations[CultureInfo.CurrentCulture].Description; }
// set { Translations[CultureInfo.CurrentCulture].Description = value; }
//}
public Step()
{
//ID = Guid.NewGuid();
Translations = new TranslationCollection<StepTranslation>();
}
}
public class StepTranslation : Translation<StepTranslation>
{
public Guid StepTranslationId { get; set; }
public string Description { get; set; }
public StepTranslation()
{
StepTranslationId = Guid.NewGuid();
}
}
Translation and translationCollection are the same as in the link
public class TranslationCollection<T> : Collection<T> where T : Translation<T>, new()
{
public T this[CultureInfo culture]
{
// indexer
}
public T this[string culture]
{
//indexer
}
public bool HasCulture(string culture)
{
return this.Any(x => x.CultureName == culture);
}
public bool HasCulture(CultureInfo culture)
{
return this.Any(x => x.CultureName == culture.Name);
}
}
public abstract class Translation<T> where T : Translation<T>, new()
{
public Guid Id { get; set; }
public string CultureName { get; set; }
protected Translation()
{
Id = Guid.NewGuid();
}
public bool HasProperty(string name)
{
return this.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Any(p => p.Name == name);
}
}
My issue in this sample is how to deal correctly with the PUT method and the Description property of my step controller. When it receive a Step object to update (which is done through a native c# client) only the string Description property of Step might have been created/updated/unchanged. So I have to update/create/do Nothing on the Description of the translation in the correct culture.
My first guess is to add in the TranslationCollection class a method in which I could pass the culture, the name of the property to update or not (Description in this case) and the value of the Description.
But as the TranslationCollection is a collection of abstract objects I don't even if this is a good idea and if it's possible.
If someone would have any advice on it (hoping I was clear enough) it would be great !
Finally answered my own question, and it was quite simple.
Just had to use the indexer like :
myobject.Translations[userLang].Name = value;
Using Web API and OData, I have a service which exposes Data Transfer Objects instead of the Entity Framework entities.
I use AutoMapper to transform the EF Entities into their DTO counter parts using ProjectTo():
public class SalesOrdersController : ODataController
{
private DbContext _DbContext;
public SalesOrdersController(DbContext context)
{
_DbContext = context;
}
[EnableQuery]
public IQueryable<SalesOrderDto> Get(ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
}
[EnableQuery]
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
}
}
AutoMapper (V4.2.1) is configured as follows, note the ExplicitExpansion() which prevents serialisation auto expanding navigation properties when they are not requested:
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());
cfg.CreateMap<SalesOrderLine, SalesOrderLineDto>()
.ForMember(dest => dest.MasterStockRecord, opt => opt.ExplicitExpansion())
.ForMember(dest => dest.SalesOrderHeader, opt => opt.ExplicitExpansion());
ExplicitExpansion() then creates a new problem where the following request throws an error:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines
The query specified in the URI is not valid. The specified type member 'SalesOrderLines' is not supported in LINQ to Entities
The navigation property SalesOrderLines is unknown to EF so this error is pretty much what I expected to happen. The question is, how do I handle this type of request?
The ProjectTo() method does have an overload that allows me to pass in an array of properties that require expansion, I found & modified the extension method ToNavigationPropertyArray to try and parse the request into a string array:
[EnableQuery]
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config, null, queryOptions.ToNavigationPropertyArray());
}
public static string[] ToNavigationPropertyArray(this ODataQueryOptions source)
{
if (source == null) { return new string[]{}; }
var expandProperties = string.IsNullOrWhiteSpace(source.SelectExpand?.RawExpand) ? new List<string>().ToArray() : source.SelectExpand.RawExpand.Split(',');
for (var expandIndex = 0; expandIndex < expandProperties.Length; expandIndex++)
{
// Need to transform the odata syntax for expanding properties to something EF will understand:
// OData may pass something in this form: "SalesOrderLines($expand=MasterStockRecord)";
// But EF wants it like this: "SalesOrderLines.MasterStockRecord";
expandProperties[expandIndex] = expandProperties[expandIndex].Replace(" ", "");
expandProperties[expandIndex] = expandProperties[expandIndex].Replace("($expand=", ".");
expandProperties[expandIndex] = expandProperties[expandIndex].Replace(")", "");
}
var selectProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawSelect) ? new List<string>().ToArray() : source.SelectExpand.RawSelect.Split(',');
//Now do the same for Select (incomplete)
var propertiesToExpand = expandProperties.Union(selectProperties).ToArray();
return propertiesToExpand;
}
This works for expand, so now I can handle a request like the following:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines
or a more complicated request like:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($expand=MasterStockRecord)
However, more complicated request that try to combine $select with $expand will fail:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($select=OrderQuantity)
Sequence contains no elements
So, the question is: am I approaching this the right way?
It feels very smelly that I would have to write something to parse and transform the ODataQueryOptions into something EF can understand.
It seems this is a rather popular topic:
odata-expand-dtos-and-entity-framework
how-to-specify-the-shape-of-results-with-webapi2-odata-with-expand
web-api-queryable-how-to-apply-automapper
how-do-i-map-an-odata-query-against-a-dto-to-another-entity
While most of these suggest using ProjectTo, none seem to address serialisation auto expanding properties, or how to handle expansion if ExplictExpansion has been configured.
Classes and Config below:
Entity Framework (V6.1.3) entities:
public class SalesOrderHeader
{
public string SalesOrderNumber { get; set; }
public string Alpha { get; set; }
public string Customer { get; set; }
public string Status { get; set; }
public virtual ICollection<SalesOrderLine> SalesOrderLines { get; set; }
}
public class SalesOrderLine
{
public string SalesOrderNumber { get; set; }
public string OrderLineNumber { get; set; }
public string Product { get; set; }
public string Description { get; set; }
public decimal OrderQuantity { get; set; }
public virtual SalesOrderHeader SalesOrderHeader { get; set; }
public virtual MasterStockRecord MasterStockRecord { get; set; }
}
public class MasterStockRecord
{
public string ProductCode { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
}
OData (V6.13.0) Data Transfer Objects:
public class SalesOrderDto
{
[Key]
public string SalesOrderNumber { get; set; }
public string Customer { get; set; }
public string Status { get; set; }
public virtual ICollection<SalesOrderLineDto> SalesOrderLines { get; set; }
}
public class SalesOrderLineDto
{
[Key]
[ForeignKey("SalesOrderHeader")]
public string SalesOrderNumber { get; set; }
[Key]
public string OrderLineNumber { get; set; }
public string LineType { get; set; }
public string Product { get; set; }
public string Description { get; set; }
public decimal OrderQuantity { get; set; }
public virtual SalesOrderDto SalesOrderHeader { get; set; }
public virtual StockDto MasterStockRecord { get; set; }
}
public class StockDto
{
[Key]
public string StockCode { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
}
OData Config:
var builder = new ODataConventionModelBuilder();
builder.EntitySet<StockDto>("Stock");
builder.EntitySet<SalesOrderDto>("SalesOrders");
builder.EntitySet<SalesOrderLineDto>("SalesOrderLines");
I have created an Automapper explicit navigation expansion utility function that should work with N-deph expands. Posting it here since it might help someone.
public List<string> ProcessExpands(IEnumerable<SelectItem> items, string parentNavPath="")
{
var expandedPropsList = new List<String>();
if (items == null) return expandedPropsList;
foreach (var selectItem in items)
{
if (selectItem is ExpandedNavigationSelectItem)
{
var expandItem = selectItem as ExpandedNavigationSelectItem;
var navProperty = expandItem.PathToNavigationProperty?.FirstSegment?.Identifier;
expandedPropsList.Add($"{parentNavPath}{navProperty}");
//go recursively to subproperties
var subExpandList = ProcessExpands(expandItem?.SelectAndExpand?.SelectedItems, $"{parentNavPath}{navProperty}.");
expandedPropsList = expandedPropsList.Concat(subExpandList).ToList();
}
}
return expandedPropsList;
}
You can call it with :
var navExp = ProcessExpands(options?.SelectExpand?.SelectExpandClause?.SelectedItems)
it will return a list with ["Parent" ,"Parent.Child"]
I never really managed to work this one out. The ToNavigationPropertyArray() extension method helps a little, but does not handle infinite depth navigation.
The real solution is to create Actions or Functions to allow clients to request data requiring a more complicated query.
The other alternative is to make multiple smaller/simple calls then aggregate the data on the client, but this isn't really ideal.
When you want to mark something for explicit expansion in AutoMapper, you need to also opt-back-in when calling ProjectTo<>().
// map
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());
// updated controller
[EnableQuery]
public IQueryable<SalesOrderDto> Get()
{
return _dbContext.SalesOrders
.ProjectTo<SalesOrderDto>(
AutoMapperConfig.Config,
so => so.SalesOrderLines,
// ... additional opt-ins
);
}
While the AutoMapper wiki does state this, the example is perhaps a little misleading by not including the paired ExplicitExpansion() call.
To control which members are expanded during projection, set ExplicitExpansion in the configuration and then pass in the members you want to explicitly expand:
I have this model
namespace ProjectTimer.Models
{
public class TimerContext : DbContext
{
public TimerContext()
: base("DefaultConnection")
{
}
public DbSet<Project> Projects { get; set; }
public DbSet<ProjectTimeSpan> TimeSpans { get; set; }
}
public class DomainBase
{
[Key]
public int Id { get; set; }
}
public class Project : DomainBase
{
public UserProfile User { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IList<ProjectTimeSpan> TimeSpans { get; set; }
}
[ComplexType]
public class ProjectTimeSpan
{
public DateTime TimeStart { get; set; }
public DateTime TimeEnd { get; set; }
public bool Active { get; set; }
}
}
When I try to use this action I get the exception The type 'ProjectTimer.Models.ProjectTimeSpan' has already been configured as an entity type. It cannot be reconfigured as a complex type.
public ActionResult Index()
{
using (var db = new TimerContext())
{
return View(db.Projects.ToList);
}
}
The view is using the model #model IList<ProjectTimer.Models.Project>
Can any one shine some light as to why this would be happening?
Your IList<ProjectTimeSpan> property is not supported by EF. A complex type must always be part of another entity type, you cannot use a complex type by itself. If you absolutely need to have ProjectTimeSpan as a complex type, you will need to create a dummy entity type that only contains a key and a ProjectTimeSpan, and change the type of Project.TimeSpans to a list of that new type.
I'm wondering what's the best way to handle default values for relationships when making models. (Specifically EF4)
For example, my Organization has a default Contact and I was wondering which one was the best approach. I got these two options (or any other anyone suggests if better)
Using Relationship:
public class Contact
{
public int Id { get; set; }
public string FirstName { get; set; }
}
public class Organization
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Contact> Contacts { get; set; }
//Use a relationship for the default contact?
public Contact DefaultContact { get; set; }
}
Using Value:
public class Contact
{
public int Id { get; set; }
public string FirstName { get; set; }
//Use value?
public boolean IsDefault { get; set; }
}
public class Organization
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Contact> Contacts { get; set; }
}
I'd go with Option 1. While 2 is definitely easier to implement, it doesn't enforce rules such as "There cannot be 2 default contacts". I end up with something like the following:
public class Organization {
// ...
public virtual ICollection<Contact> { get;set; }
[ForeignKey("DefaultContactId")]
public Contact DefaultContact { get;set; }
public int? DefaultContactId { get;set; }
}
There's a limitation of this approach - it doesn't work nested deletes (see this question for more details). Because of this, you need to disable CascadeOnDelete for the 1-to-many relationship:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>().HasRequired(co => co.Organization).WithMany().WillCascadeOnDelete(false);
}
(Code done without testing, but should work)
The other problem with this is that it's not possible to add the Default Contact at the same time as you're adding the organization, as EF can't figure out the correct order of statements. You need to call .SaveChanges between each. You can still use a TransactionScope to overcome this, but it's not clean:
using (var ts = new TransactionScope())
{
Organization org = new Organization
{
// ...
Contacts = new Collection<Contact>()
}
org.Contacts = new Contact() {};
orgRepo.SaveChanges();
// Now wire up the default contact
org.DefaultContact = org.Contacts.First();
orgRepo.SaveChanges();
}