I'm currently working on a Windows Forms application, and I would like to use a simple database which doesn't require a backing server or credentials to run, so I chose SQLite. But trying to get this thing to work so far was a nightmare.
Right now, I have a few, simple classes with simple properties, which I'd like to store in the database. I've appended the appropriate labels to everything ([Table("")] for classes, [PrimaryKey, AutoIncrement] for the Id property), but whenever I do Connection.CreateTable(CreateFlags.AutoIncPK) (it won't show the generic parameter, but it's there, I promise), it throws a NotSupportedException, saying "Don't know about MyProject.MyClass". I have also provided an empty, parameterless constructor in each class.
So, how do I make SQLite "know" about my class?
EDIT:
My Character.cs file:
[Table("Character")]
class Character
{
[PrimaryKey, AutoIncrement]
public int id { get; set; }
public string Name
{
get;
set;
}
public string FilePath
{
get;
set;
}
public Character()
{
}
public Character(string file)
{
this.FilePath = file;
this.Name = FetchName(file);
}
private string FetchName(string file)
{
string[] fileHolder = File.ReadAllLines("\\chars\\" + file);
foreach (string line in fileHolder)
{
if (line.ToLower().StartsWith("name"))
{
if (!line.Contains(';'))
return line.Split('=')[1].Trim().Trim('"');
else return line.Split('=')[1].Split(';')[0].Trim().Trim('"');
}
}
return string.Empty;
}
}
My Database.cs file:
class Database
{
private static SQLiteConnection Connection;
private static string ConnectionString = "MyProject.sqlite";
public Database()
{
if (!File.Exists("MyProject.sqlite"))
System.Data.SQLite.SQLiteConnection.CreateFile("MyProject.sqlite");
using (Connection = new SQLiteConnection(ConnectionString))
{
Connection.CreateTable<Character>(CreateFlags.AutoIncPK);
}
}
}
The class that describe your table has to be public.
In your example, the character class is not public.
You should change the declaration like this:
[Table("Character")]
public class Character
{
// your class code...
}
I know it's a bit late to answer the original question but since my Google-Fu led me here, I hope this will help others that encounter the same error message:
I started receiving this error message after I added as public static property to a class with a [table] attribute. After adding the [Ignore] attribute to the property, I no longer received the error message.
Related
For school homework I'm supposed to work out a class diagram in C#. Everything went smoothly, but I'm struggling with the constructor for the Track part.
So I think I have to convert a SectionTypes -> Section to put it in the LinkedList, but this doesn't seem logical to me, or am I missing something? Should I convert it in any way or is my overall code for Section wrong?
Here is the class diagram
Here is the part of Section:
namespace Model
{
public enum SectionTypes { Straight, LeftCorner, RightCorner, StartGrid, Finish }
internal class Section
{
public SectionTypes SectionType { get; set; }
}
}
And finally here is where I'm trying to make the constructor, Track:
namespace Model
{
internal class Track
{
public string Name { get; set; }
public LinkedList<Section> Sections { get; set; }
public Track(string name, SectionTypes[] sections)
{
Name = name;
// set Sections here
}
}
}
The error that I get is CS1503, when I try to add anything to Sections in the front, which means the types aren't the same.
Thanks for reading, and thank you for helping in advance!
Here's what I did. By the way, I renamed the SectionTypes enumeration to SectionType (that way, it reads SectionType.Finish, not SectionTypes.Finish).
First I created the enum the same as you:
public enum SectionType
{
Straight,
LeftCorner,
RightCorner,
StartGrid,
Finish,
}
and the Section class pretty much the same way:
public class Section
{
public SectionType SectionType { get; set; }
}
I'm not sure why the class diagram is laid out the way it is, but you need to translate a SectionType to a Section in order to get it to work. That's pretty easy; a Section is a pretty stupid/simple wrapper around a single SectionType. So, things end up looking like:
public class Track
{
public string Name { get; set; }
public LinkedList<Section> Sections { get; set; } = new LinkedList<Section>();
public Track(string name, SectionType[] sections)
{
Name = name;
foreach (var section in sections)
{
Sections.AddLast(new Section { SectionType = section });
}
}
}
Note that I construct the Sections LinkedList. It can either be done the way I show, or it could be done in the constructor. But, the magic is to convert the incoming SectionType[] sections into a collection of Section type. I'm sure that there is a way to do this with LINQ (though I don't have a lot of experience with the LinkedList collection). But, doing it explicitly like this makes it more clear.
I have a little design problem. Let's say I have a project that contains a large number of people. I want to allow the user to export those people to a CSV file with the information he chooses.
For example, He could choose Id, Name, Phone number and according to his choice I would create the file.
Of course, there is a simple of way doing it like if(idCheckBox.Checked) getId(); etc.
I'm looking for something better. I don't want that for each new option I would like to add I would need to change the UI (e.g. New checkbox).
I thought of reading the possible options from a file, but that will only solved the UI problem. How would I know which values to get without using all those "if's" again?
You don't need a fancy design pattern for this task. However I understand you have identified a reason to change (added options in future). So you want to minimize amount of classes to be modified.
Your real problem is how to decouple CSV creation from the objects whose structure is going to change. You don't want your parsing logic to be affected whenever your Person class is changed.
In the following example the CSV object is truly decoupled from the objects it receives and parses. To achieve this, we are coding to an abstraction rather to an implementation. This way we are not even coupled to the Person object, but will welcome any objects that implement the AttributedObject interface. This dependency is being injected to our CSV parser.
I implemented this in PHP, but the idea is the same. C# is a static language, so fetching the attributes would be with a bit of change. You might use some kind of ArrayAccess interface.
interface AttributedObject {
public function getAttribute($attribute);
}
class Person implements AttributedObject {
protected $firstName;
protected $lastName;
protected $age;
protected $IQ;
public function __construct($firstName, $lastName, $age, $IQ)
{
$this->firstName = $firstName;
$this->lastName = $lastName;
$this->age = $age;
$this->IQ = $IQ;
}
public function getAttribute($attribute)
{
if(property_exists($this, $attribute)) {
return $this->$attribute;
}
throw new \Exception("Invalid attribute");
}
}
class CSV {
protected $attributedObject = null;
protected $attributesToDisplay = null;
protected $csvRepresentation = null;
protected $delimiter = null;
public function __construct(AttributedObject $attributedObject, array $attributesToDisplay, $delimiter = '|')
{
$this->attributedObject = $attributedObject;
$this->attributesToDisplay = $attributesToDisplay;
$this->delimiter = $delimiter;
$this->generateCSV();
}
protected function generateCSV()
{
$tempCSV = null;
foreach ($this->attributesToDisplay as $attribute) {
$tempCSV[] = $this->attributedObject->getAttribute($attribute);
}
$this->csvRepresentation = $tempCSV;
}
public function storeCSV()
{
$file = fopen("tmp.csv", "w");
fputcsv($file, $this->csvRepresentation, $this->delimiter);
}
}
$person1 = new Person('John', 'Doe', 30, 0);
$csv = new CSV($person1, array('firstName', 'age', 'IQ'));
$csv->storeCSV();
You can build a mapping set of fields based what fields the user is allowed to select, and which fields are required. This data can be read from a file or database. Your import/export can be as flexible as needed.
Here is a conceivable data structure that could hold info for your import/export sets.
public class FieldDefinition
{
public FieldDataTypeEnum DataType { get; set; }
public string FieldName{get;set;}
public int MaxSize { get; set; }
public bool Required { get; set; }
public bool AllowNull { get; set; }
public int FieldIndex { get; set; }
public bool CompositeKey { get; set; }
}
public class BaseImportSet
{
private List<FieldDefinition> FieldDefinitions { get; set; }
protected virtual void PerformImportRecord(Fields selectedfields)
{
throw new ConfigurationException("Import set is not properly configured to import record.");
}
protected virtual void PerformExportRecord(Fields selectedfields)
{
throw new ConfigurationException("Export set is not properly configured to import record.");
}
public LoadFieldDefinitionsFromFile(string filename)
{
//Implement reading from file
}
}
public class UserImportSet : BaseImportSet
{
public override void PerformImportRecord(Fields selectedfields)
{
//read in data one record at a time based on a loop in base class
}
public override string PerformExportRecord(Fields selectedfields)
{
//read out data one record at a time based on a loop in base class
}
}
I've inherited a MVC project that seems to use Telerik Open Access to handle data instead of using something I'm more familiar with like entity framework. I'm trying to understand the whole concept of how to work with this data method, but right now I'm just needing to find out how to add a table. I've limited my code examples to one table, but in reality there are dozens of them.
So I see that the class OpenAccessContext.cs has a database connection string, but it also has a IQueryable item made up of the class tblMaterial. The tblMaterial class is defined in tblMaterial.cs. I don't understand how this class is connected to the SQL database version of tblMaterial (so feel free to educate me on that).
I have a table called tblContacts in the SQL database. What do I need to do to connect it to my project? There's no "update from database" option when I right click any object in the solution (because they're all just classes). Will I need to create a new class manually called tblContacts.cs? If so, how do I connect it to the database version of tblContacts? Am I going to need to manually change multiple classes to add the table (OpenAccessContext, MetadataSources, Repository, etc.)?
I tried to keep this as one simple question (how do I add a table) so I don't get dinged, but any light you can shine on the Telerik Open Access would be helpful. (Please don't ding me for asking that!) I checked out the Telerik documentation here: http://docs.telerik.com/data-access/developers-guide/code-only-mapping/getting-started/fluent-mapping-getting-started-fluent-mapping-api , but it's related to setting up a new open access solution. I need to know how to modify one (without ruining the already working code). Thank you in advance for your help!
Here's the solution as seen in Visual Studio:
Open Access
Properties
References
OpenAccessContext.cs
OpenAccessMetadataSources.cs
Repository.cs
tblMaterial.cs
Here's the code:
OpenAccessContext.cs
namespace OpenAccess
{
public partial class OpenAccessContext : OpenAccessContext
{
static MetadataContainer metadataContainer = new OpenAccessMetadataSource().GetModel();
static BackendConfiguration backendConfiguration = new BackendConfiguration()
{
Backend = "mssql"
};
private static string DbConnection = ConfigurationManager.ConnectionStrings["ConnString"].ConnectionString;
private static int entity = ConfigurationManager.AppSettings["Entity"] == "" ? 0 : int.Parse(ConfigurationManager.AppSettings["Entity"]);
public OpenAccessContext() : base(DbConnection, backendConfiguration, metadataContainer)
{
}
public IQueryable<tblMaterial> tblMaterials
{
get
{
return this.GetAll<tblMaterial>(); //.Where(a => a.EntityId == entity);
}
}
}
}
OpenAccessMetadataSources.cs
namespace OpenAccess
{
public class OpenAccessMetadataSource : FluentMetadataSource
{
protected override IList<MappingConfiguration> PrepareMapping()
{
var configurations = new List<MappingConfiguration>();
// tblMaterial
var materialConfiguration = new MappingConfiguration<tblMaterial>();
materialConfiguration.MapType(x => new
{
MaterialId = x.MaterialId,
MaterialName = x.MaterialName,
MaterialDescription = x.MaterialDescription,
MaterialActive = x.MaterialActive,
MaterialUsageType = x.MaterialUsageType,
AddDate = x.AddDate,
AddBy = x.AddBy,
ModDate = x.ModDate,
ModBy = x.ModBy
}).ToTable("tblMaterial");
materialConfiguration.HasProperty(x => x.MaterialId).IsIdentity(KeyGenerator.Autoinc);
}
}
}
Repository.cs
namespace OpenAccess
{
public class Repository : IRepository
{
#region private variables
private static OpenAccessContext dat = null;
#endregion private varibles
#region public constructor
/// <summary>
/// Constructor
/// </summary>
public Repository()
{
if (dat == null)
{
dat = new OpenAccessContext();
}
}
#endregion public constructor
#region Material (tblMaterials)
public int CreateMaterial(tblMaterial itm)
{
try
{
dat.Add(itm);
dat.SaveChanges();
return itm.MaterialId;
}
catch (Exception)
{
return 0;
}
}
}
}
tblMaterial.cs
namespace OpenAccess
{
public class tblMaterial
{
public int MaterialId { get; set; }
public string MaterialName { get; set; }
public string MaterialDescription { get; set; }
public bool MaterialActive { get; set; }
public int MaterialUsageType { get; set; }
public DateTime? AddDate { get; set; }
public string AddBy { get; set; }
public DateTime? ModDate { get; set; }
public string ModBy { get; set; }
}
}
In the case of tblContacts, I would suggest to you the following workflow for extending the model:
Add a new class file that will hold the definition of the tblContact POCO class. In this class add properties that will correspond to the columns of the table. The types of the properties should logically match the datatypes of the table columns.
In the OpenAccessMetadataSource class, add a new MappingConfiguration<tblContact> for the tblContact class and using explicit mapping provide the mapping details that logically connect the tblContact class with the tblContacts table. Make sure to add both the existing and the new mapping configurations to the configurations list.
Expose the newly added class through an IQueryable<tblContact> property in the context. This property will allow you to compose LINQ queries against the tblContacts table.
Regarding the Repository class, it seems like it is related to the custom logic of the application. It surely is not a file generated by Data Access. Therefore, you need to discuss it in your team.
I also strongly advise you against using OpenAccess in the namespaces of your application. This is known to interfere with the Data Access' namespaces during build time and at some point it causes runtime errors.
I hope this helps.
I keep running into this error:
An unhandled exception of type 'CsvHelper.CsvReaderException' occurred in CsvHelper.dll
Additional information: No properties are mapped for type 'RPS_String_Parse.Program+FormattedRow'.
But I believe I am following the documentation correctly. After referencing the "getting started" portion i implemented this:
using (var sr = new StreamReader(filePath))
{
var csv = new CsvReader(sr);
var records = csv.GetRecords<FormattedRow>();
foreach (var record in records)
{
Console.WriteLine(record.Address1);
}
Console.ReadLine();
}
and my class:
public class FormattedRow
{
public string IDOrderAlpha;
public string IDOrder;
public string AddressCompany;
public string Address1;
public string Address2;
public string AddressCity;
public string AddressState;
public string AddressZip;
public string AddressCountry;
public string ShipMethod;
public string ContactEmail;
public string ContactName;
public string ServiceRep;
public string CustomerPuchaseOrder;
}
I feel like this should work, because the documentation states:
Auto Mapping
If you don't supply a mapping file, auto mapping will be
used. Auto mapping will map the properties in your class in the order
they appear in. If the property is a custom class, it recursively maps
the properties from that class in the order they appear in. If the
auto mapper hits a circular reference, it will stop going down that
reference branch
What am I missing?
The documentation states that it will map to Properties. Your class has Fields. Make this change:
public class FormattedRow
{
public string IDOrderAlpha { get; set; }
// add { get; set; } for all
}
This will change your fields to "auto properties".
You need to set the configuration options for mapping:
var generatedMap = csv.Configuration.AutoMap<MyClass>();
So it appears you need to tell it to automap. I've never used this library before.
Edit: Jon B nailed it.
It's an EntLib-Validator-issue again. I'm playing with EntLib 5.0 in C# and .Net 4.0 on XP pro.
I have some business objects (partial classes) generated by T4 templates. So I decided to put their validation attributes in buddy-classes by using MetadataTypeAttribute as definitely recommended by the documentation of entLib 5.0 (msdn).
But the Validator object I get from the ValidatorFactory doesn't know about the validation attributes, defined in the metadata-class.
The business object is defined like this:
[MetadataType(typeof(PatientMetadata))]
public partial class Patient
{
private string _Name;
private int _DiagnosisCount;
public int DiagnosisCount
{
get
{
return _DiagnosisCount;
}
set
{
if (value != _DiagnosisCount)
{
_DiagnosisCount = value;
}
}
}
public string Name
{
get
{
return _Name;
}
set
{
if (value != _Name)
{
_Name = value;
}
}
}
}
And the metadata class like this, according to documentation:
public class PatientMetadata
{
[RangeValidator(4)]
public int DiagnosisCount { get; set; }
[StringLengthValidator(64, ErrorMessage = "Name must not exceed 64 chars.")]
public string Name { get; set; }
}
If I know try to do validation this way:
var factory = ValidationFactory.DefaultCompositeValidatorFactory;
var validator = factory.CreateValidator<Patient>();
...then watching into validator (during debugging) already says, that it's just an AndCompositeValidator without any children validators.
Again, if I put the validation attributes right in the Patient class, it works perfectly.
By now, I have no real idea, what I'm missing here, since I think doing everything according to the docs.
Thanks in advance to you guys!
The property names of the metadata class must match the property names of the main class.
In your case your metadata class should look like:
public class PatientMetadata
{
[RangeValidator(0, RangeBoundaryType.Inclusive, 10, RangeBoundaryType.Ignore)]
public int DiagnosisCount { get; set; }
[StringLengthValidator(6, ErrorMessage = "Name must not exceed 6 chars.")]
public string Name { get; set; }
}
Also, the docs indicate the accepted approach is to declare all return types as object. However, the docs also talk about using properties but in their example use fields so take it under advisement. :)