I'm working on a site where we want to include a pie chart on a page. Right now I'm working on implementing that through Reporting Services (RDLC file) with an object data source.
The SelectMethod of my object data source is a method that returns a list of business objects; lets say a List<Alpha> and Alpha has a sub object Beta with a Name property. In my report definition I have set the Category groups to be: =Fields!Beta.Value.Name this means that Alpha.Beta.Name are my pie slices. I got the following error:
An error has occurred during report processing. The Group expression for the grouping 'chart1_CategoryGroup1' contains an error: Object variable or With block variable not set.
I was able to confirm this is because Beta is nullable and was able to fix the issue by updating the object Alpha to return a new Beta() if the Beta property is null. This solution is not ideal though because there are other places in my code where I need Beta to be null if it doesn't have a value yet.
Is there a way to update the report definition to accept a null property as valid? Ideally I would like to specify the value as "Not Set" if Beta is null.
I had similar problem as yours, and I solved it using Null Object Refactoring ( many thanks to Martin Fowler's book Refactoring :)).
Here you can find nice example Null object refactoring
So you could create class NullableBeta that inherits Beta, while properties Name and e.g. IsNullable are virtual on Beta entity.
public class Beta
{
public virtual Name{ get; set;}
public virtual IsSet{ get{return true;}}
}
public class NullableBeta:Beta
{
public override Name
{
get{
return "Not Set";
}
set{}
}
public override IsSet{ get{ return false;}}
}
Now, if Beta is not set on Alfa entity, you could return instance of NullableBeta entity. In reports, you would have "Not Set" instead of empty string, and on places where you are setting Beta entity to Alfa you can check IsSet property instead of checking if Beta is null.
Hope this was helpful
I had similar problem as yours, and I solved it using Null Object Refactoring ( many thanks to Martin Fowler's book Refactoring :)). Here you can find nice example Null object refactoring
I first deal with this shortcomings of SSRS/RDLC by implementing the Null object pattern as well.
Implementing this manually is of course too much effort when more then one or two domain objects are involved.
However, since I am already using AutoMapper, #LucianBargaoanu correctly pointed out in the comments that null objects are natively supported as an opt-in feature by AutoMapper, so there is no explicit implementation needed.
I therefore use AutoMapper with its AllowNullDestinationValues, AllowNullCollections, PreserveReferences(), NullSubstitute and ForAllPropertyMaps() features, to map all my domain classes to report specific classes and substitute all null references to either null objects (when mapping domain object null references to report objects) or reasonable default values (e.g. an empty string for null strings or the default value of the underlying primitive type for Nullable<PrimitiveType>).
Here is some sample code to demonstrate the approach:
namespace Domain
{
public class MyClass
{
public int Id {get; set;}
public string Name {get; set;} // could be null
public string Code {get; set;} // could be null
public decimal? SomeNullableValue {get; set;} // could be null
public MyOtherClass OptionalOtherClass {get; set;} // could be null
}
public class MyOtherClass
{
public int OtherId {get; set;}
public string OtherName {get; set;} // could be null
public decimal? SomeOtherNullableValue {get; set;} // could be null
}
}
namespace ReportViewModels
{
[Serializable]
public class MyClass
{
public int Id {get; set;}
public string Name {get; set;} // should not be null (but empty)
public string Code {get; set;} // should not be null (but empty)
public decimal? SomeNullableValue {get; set;} // should not be null (but default(decimal))
public string CommonName
=> (Name + " " + Code).Trim();
public MyOtherClass OptionalOtherClass {get; set;} // should not be null (but a MyOtherClass null object)
}
[Serializable]
public class MyOtherClass
{
public int OtherId {get; set;}
public string OtherName {get; set;} // should not be null (but empty)
public decimal? SomeOtherNullableValue {get; set;} // should not be null (but default(decimal))
}
}
public partial class Form1 : Form
{
private Context _context;
private ReportObjectGenerator _reportObjectGenerator;
public Form1(Context context, ReportObjectGenerator reportObjectGenerator)
{
_context = context;
_reportObjectGenerator = reportObjectGenerator;
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
var myDomainObjects = context.MyClass
.Include(e => e.OptionalOtherClass)
.ToList();
var myReportViewModels = _reportObjectGenerator.GetReportObjects<Domain.MyClass, ReportViewModels.MyClass>(myDomainObjects);
components ??= new System.ComponentModel.Container();
//reportViewer1.LocalReport.ReportEmbeddedResource = "MyNamespace.Report1.rdlc";
reportViewer1.LocalReport.ReportPath = "Report1.rdlc";
reportViewer1.LocalReport.DataSources.Clear();
reportViewer1.LocalReport.DataSources.Add(
new ReportDataSource
{
Name = "MyClassDataSet",
Value = new BindingSource(components)
{
DataMember = "MyClass",
DataSource = myReportViewModels
}
});
reportViewer1.RefreshReport();
}
}
public class ReportObjectGenerator
{
public List<TDestination> GetReportObjects<TSource, TDestination>(
IEnumerable<TSource> sourceObjects)
{
var domainNamespace = typeof(TSource).Namespace ?? throw new InvalidOperationException();
var reportNamespace = typeof(TDestination).Namespace ?? throw new InvalidOperationException();
var mapper = new MapperConfiguration(
cfg =>
{
cfg.AllowNullDestinationValues = false;
cfg.AllowNullCollections = false;
var allTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).ToList();
var allDomainTypes = allTypes.Where(t => t.Namespace?.StartsWith(domainNamespace) ?? false).ToList();
var allReportTypes = allTypes.Where(t => t.Namespace?.StartsWith(reportNamespace) ?? false).ToList();
foreach (var reportClassType in allReportTypes)
{
var domainClassType = allDomainTypes.Single(t => t.Name == reportClassType.Name);
cfg.CreateMap(domainClassType, reportClassType)
.PreserveReferences();
}
// If we want to set the default value of the underlying type of Nullable<UnderlyingType>
// properties in case they would be null, than AllowNullDestinationValues is not enough and we
// need to manually replace the null value here.
cfg.ForAllPropertyMaps(
pm => pm.SourceMember.GetMemberType().IsNullableType(),
(p, _) => p.NullSubstitute ??= Activator.CreateInstance(p.SourceMember.GetMemberType().GetTypeOfNullable()));
})
.CreateMapper();
return mapper.Map<IEnumerable<TSource>, List<TDestination>>(sourceObjects);
}
}
Related
So I am using .Net 3.1, and I have the following class:
public Addresses{
public int AddressID { get; set; }
public String Name {get; set;}
}
and I want to add a new Column to it like this
public Addresses{
public int AddressID { get; set; }
public String Name {get; set;}
public bool IsAddrLocal {get; set;}
}
but I want the new column IsAddrLocal to have a default value of True for all columns that have already been created in the database.
I tried adding doing this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CompanyShippingAddress>(entity =>
{
entity.Property(x=>x.IsAddrLocal).HasDefaultValue(true);
});
}
however it would cause my values to be always true (even the new ones being entered) so I tried to make the field nullable
public Addresses{
public int AddressID { get; set; }
public String Name {get; set;}
public bool? IsAddrLocal {get; set;}
}
Which would cause old addresses to have the value NULL assigned to them and only new ones have the True value assigned to them.
I tried adding a constructor to the class and making the field non-nullable again
public Addresses()
{
IsAddrLocal= true;
}
but this would cause old addresses to get a value of False and new ones a value of True.
How can I make the default value of this field True for new and old values unless later on a User sends a request to update the field for a specific row to false?
From your description, your question is that when adding the IsAddrLocal property, you want to set the default value to true, then the new address without set the IsAddrLocal property and the existed Address in the database should also set the default value. But if the new address has the IsAddrLocal property, it should store its value.
To solve this issue, you can try to use the following method:
Assume you have create the Address table with two columns: AddressID and Name, and insert some value.
Add the IsAddrLocal property in the Addresses model.
public class Addresses
{
[Key]
public int AddressID { get; set; }
public string Name { get; set; }
public bool IsAddrLocal { get; set; } = true; //set the default value.
}
[Note]There is no need to use the HasDefaultValue to set the default value. Remove this part of code from the OnModelCreating method.
Execute the Add-Migration adddefaultvaluerul command to generate the migration file.
Open the adddefaultvaluerul migration file, in the Up method, change the defaultvalue from false to true.
Execute the update-database comment, then you can see the existed Address has been set the default value.
Then, when you insert new address, like this:
var items = new List<Addresses>()
{
new Addresses(){ Name="A1", IsAddrLocal=false },
new Addresses(){ Name="A2"},
new Addresses(){ Name="A3" ,IsAddrLocal=false },
};
_dbcontext.Addresses.AddRange(items);
_dbcontext.SaveChanges();
the output as below:
I have an entity
public class MyEntity
{
public string Id {get; set;}
public ObjectNode Node;
}
ObjectNode is the base class for specific nodes:
public class ObjectNode
{
public string NodeType {get; set;}
public string Id { get; set; }
}
Then, there are various subclasses of ObjectNode, for example:
public class DocumentNode : ObjectNode
{
public string Path { get; set;}
}
I configure:
modelBuilder.HasDefaultContainer("MyEntity");
I create a MyEntity instance like this:
var ent = new MyEntity {
Id = "entity1",
Node = new DocumentNode {
Id = "node1",
NodeType = "Document"
Path = "some document path"
}
};
When storing it in Cosmos DB, the result is:
{
"Id" : "entity1",
"Node" : {
"Id" : "node1",
"NodeType" : "Document"
}
]
}
The Path property doesn't show up and when deserializing, I'm getting back an ObjectNode instead of a DocumentNode.
Only by changing NodeType Node to Document Node in my model, would I get all properties. But obviously then it won't work for other derived types.
How can this behavior be fixed?
I tried to declare DocumentNode as an entity using modelBuilder.Entity<DocumentNode>, but that yields a runtime error about a missing partition key property - which is right, as I don't want this to be stored as a separate document but just as a nested element.
I'm trying to save to my table Users let's say, string ID, string email, and string password. The problem is that ID must be a guid that I have to create it and save it and not SQL server. Any ideas how?
I searched but I only found how to make SQL server to create the guid.
First of all, tell Entity framework that you will generate the value of the primary key:
Use DatabaseGenerated Attribute
public class School
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name {get; set;}
...
}
The None option prevents values from being generated by the database automatically in cases where they would otherwise be created.
Furthermore, consider to overwrite DbContext.SaveChanges(). In this procedure ask the ChangeTracker for all elements that are Added. Generate an Id for every Added element. It might be dangerous to let others generate an Id, because they might be adding a constant value or just an auto-increment.
Another possibility would be to generate it within the Add function, but if you do that, then users could change your generated Id. So the proper place is within SaveChanges:
public override int SaveChanges()
{
var addedElements = this.ChangeTracker.Entries
.Where(entry => entry.State == EntityState.Added);
foreach(var addedElement in addedElements)
{
// This will fail: the added element doesn't have a property Id:
addedElement.Id = GenerateId();
}
return base.SaveChanges();
}
For this you have to be certain that every added element has a property Id. The simplest way is to create an interface and let all your tables implement this interface:
public interface IID
{
string Id {get; set;}
}
public class School : IID {...}
public class Student : IID {...}
public class Teacher : IID {...}
public class DbContext
{
public DbSet<School> Schools {get; set;}
public DbSet<Student> Students{get; set;}
public DbSet<Teacher> Teachers {get; set;}
public override int SaveChanges()
{
var addedElements = this.ChangeTracker.Entries.Cast<IID>
.Where(entry => entry.State == EntityState.Added);
foreach(var addedElement in addedElements)
{
addedElement.Id = GenerateId(); // Every Added element implements IId
}
return base.SaveChanges();
}
private string GenerateId()
{
... // TODO: return unique ID, for instance a GUID
}
}
I have a List<Role> (see below) that I am binding to an asp.net gridview. I want to sort this data using SortExpression, such that it is sorted by two properties of sub-objects of the rows. Specifically, I want to sort by the Application's Name, then the ApplicationType's ApplicationTypeName.
How can I do this?
The classes here are:
public class Application
{
public string Name {get; set;}
public int Status {get; set;}
}
public class ApplicationType
{
public string ApplicationTypeName {get; set;}
public int ApplicationTypeStatus {get; set;}
}
public class Role
{
public Application oApplication {get; set;}
public ApplicationType oApplicationType {get; set;}
}
Edit: note that I was responding to the earlier verison of the question, before it related to gridview; still, this might be useful...
Worst case: you can use the approach here to pre-sort the list before binding it to the gridview.
Various options:
implement IComparable[<T>]
implement IComparer[<T>]
use an ad-hoc sort
I'm guessing you just need the last, so perhaps:
list.Sort((x,y) => {
int delta = string.Compare(x.Application.Name, y.Application.Name);
if (delta == 0) delta = string.Compare(
x.ApplicationType.ApplicationTypeName, y.ApplicationType.ApplicationTypeName);
return delta;
});
Alternatively, you can perhaps do it via LINQ in the source data - note however that this is done when creating a new list - it isn't an in-place sort of an existing list:
var list = source.OrderBy(x => x.Application.Name)
.ThenBy(x => x.ApplicationType.ApplicationTypeName)
.ToList();
Here is my Session Class
public static class Sessions
{
public class UserSession
{
public string CurrentSelected
{
get;
set;
}
public string Req
{
get;
set;
}
public DateTime Reque
{
get;
set;
}
public List<Options> Option;
}
public class Option
{
public string Te;
public string Fe;
public string Fg;
public string DE;
}
}
I create a new session of my class
Session["SessionStats"] = new UserSession();
Then I try to add to the List
foreach(string hello in helloworld) {
Options RO = new Options();
RO.DE = item.GetDataKeyValue("DE").ToString();
RO.Fg = item.GetDataKeyValue("Fg").ToString();
RO.Fe = item.GetDataKeyValue("Fe").ToString();
RO.Te = item.GetDataKeyValue("Te").ToString();
}
This is where the error occurs
((UserSession)Session["SessionStats"]).Options.Add(RO);
RO is correctly populated but ((UserSession)Session["SessionStats"]).Option is null, I'm not sure how to add RO to this list. This has to be a list because I have like 10 RO's I need to put in this list.
After
Session["SessionStats"] = new UserSession();
you have added a new UserSession, whose Option property is null, to Session. Then, when you do
((UserSession)Session["SessionStats"]).Options.Add(RO);
you are pulling out that very same object and accessing the Option property, which is null, hence the NullReferenceException.
It looks like you are forgetting to assign something to the newly created UserOption's Option property. However, you don't seem to be using the Options you are instantiating in the foreach for anything...
You are mixing 'Option' and 'Options'. Change the class name from 'Option' to 'Options'. Then change this line:
((UserSession)Session["SessionStats"]).Options.Add(RO);
to
((UserSession)Session["SessionStats"]).Option.Add(RO);