java.lang.NullPointerException while trying to update sql table - sql

i'm trying to update "PA49_Debut"column with :
- the"PA49_Debut" value from the second table if the product exists there
- else "PA49_Debut" from the same table if the product exists in the previous rows
- else "PA49_Actuel" from the same table
take a look to the code in the repository
public interface FSrepository extends JpaRepository<FSmodel, String> {
#Transactional
#Modifying
#Query("Update FSmodel FS Set FS.PA49_Debut = CASE WHEN EXISTES (SELECT 1 FROM ISmodel SI where FS.Partnumber=SI.Partnumber)" +
" THEN SI.PA49 " +
"WHEN EXISTS (SELECT 1 FROM FSmodel FS2 WHERE FS.Partnumber=FS2.Partnumber AND FS.id>FS2.id )" +
" THEN FS2.PA49_Debut "+
" ELSE (FS.PA49_Actuel) "
+ "END" )
void setPA49Debut(); ```
these are my ISmodel and FSmodel entities
#Entity
public class FSmodel {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public Long id;
public String Partnumber;
public String Materialgroup;
public String Warehouse;
public Float Value;
public Float Stock;
public Float PA49_Actuel;
public Float PA49_Debut;
public String article_fg;
public Float Priceperunit;}
#Entity
public class ISmodel {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public Long id;
public String Partnumber;
public String Materialgroup;
public String Warehouse;
public Float Stock;
public Float Value;
public Float PA49;
public Float STPA49; }
and that is the error
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'controller': Unsatisfied dependency expressed through field 'FSrepos'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'FSrepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Validation failed for query for method public abstract void com.example.demo2.repository.FSrepository.setPA49Debut()!
Caused by: java.lang.NullPointerException: null
Any help would be greatly appreciated. Thanks.

the solution to NullPointerException: null is to
try #Query(vaue="your query goes here",nativeQuery = true)
as #stacker said but i found out that there is a lot of mistakes in my code,i'll try to explain them in hope it helps someone else.
The first
CASE WHEN EXISTES spelled wrongly. Have to be written CASE WHEN EXISTS #alex
The second is using SI out of the select statement i resolved this problem by adding a new select statement.
The third mistake is to update my table by subquery the same table in those lines "WHEN EXISTS (SELECT 1 FROM FSmodel FS2 WHERE FS.Partnumber=FS2.Partnumber AND FS.id>FS2.id )" +
" THEN FS2.PA49_Debut "
and i found the solution as well as the explanation here
Eventually this is my final code and it works great
#Transactional
#Modifying
#Query(value="Update FSmodel FS Set FS.PA49_Debut = CASE" +
" WHEN exists(SELECT SI.Partnumber FROM ISmodel SI where FS.Partnumber=SI.Partnumber)"
+ " THEN (select distinct S.PA49 from ISmodel S where FS.Partnumber=S.Partnumber)" +
"WHEN exists(SELECT 1 FroM (SELECT * FROM FSmodel) AS FSd WHERE FS.Partnumber=FSd.Partnumber AND FS.id>FSd.id )"
+ " THEN (select distinct F.PA49_Debut from (SELECT * FROM FSmodel) AS F where FS.Partnumber=F.Partnumber AND FS.id>F.id)+1" +
" ELSE (FS.PA49_Actuel )" + "END",nativeQuery = true)
void setPA49Debut();

Related

How to check collection for null in spring data jpa #Query with in predicate

I have this query in my spring data jpa repository:
#Query("SELECT table1 FROM Table1 table1 "
+ "INNER JOIN FETCH table1.error error"
+ "WHERE table1.date = ?1 "
+ "AND (COALESCE(?2) IS NULL OR (table1.code IN ?2)) "
+ "AND (COALESCE(?3) IS NULL OR (error.errorCode IN ?3)) ")
List<Table1> findByFilter(Date date, List<String> codes, List<String> errorCodes);
When I run this query, it shows me this error by console:
org.postgresql.util.PSQLException: ERROR: operator does not exist: character varying = bytea
Hint: No operator matches the given name and argument types. You might need to add explicit type casts.
Position: 1642
However if I run the query without the (COALESCE (?2) IS NULL OR part, just the table1.code IN ?2, it does work
Does anyone know what this error could be due to?
COALESCE with one parameter does not make sense. This is an abbreviated CASE expression that returns the first non-null operand. (See this)
I would suggest you to use named parameters instead of position-based parameters. As it's stated in the documentation this makes query methods a little error-prone when refactoring regarding the parameter position.
As it's stated in documentation related to the IN predicate:
The list of values can come from a number of different sources. In the constructor_expression and collection_valued_input_parameter, the list of values must not be empty; it must contain at least one value.
I would suggest you also avoid to use outdated Date and use instead java 8 Date/Time API.
So, taken into account all above, you should use a dynamic query as it was suggested also in comments by #SimonMartinelli. Particularly you can have a look at the specifications.
Assuming that you have the following mapping:
#Entity
public class Error
{
#Id
private Long id;
private String errorCode;
// ...
}
#Entity
public class Table1
{
#Id
private Long id;
private LocalDateTime date;
private String code;
#ManyToOne
private Error error;
// ...
}
you can write the following specification:
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.CollectionUtils;
public class TableSpecs
{
public static Specification<Table1> findByFilter(LocalDateTime date, List<String> codes, List<String> errorCodes)
{
return (root, query, builder) -> {
root.fetch("error", JoinType.LEFT);
Predicate result = builder.equal(root.get("date"), date);
if (!CollectionUtils.isEmpty(codes)) {
result = builder.and(result, root.get("code").in(codes));
}
if (!CollectionUtils.isEmpty(errorCodes)) {
result = builder.and(result, root.get("error").get("errorCode").in(errorCodes));
}
return result;
};
}
}
public interface TableRepository extends CrudRepository<Table1, Long>, JpaSpecificationExecutor<Table1>
{
default List<Table1> findByFilter(LocalDateTime date, List<String> codes, List<String> errorCodes)
{
return findAll(TableSpecs.findByFilter(date, codes, errorCodes));
}
}
and then use it:
List<Table1> results = tableRepository.findByFilter(date, Arrays.asList("TBL1"), Arrays.asList("ERCODE2")));

SQLNative query returning empty results

I'm trying to execute a query which needs 4 tables :
#Query(value="SELECT e.* FROM erreur e, synop sy, synop_decode sd, station st WHERE e.id_synop = sd.id_synop_decode "
+ "and sd.id_synop_decode = sy.id_synop" + " and DATE(sy.date)= :date and "
+ "sy.id_station = st.id_station and st.id_station= :stationId", nativeQuery=true)
public List<Erreur> recherche(#Param("date") Date date, #Param("stationId") Long stationId);
This query works fine et native sql, i pass an existing stationId and a date like the following :
SELECT e.* FROM erreur e, synop sy, synop_decode sd, station st WHERE e.id_synop = sd.id_synop_decode and sd.id_synop_decode = sy.id_synop
and DATE(sy.date)= '2019-05-27' and sy.id_station = st.id_station and st.id_station= 60355;
This query works fine in Mysql Workbench.
Here's the actual controller i'm using for testing purpose :
#GetMapping("/station/{stationId}/erreurs/today")
public List<Erreur> getTodayErreurByStationId(#PathVariable Long stationId)
{
List<Erreur> erreurs = new ArrayList<Erreur>();
Optional<Station> stationOptional = stationRepository.findById(stationId);
if(stationOptional.isPresent())
{
return erreurRepository.recherche(new Date(), stationId);
}
return null;
}
The expected results are the actual "Ererur" objects in my array list, but RestClient just returns an empty array [], while the query works just fine in mysql like i described it above.
So my question is : How can i write this query into Hql language so that i can return the right entities. Or how can i map my sql results to my target custom calss "Erreur"?
#Entity
#Getter #Setter #NoArgsConstructor
#Table(name="erreur")
public class Erreur {
public Erreur(int section, int groupe, String info) {
this.section = section;
this.groupe = groupe;
this.info = info;
}
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id_erreur")
private Long id;
#ManyToOne(cascade= {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH},
fetch=FetchType.LAZY)
#JsonIgnore
#JoinColumn(name="id_synop")
private SynopDecode synopDecode;
#OneToOne
#JoinColumn(name="id_controle")
private Controle controle;
#ManyToOne(cascade= {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH},
fetch=FetchType.LAZY)
#JsonIgnore
#JoinColumn(name="id_station")
private Station station;
#Column(name="section")
private int section;
#Column(name="groupe")
private int groupe;
#Column(name="info")
private String info;
}
If you want to use jpa convention directly then you will have to make associations between different entities i.e. how two entities are linked. When we define these associations then spring jpa knows how to convert method name or custom queries into SQL.
Your code will need to be something like
public class Erreur {
...
#ManyToOne
#JoinColumns//define how Erreur and SynopeDecone are linked
private SynopDecode synopDecode;
...
public class SynopDecode {
...
#ManyToOne // or #OneToOne its not mentioned in question how these two are linked
#JoinColumns//define how SynopDecode and Synop are linked
private Synop synop;
...
Then you can write your query like
#Query("select e from Erreur e LEFT JOIN e.synopDecode sy LEFT JOIN sy.synop sy WHERE DATE(sy.date) = :date AND sy.id_station = :stationId")
List<Erreur> getByDateAndStationId(#Param("date") Date date, #Param("stationId") Long stationId)
You can't use method name based query because you want to use SQL function to match only "date" part of your date and not the whole timestamp.
You can use jpa methods by conventions.
Assuming SynopDecode has property like:
//SynopDecode class
#ManyToOne
private Synop synop;
//repository interface
List<Erreur> findByStationIdAndSynopDecodeSynopDate(Long stationId, Date date);
//or
//List<Erreur> findByStationIdAndSynopDecode_Synop_Date(Long stationId, Date date);
UPDATE
As Punit Tiwan (#punit-tiwan) note that, the above methods used for a specific datettime.
You can use methods below for just DATE.
//repository interface
List<Erreur> findByStationIdAndSynopDecodeSynopDateBetween(Long stationId, Date startOfDate, Date endOfDate);
//or
//List<Erreur> findByStationIdAndSynopDecode_Synop_DateBetween(Long stationId, Date startOfDate, Date endOfDate);
I figured a way to get the same results as my SQL Query using the #Query annotation and accessing object properties like this :
#Query("from Erreur e where e.synopDecode.synop.station.id = :stationId and "
+ "DATE(e.synopDecode.synop.date) = :date")
public List<Erreur> recherche(#Param("date") Date date, #Param("stationId") Long stationId);
I think it solves my problem, thanks for the help

Spring data with custom query - like, between, like and order by

how I can pass this string "sql" for a Spring Data #Query?
My Class Resources:
public void findMessage(String startDate, String endDate, String id, String values, String fieldName, String sort){
String sql = "SELECT e FROM message e WHERE (e.create_at BETWEEN '"+
startDate+ "' AND "+"'"+ endDate +"') AND (e.id = '"+id+"')
AND (e.category LIKE '%"+values+"%') ORDER BY
e."+fieldName+" "+sortParam;
}
My Custom Repository:
public interface CustomMessageRepository extends PagingAndSortingRepository<Message,Long>{
#Query(value = **MyStringSql**)
public Page<Message> find(Pageable page);
}
I'm using JHipster + Spring Data. Using the examples I found, just can not run queries are passed parameter as fieldName and sort.
What is the solution?
Thank you.

How to select rows from SQLite table which fall between today's date and 7 days time

public class DbHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "assignment_info.db";
public static final String ASSIGNMENT_ID = "_id";
public static final String ASSIGNMENT_VALUE = "assignment_value";
public static final String ASSIGNMENT_RESULT = "assignment_result";
public static final String SUBJECT = "subject";
public static final String ASSIGNMENT_TITLE = "assignment_title";
public static final String DUE_DATE = "due_date";
public static final String NOTES_ASSIGNMENT = "notes";
public static final String TABLE_NAME_ASSIGNMENT = "assignment_information";
private static final String ADD_QUERY = "CREATE TABLE "+
TABLE_NAME_ASSIGNMENT+"("+
ASSIGNMENT_ID+" INTEGER PRIMARY KEY AUTOINCREMENT,"+
SUBJECT+" TEXT,"+
ASSIGNMENT_VALUE+" INTEGER,"+
ASSIGNMENT_RESULT+" DOUBLE,"+
ASSIGNMENT_TITLE+" TEXT,"+
DUE_DATE+" DATE,"+
NOTES_ASSIGNMENT+" TEXT);";
public DbHelper(Context context){
super(context,DATABASE_NAME,null,DATABASE_VERSION);
Log.e("Database Operations","Database opened/created from constructor.");
}
#Override
public void onCreate(SQLiteDatabase db) {
//IF DATABASE IS CREATED FOR FIRST TIME THIS METHOD WILL BE CALLED
db.execSQL(ADD_QUERY);
db.execSQL(ADD_NOTES_QUERY);
Log.e("Database Operations"," DbHelper - onCreate called ");
}
I have an SQLite table which contains dates stored as strings >>
"+ DUE_DATE+" STRING," .
I want to select all rows which fall between todays date and 7 days from now.
This is what I have so far, and it will not return any results,
String sql = "SELECT * FROM " + TABLE_NAME_ASSIGNMENT + " WHERE "+ DUE_DATE + " >= date('now') AND date('now','+7 days')";
Can anyone please help??
Thanks
CREATE TABLE TABLE_NAME_ASSIGNMENT
(ASSIGNMENT_ID INT, SUBJECT TEXT, ASSIGNMENT_TITLE TEXT, DUE_DATE DATE)
;
INSERT INTO TABLE_NAME_ASSIGNMENT
(ASSIGNMENT_ID, SUBJECT, ASSIGNMENT_TITLE, DUE_DATE)
VALUES
(1, 'MATHS', 'Prime Numbers', '2015-08-22'),
(2, 'SCIENCE', 'Periodic Table', '2015-08-30'),
(3, 'MATHS', 'Odd Numbers', '2015-09-02')
;
SELECT
*
FROM
TABLE_NAME_ASSIGNMENT
WHERE Due_Date BETWEEN date('now') AND date('now','+7 days')
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''+7 days')' at line 10
Have you tried using BETWEEN?
String sql = "SELECT * FROM " + TABLE_NAME_ASSIGNMENT + " WHERE "+ DUE_DATE + " BETWEEN date('now') AND date('now','+7 days')"
Also make sure that DUE_DATE is type DATE of the form YYYY-MM-DD
As in your table you have stored date as string type so you can not retrieve data in traditional way unless you convert your date string in actual date format while executing query.
Please try once again using the following query.
SELECT
*
FROM
Mytable
WHERE
DATE(substr(fecha ,1,4) ||substr(fecha ,6,2)||substr(fecha ,9,2))
BETWEEN
DATE(20140105)
AND
DATE(20140105);

To have or not to have enums [duplicate]

How do I automatically create an enum and subsequently use its values in C# based on values in a database lookup table (using enterprise library data layer)?
For example, If I add a new lookup value in the database, I don't want to have to manually add the extra static enum value declaration in code - I'd like to keep the enum in sync with the database.
Is there such a thing as this?
I don't want to create a code generated static enum (as per The Code Project article Enum Code Generator - Generating enum code automatically from database look up tables) and would prefer it to be completely automatic.
I'm doing this exact thing, but you need to do some kind of code generation for this to work.
In my solution, I added a project "EnumeratedTypes". This is a console application which gets all of the values from the database and constructs the enums from them. Then it saves all of the enums to an assembly.
The enum generation code is like this:
// Get the current application domain for the current thread
AppDomain currentDomain = AppDomain.CurrentDomain;
// Create a dynamic assembly in the current application domain,
// and allow it to be executed and saved to disk.
AssemblyName name = new AssemblyName("MyEnums");
AssemblyBuilder assemblyBuilder = currentDomain.DefineDynamicAssembly(name,
AssemblyBuilderAccess.RunAndSave);
// Define a dynamic module in "MyEnums" assembly.
// For a single-module assembly, the module has the same name as the assembly.
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(name.Name,
name.Name + ".dll");
// Define a public enumeration with the name "MyEnum" and an underlying type of Integer.
EnumBuilder myEnum = moduleBuilder.DefineEnum("EnumeratedTypes.MyEnum",
TypeAttributes.Public, typeof(int));
// Get data from database
MyDataAdapter someAdapter = new MyDataAdapter();
MyDataSet.MyDataTable myData = myDataAdapter.GetMyData();
foreach (MyDataSet.MyDataRow row in myData.Rows)
{
myEnum.DefineLiteral(row.Name, row.Key);
}
// Create the enum
myEnum.CreateType();
// Finally, save the assembly
assemblyBuilder.Save(name.Name + ".dll");
My other projects in the solution reference this generated assembly. As a result, I can then use the dynamic enums in code, complete with intellisense.
Then, I added a post-build event so that after this "EnumeratedTypes" project is built, it runs itself and generates the "MyEnums.dll" file.
By the way, it helps to change the build order of your project so that "EnumeratedTypes" is built first. Otherwise, once you start using your dynamically generated .dll, you won't be able to do a build if the .dll ever gets deleted. (Chicken and egg kind of problem -- your other projects in the solution need this .dll to build properly, and you can't create the .dll until you build your solution...)
I got most of the above code from this msdn article.
Enums must be specified at compile time, you can't dynamically add enums during run-time - and why would you, there would be no use/reference to them in the code?
From Professional C# 2008:
The real power of enums in C# is that behind the scenes they are instantiated as structs derived from the base class, System.Enum . This means it is possible to call methods against them to perform some useful tasks. Note that because of the way the .NET Framework is implemented there is no performance loss associated with treating the enums syntactically as structs. In practice, once your code is compiled, enums will exist as primitive types, just like int and float .
So, I'm not sure you can use Enums the way you want to.
Does it have to be an actual enum? How about using a Dictionary<string,int> instead?
for example
Dictionary<string, int> MyEnum = new Dictionary(){{"One", 1}, {"Two", 2}};
Console.WriteLine(MyEnum["One"]);
I've done this with a T4 template. It is fairly trivial to drop a .tt file into your project, and set up Visual Studio to run the T4 template as a pre-build step.
The T4 generates a .cs file, which means you can have it just query the database and build an enum in a .cs file from the result. Wired up as a pre-build task, it would re-create your enum on every build, or you can run the T4 manually as needed instead.
Let's say you have the following in your DB:
table enums
-----------------
| id | name |
-----------------
| 0 | MyEnum |
| 1 | YourEnum |
-----------------
table enum_values
----------------------------------
| id | enums_id | value | key |
----------------------------------
| 0 | 0 | 0 | Apple |
| 1 | 0 | 1 | Banana |
| 2 | 0 | 2 | Pear |
| 3 | 0 | 3 | Cherry |
| 4 | 1 | 0 | Red |
| 5 | 1 | 1 | Green |
| 6 | 1 | 2 | Yellow |
----------------------------------
Construct a select to get the values you need:
select * from enums e inner join enum_values ev on ev.enums_id=e.id where e.id=0
Construct the source code for the enum and you'll get something like:
String enumSourceCode = "enum " + enumName + "{" + enumKey1 + "=" enumValue1 + "," + enumKey2 + ... + "}";
(obviously this is constructed in a loop of some kind.)
Then comes the fun part, Compiling your enum and using it:
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters cs = new CompilerParameters();
cp.GenerateInMemory = True;
CompilerResult result = provider.CompileAssemblyFromSource(cp, enumSourceCode);
Type enumType = result.CompiledAssembly.GetType(enumName);
Now you have the type compiled and ready for use.
To get a enum value stored in the DB you can use:
[Enum].Parse(enumType, value);
where value can be either the integer value (0, 1, etc.) or the enum text/key (Apple, Banana, etc.)
Just showing the answer of Pandincus with "of the shelf" code and some explanation:
You need two solutions for this example ( I know it could be done via one also ; ), let the advanced students present it ...
So here is the DDL SQL for the table :
USE [ocms_dev]
GO
CREATE TABLE [dbo].[Role](
[RoleId] [int] IDENTITY(1,1) NOT NULL,
[RoleName] [varchar](50) NULL
) ON [PRIMARY]
So here is the console program producing the dll:
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Data.Common;
using System.Data;
using System.Data.SqlClient;
namespace DynamicEnums
{
class EnumCreator
{
// after running for first time rename this method to Main1
static void Main ()
{
string strAssemblyName = "MyEnums";
bool flagFileExists = System.IO.File.Exists (
AppDomain.CurrentDomain.SetupInformation.ApplicationBase +
strAssemblyName + ".dll"
);
// Get the current application domain for the current thread
AppDomain currentDomain = AppDomain.CurrentDomain;
// Create a dynamic assembly in the current application domain,
// and allow it to be executed and saved to disk.
AssemblyName name = new AssemblyName ( strAssemblyName );
AssemblyBuilder assemblyBuilder =
currentDomain.DefineDynamicAssembly ( name,
AssemblyBuilderAccess.RunAndSave );
// Define a dynamic module in "MyEnums" assembly.
// For a single-module assembly, the module has the same name as
// the assembly.
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule (
name.Name, name.Name + ".dll" );
// Define a public enumeration with the name "MyEnum" and
// an underlying type of Integer.
EnumBuilder myEnum = moduleBuilder.DefineEnum (
"EnumeratedTypes.MyEnum",
TypeAttributes.Public,
typeof ( int )
);
#region GetTheDataFromTheDatabase
DataTable tableData = new DataTable ( "enumSourceDataTable" );
string connectionString = "Integrated Security=SSPI;Persist " +
"Security Info=False;Initial Catalog=ocms_dev;Data " +
"Source=ysg";
using (SqlConnection connection =
new SqlConnection ( connectionString ))
{
SqlCommand command = connection.CreateCommand ();
command.CommandText = string.Format ( "SELECT [RoleId], " +
"[RoleName] FROM [ocms_dev].[dbo].[Role]" );
Console.WriteLine ( "command.CommandText is " +
command.CommandText );
connection.Open ();
tableData.Load ( command.ExecuteReader (
CommandBehavior.CloseConnection
) );
} //eof using
foreach (DataRow dr in tableData.Rows)
{
myEnum.DefineLiteral ( dr[1].ToString (),
Convert.ToInt32 ( dr[0].ToString () ) );
}
#endregion GetTheDataFromTheDatabase
// Create the enum
myEnum.CreateType ();
// Finally, save the assembly
assemblyBuilder.Save ( name.Name + ".dll" );
} //eof Main
} //eof Program
} //eof namespace
Here is the Console programming printing the output ( remember that it has to reference the dll ). Let the advance students present the solution for combining everything in one solution with dynamic loading and checking if there is already build dll.
// add the reference to the newly generated dll
use MyEnums ;
class Program
{
static void Main ()
{
Array values = Enum.GetValues ( typeof ( EnumeratedTypes.MyEnum ) );
foreach (EnumeratedTypes.MyEnum val in values)
{
Console.WriteLine ( String.Format ( "{0}: {1}",
Enum.GetName ( typeof ( EnumeratedTypes.MyEnum ), val ),
val ) );
}
Console.WriteLine ( "Hit enter to exit " );
Console.ReadLine ();
} //eof Main
} //eof Program
Aren't we coming to this from the wrong direction?
If the data is likely to change at all during the lifetime of the deployed release then an enum is just not appropriate, and you need to use a dictionary, hash or other dynamic collection.
If you know the set of possible values is fixed for the life of the deployed release, then an enum is preferable.
If you must have something in your database that replicates the enumerated set, then why not add a deployment step to clear and repopulate the database table with the definitive set of enum values?
I always like to write my own "custom enum". Than I have one class that is a little bit more complex, but I can reuse it:
public abstract class CustomEnum
{
private readonly string _name;
private readonly object _id;
protected CustomEnum( string name, object id )
{
_name = name;
_id = id;
}
public string Name
{
get { return _name; }
}
public object Id
{
get { return _id; }
}
public override string ToString()
{
return _name;
}
}
public abstract class CustomEnum<TEnumType, TIdType> : CustomEnum
where TEnumType : CustomEnum<TEnumType, TIdType>
{
protected CustomEnum( string name, TIdType id )
: base( name, id )
{ }
public new TIdType Id
{
get { return (TIdType)base.Id; }
}
public static TEnumType FromName( string name )
{
try
{
return FromDelegate( entry => entry.Name.Equals( name ) );
}
catch (ArgumentException ae)
{
throw new ArgumentException( "Illegal name for custom enum '" + typeof( TEnumType ).Name + "'", ae );
}
}
public static TEnumType FromId( TIdType id )
{
try
{
return FromDelegate( entry => entry.Id.Equals( id ) );
}
catch (ArgumentException ae)
{
throw new ArgumentException( "Illegal id for custom enum '" + typeof( TEnumType ).Name + "'", ae );
}
}
public static IEnumerable<TEnumType> GetAll()
{
var elements = new Collection<TEnumType>();
var infoArray = typeof( TEnumType ).GetFields( BindingFlags.Public | BindingFlags.Static );
foreach (var info in infoArray)
{
var type = info.GetValue( null ) as TEnumType;
elements.Add( type );
}
return elements;
}
protected static TEnumType FromDelegate( Predicate<TEnumType> predicate )
{
if(predicate == null)
throw new ArgumentNullException( "predicate" );
foreach (var entry in GetAll())
{
if (predicate( entry ))
return entry;
}
throw new ArgumentException( "Element not found while using predicate" );
}
}
Now I just need to create my enum I want to use:
public sealed class SampleEnum : CustomEnum<SampleEnum, int>
{
public static readonly SampleEnum Element1 = new SampleEnum( "Element1", 1, "foo" );
public static readonly SampleEnum Element2 = new SampleEnum( "Element2", 2, "bar" );
private SampleEnum( string name, int id, string additionalText )
: base( name, id )
{
AdditionalText = additionalText;
}
public string AdditionalText { get; private set; }
}
At last I can use it like I want:
static void Main( string[] args )
{
foreach (var element in SampleEnum.GetAll())
{
Console.WriteLine( "{0}: {1}", element, element.AdditionalText );
Console.WriteLine( "Is 'Element2': {0}", element == SampleEnum.Element2 );
Console.WriteLine();
}
Console.ReadKey();
}
And my output would be:
Element1: foo
Is 'Element2': False
Element2: bar
Is 'Element2': True
You want System.Web.Compilation.BuildProvider
I also doubt the wisdom of doing this, but then there maybe a good use case that I can't think of.
What you're looking for are Build Providers i.e. System.Web.Compilation.BuildProvider
They're used very effectively by SubSonic, you can download the source and see how they use them, you won't need anything half as intricate as what they're doing.
Hope this helps.
Using dynamic enums is bad no matter which way. You will have to go through the trouble of "duplicating" the data to ensure clear and easy code easy to maintain in the future.
If you start introducing automatic generated libraries, you are for sure causing more confusion to future developers having to upgrade your code than simply making your enum coded within the appropriate class object.
The other examples given sound nice and exciting, but think about the overhead on code maintenance versus what you get from it. Also, are those values going to change that frequently?
Word up, I as well got tired of writing out enumerations based on Id / Name db table columns, copying and pasting stuff from queries in SSMS.
Below is a super dirty stored procedure that takes as input a table name, the column name you want to use for the c# enumeration name, and the column name that you want to use for the c# enumeration value.
Most of theses table names I work with a) end with "s" b) have a [TABLENAME]Id column and c) have a [TABLENAME]Name column, so there are a couple if statements that will assume that structure, in which case, the column name parameters are not required.
A little context for these examples - "Stonk" here doesn't really mean "stock" but kinda, the way I'm using "stonk" it means "a thing that has some numbers associated to it for a time period" But that's not important, it's just an example of table with this Id / Name schema. It looks like this:
CREATE TABLE StonkTypes (
StonkTypeId TINYINT IDENTITY(1,1) PRIMARY KEY NOT NULL,
StonkTypeName VARCHAR(200) NOT NULL CONSTRAINT UQ_StonkTypes_StonkTypeName UNIQUE (StonkTypeName)
)
After I create the proc, this statement:
EXEC CreateCSharpEnum 'StonkTypes'
Selects this string:
public enum StonkTypes { Stonk = 1, Bond = 2, Index = 3, Fund = 4, Commodity = 5,
PutCallRatio = 6, }
Which I can copy and paste into a C# file.
I have a Stonks table and it has StonkId and StonkName columns so this exec:
EXEC CreateCSharpEnum 'Stonks'
Spits out:
public enum Stonks { SP500 = 1, DowJonesIndustrialAverage = 2, ..... }
But for that enum I want to use the "Symbol" column for the enum name values so this:
EXEC CreateCSharpEnum 'Stonks', 'Symbol'
Does the trick and renders:
public enum Stonks { SPY = 1, DIA = 2, ..... }
Without further ado, here is this dirty piece of craziness. Yeah, very dirty, but I'm kind of pleased with myself - it's SQL code that constructs SQL code that constructs C# code. Couple layers involved.
CREATE OR ALTER PROCEDURE CreateCSharpEnum
#TableName VARCHAR(MAX),
#EnumNameColumnName VARCHAR(MAX) = NULL,
#EnumValueColumnName VARCHAR(MAX) = NULL
AS
DECLARE #LastCharOfTableName VARCHAR(1)
SELECT #LastCharOfTableName = RIGHT(#TableName, 1)
PRINT 'Last char = [' + #LastCharOfTableName + ']'
DECLARE #TableNameWithoutS VARCHAR(MAX)
IF UPPER(#LastCharOfTableName) = 'S'
SET #TableNameWithoutS = LEFT(#TableName, LEN(#TableName) - 1)
ELSE
SET #TableNameWithoutS = #TableName
PRINT 'Table name without trailing s = [' + #TableNameWithoutS + ']'
IF #EnumNameColumnName IS NULL
BEGIN
SET #EnumNameColumnName = #TableNameWithoutS + 'Name'
END
PRINT 'name col name = [' + #EnumNameColumnName + ']'
IF #EnumValueColumnName IS NULL
SET #EnumValueColumnName = #TableNameWithoutS + 'Id'
PRINT 'value col name = [' + #EnumValueColumnName + ']'
-- replace spaces and punctuation
SET #EnumNameColumnName = 'REPLACE(' + #EnumNameColumnName + ', '' '', '''')'
SET #EnumNameColumnName = 'REPLACE(' + #EnumNameColumnName + ', ''&'', '''')'
SET #EnumNameColumnName = 'REPLACE(' + #EnumNameColumnName + ', ''.'', '''')'
SET #EnumNameColumnName = 'REPLACE(' + #EnumNameColumnName + ', ''('', '''')'
SET #EnumNameColumnName = 'REPLACE(' + #EnumNameColumnName + ', '')'', '''')'
PRINT 'name col name with replace sql = [' + #EnumNameColumnName + ']'
DECLARE #SqlStr VARCHAR(MAX) = 'SELECT ' + #EnumNameColumnName
+ ' + '' = '''
+ ' + LTRIM(RTRIM(STR(' + #EnumValueColumnName + '))) + '','' FROM ' + #TableName + ' ORDER BY ' + #EnumValueColumnName
PRINT 'sql that gets rows for enum body = [' + #SqlStr + ']'
CREATE TABLE #EnumRowsTemp (s VARCHAR(MAX))
INSERT
INTO #EnumRowsTemp
EXEC(#SqlStr)
--SELECT * FROM #EnumRowsTemp
DECLARE #csharpenumbody VARCHAR(MAX)
SELECT #csharpenumbody = COALESCE(#csharpenumbody + ' ', '') + s FROM #EnumRowsTemp
--PRINT #csharpenumbody
DECLARE #csharpenum VARCHAR(MAX) = 'public enum ' + #TableName + ' { ' + #csharpenumbody + ' }'
PRINT #csharpenum
SELECT #csharpenum
DROP TABLE #EnumRowsTemp
Please, be critical. One funky thing I didn't understand, how come I have to create and drop this #EnumRowsTemp table and not just "SELECT INTO #EnumRowsTemp" to create the temp table on the fly? I don't know the answer, I tried that and it didn't work. That's probably the least of the problems of this code...
As dirty as it may be... I hope this saves some of you fellow dorks a little bit of time.
I don't think there is a good way of doing what you want. And if you think about it I don't think this is what you really want.
If you would have a dynamic enum, it also means you have to feed it with a dynamic value when you reference it. Maybe with a lot of magic you could achieve some sort of IntelliSense that would take care of this and generate an enum for you in a DLL file. But consider the amount of work it would take, how uneffective it would be to access the database to fetch IntelliSense information as well as the nightmare of version controlling the generated DLL file.
If you really don't want to manually add the enum values (you'll have to add them to the database anyway) use a code generation tool instead, for example T4 templates. Right click+run and you got your enum statically defined in code and you get all the benefits of using enums.
One way to keep the Enums and to create a Dynamic list of values at the same time is to use the Enums that you currently have with a Dynamically created Dictionary.
Since most Enums are used in the context that they are defined to be used, and the "dynamic enums" will be supported by dynamic processes, you can distinguish the 2.
The first step is to create a table/collection that houses the IDs and References for the Dynamic Entries. In the table you will autoincrement much larger than your largest Enum value.
Now comes the part for your dynamic Enums, I am assuming that you will be using the Enums to create a set of conditions that apply a set of rules, some are dynamically generated.
Get integer from database
If Integer is in Enum -> create Enum -> then run Enum parts
If Integer is not a Enum -> create Dictionary from Table -> then run Dictionary parts.
enum builder class
public class XEnum
{
private EnumBuilder enumBuilder;
private int index;
private AssemblyBuilder _ab;
private AssemblyName _name;
public XEnum(string enumname)
{
AppDomain currentDomain = AppDomain.CurrentDomain;
_name = new AssemblyName("MyAssembly");
_ab = currentDomain.DefineDynamicAssembly(
_name, AssemblyBuilderAccess.RunAndSave);
ModuleBuilder mb = _ab.DefineDynamicModule("MyModule");
enumBuilder = mb.DefineEnum(enumname, TypeAttributes.Public, typeof(int));
}
/// <summary>
/// adding one string to enum
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public FieldBuilder add(string s)
{
FieldBuilder f = enumBuilder.DefineLiteral(s, index);
index++;
return f;
}
/// <summary>
/// adding array to enum
/// </summary>
/// <param name="s"></param>
public void addRange(string[] s)
{
for (int i = 0; i < s.Length; i++)
{
enumBuilder.DefineLiteral(s[i], i);
}
}
/// <summary>
/// getting index 0
/// </summary>
/// <returns></returns>
public object getEnum()
{
Type finished = enumBuilder.CreateType();
_ab.Save(_name.Name + ".dll");
Object o1 = Enum.Parse(finished, "0");
return o1;
}
/// <summary>
/// getting with index
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
public object getEnum(int i)
{
Type finished = enumBuilder.CreateType();
_ab.Save(_name.Name + ".dll");
Object o1 = Enum.Parse(finished, i.ToString());
return o1;
}
}
create an object
string[] types = { "String", "Boolean", "Int32", "Enum", "Point", "Thickness", "long", "float" };
XEnum xe = new XEnum("Enum");
xe.addRange(types);
return xe.getEnum();
You could use CodeSmith to generate something like this:
http://www.csharping.com/PermaLink,guid,cef1b637-7d37-4691-8e49-138cbf1d51e9.aspx